- 浏览: 1748485 次
- 性别:
- 来自: 大连
博客专栏
-
Spring数据库访问系列...
浏览量:172858
-
Android学习笔记
浏览量:366623
-
iBatis开发详解
浏览量:188354
-
Objective-C学习...
浏览量:98808
最新评论
-
iLidy:
引用[/c
Hibernate持久化对象的生命周期详解 -
fengzigogo:
您好,有这个项目架构的源码下载地址吗?
一种Java Web应用开发框架的构建(基于Struts2+Spring+FreeMarker)之一 -
spring_springmvc:
可以参考最新的文档:如何在eclipse jee中检出项目并转 ...
用Maven构建Java Web开发环境(Jetty容器)之二 -
springdata_springmvc:
spring mvc demo教程源代码下载,地址:http: ...
Spring 3之MVC & Security简单整合开发(二) -
赵庆辉:
看帖回复是美德,楼主讲的很清晰明了,看了豁然开朗.
Java String对象的经典问题(new String())
本文系iBatis开发详解系列文章之在iBatis查询复杂集合
通常我们使用iBatis的select查询都是映射的简单对象,即便在一个查询中连接多个表也是如此,那么既然iBatis是SQL Mapper,也就是说它可以映射复杂集合,我们来看看如何让对象模型向数据模型(关系型数据模型)靠拢。
假设在在线购物应用中,我们有用户表User,订单表Order和订单项表OrderItem,它们之间存在的关联是显而易见的。用户可以下订单,而订单中可以包含多个项。
我们的数据库设计如下:
订单表为:
订单项表为:
三个表之前通过userId和orderId进行关联,这里仅做示例性说明,并没有添加物理外键关联。
下面我们设计POJO来描述这三个对象:
我们使用各个属性连描述orderItem表的字段,下面是order对象:
使用数组来存放每个订单的订单项,分别给出setter和getter方法。下面是user对象:
和OrderInfo类似,我们也使用数组来存放用户所下的订单。
那么实体对象和数据表我们就都有了,下面来编写iBatis的sqlMap文件来描述它们之间的关系,我们自定义resultMap来说明:
描述OrderItem则是最简单的了,就是刻画各个属性即可。
在刻画OrderInfo时,我们加入了一个select查询,就是查到该订单下的所有订单项,并使用数组保存起来,那么上面这段XML代码就好理解了。
和上面的订单项是类似的,我们在查询用户时,也把用户所下的订单都给查出来。最后我们是要获取内系统的所有用户,那么使用下面的这个SQL:
综上的XML表述了这么一个需求,就是查询系统的内的所有用户,以及它们所下的所有订单,并还要查出每个订单项。
那么示例程序就很简单了:
运行程序,我们可以得到如下输出:
查询得到的内容为:
[UserInfo [orderList=[OrderInfo [order=Order [generateTime=Mon Aug 27 21:35:27 CST 2012, orderId=1, orderItems=null, orderName=Mobile Phone], orderItemList=[OrderItem [itemName=Moto MB525, orderItemId=1, orderId=1, price=1000.0, quantity=1], OrderItem [itemName=Moto MB526, orderItemId=2, orderId=1, price=1200.0, quantity=1]]], OrderInfo [order=Order [generateTime=Mon Aug 27 22:28:38 CST 2012, orderId=2, orderItems=null, orderName=Laptop], orderItemList=[OrderItem [itemName=Lenovo X201, orderItemId=3, orderId=2, price=5000.0, quantity=1], OrderItem [itemName=Lenovo X220, orderItemId=4, orderId=2, price=7000.0, quantity=1]]]], user=User [age=0, email=null, mobile=null, password=null, userId=1, userName=Sarin]]]
他们都是以对象的形式来描述的,因为我们命没有查询user的email,mobile等信息,所以它们是null。
程序编写完了,但是问题随之而来。先看这么一大堆的输出,我们仅仅有1个用户的两个订单,每个订单仅包含2个项。如果我们的系统内有10000个用户,每个用户下了100个订单,每个订单有5项,那么一次查询结果就会生成500万个对象,显然在数据不是很多时,就已经带来了问题。首先这是一个数据库I/O的问题,大量数据库I/O和内存对象,降低了性能,消耗了资源。其次是N+1查询问题。
N+1问题也好理解,在本例中,我们要查询一个用户的N个订单,还要查询这N个订单中每个订单的N个订单项,就产生了N+1查询问题。
iBatis提供了针对每个问题的解决方案,但是却不能同时解决这两个问题。
针对数据库I/O,我们很容易想到延迟加载的特性,就是对于所有数据并不是一次全部查出,在需要的时候继续进行查询,那么就会大幅度减少数据库的I/O,开启iBatis的延迟加载特性非常简单,只需修改如下XML设置即可:
也就是在settings中设置了lazyLoadingEnabled为true,如果想开启cglib的增强版,需要在类路径下添加相关jar包,并设置enhancementEnabled为true即可。但是要清楚一点,这些设置都是全局的设置,也就是说项目中的其它iBatis查询都将应用延迟加载特性。下面我们来测试一下代码:
可以看到,在仅有的几条数据时,所消耗的时间也大幅减少,说明延迟加载特性已经启用。
下面我们来看针对N+1查询问题的解决方案,那就是使用iBatis在resultMap中为我们提供的groupBy属性。我们将上面的XML文件修改如下:
resultMap的配置使用了groupBy属性,其余和上面配置类似,而这次我们的查询语句就合并成一个连接查询,测试时,需要修改POJO中的数组变量为单对象变量,那么执行程序后,我们得到如下效果:
也可以看到查询效率的显著提升。
综上所述,延迟加载适用于大型数据集合,但是并非其中的每条记录都会被用到。此方法前期的性能大幅度提高,但是后期仍需加载所需数据。而N+1查询解决方案适用于小型数据集合或者是所有数据都肯定会被用到的数据集合,使得整体性能得到提高。
支持
通常我们使用iBatis的select查询都是映射的简单对象,即便在一个查询中连接多个表也是如此,那么既然iBatis是SQL Mapper,也就是说它可以映射复杂集合,我们来看看如何让对象模型向数据模型(关系型数据模型)靠拢。
假设在在线购物应用中,我们有用户表User,订单表Order和订单项表OrderItem,它们之间存在的关联是显而易见的。用户可以下订单,而订单中可以包含多个项。
我们的数据库设计如下:
CREATE TABLE `user` ( `userId` int(11) NOT NULL AUTO_INCREMENT , `userName` varchar(50) NULL DEFAULT NULL , `password` varchar(32) NULL DEFAULT NULL , `mobile` varchar(11) NULL DEFAULT NULL , `email` varchar(50) NULL DEFAULT NULL , `age` int(3) NULL DEFAULT NULL , PRIMARY KEY (`userId`) )
订单表为:
CREATE TABLE `order` ( `orderId` int(11) NOT NULL AUTO_INCREMENT , `orderName` varchar(50) NULL DEFAULT NULL , `generateTime` datetime NULL DEFAULT NULL , `userId` int(11) NULL DEFAULT NULL , PRIMARY KEY (`orderId`) )
订单项表为:
CREATE TABLE `orderItem` ( `orderItemId` int(11) NOT NULL AUTO_INCREMENT , `itemName` varchar(50) NULL DEFAULT NULL , `quantity` int(3) NULL DEFAULT NULL , `price` float NULL DEFAULT NULL , `orderId` int(11) NOT NULL , PRIMARY KEY (`orderItemId`) )
三个表之前通过userId和orderId进行关联,这里仅做示例性说明,并没有添加物理外键关联。
下面我们设计POJO来描述这三个对象:
package ibatis.model; public class OrderItem implements java.io.Serializable { private Integer orderItemId; private String itemName; private int quantity; private float price; private Integer orderId; public OrderItem() { } public OrderItem(Integer orderItemId, String itemName, int quantity, float price, Integer orderId) { super(); this.orderItemId = orderItemId; this.itemName = itemName; this.quantity = quantity; this.price = price; this.orderId = orderId; } //Setters And Getters public String toString() { return "OrderItem [itemName=" + itemName + ", orderItemId=" + orderItemId+ ", orderId=" + orderId + ", price=" + price + ", quantity=" + quantity + "]"; } }
我们使用各个属性连描述orderItem表的字段,下面是order对象:
package ibatis.model; import java.util.Arrays; public class OrderInfo { private Order order; private OrderItem[] orderItemList; public Order getOrder() { return order; } public void setOrder(Order order) { this.order = order; } public OrderItem[] getOrderItemList() { return orderItemList; } public void setOrderItemList(OrderItem[] orderItemList) { this.orderItemList = orderItemList; } public String toString() { return "OrderInfo [order=" + order + ", orderItemList=" + Arrays.toString(orderItemList) + "]"; } }
使用数组来存放每个订单的订单项,分别给出setter和getter方法。下面是user对象:
package ibatis.model; import java.util.Arrays; public class UserInfo { private User user; private OrderInfo[] orderList; public User getUser() { return user; } public void setUser(User user) { this.user = user; } public OrderInfo[] getOrderList() { return orderList; } public void setOrderList(OrderInfo[] orderList) { this.orderList = orderList; } public String toString() { return "UserInfo [orderList=" + Arrays.toString(orderList) + ", user="+ user + "]"; } }
和OrderInfo类似,我们也使用数组来存放用户所下的订单。
那么实体对象和数据表我们就都有了,下面来编写iBatis的sqlMap文件来描述它们之间的关系,我们自定义resultMap来说明:
<resultMap class="ibatis.model.OrderItem" id="ResultOrderItemMap"> <result property="orderId" column="orderId" /> <result property="orderItemId" column="orderItemId" /> <result property="itemName" column="itemName" /> <result property="quantity" column="quantity" /> <result property="price" column="price" /> </resultMap>
描述OrderItem则是最简单的了,就是刻画各个属性即可。
<resultMap class="ibatis.model.OrderInfo" id="ResultOrderInfoMap"> <result property="order.orderId" column="orderId" /> <result property="order.orderName" column="orderName" /> <result property="order.generateTime" column="generateTime" /> <result property="orderItemList" select="pioneer.getOrderItemList" column="orderId" /> </resultMap> <select id="getOrderItemList" resultMap="ResultOrderItemMap"> select orderId, orderItemId, itemName, quantity, price from orderItem where orderId = #orderId# </select>
在刻画OrderInfo时,我们加入了一个select查询,就是查到该订单下的所有订单项,并使用数组保存起来,那么上面这段XML代码就好理解了。
<resultMap class="ibatis.model.UserInfo" id="ResultUserInfoMap"> <result property="user.userId" column="userId" /> <result property="user.userName" column="userName"/> <result property="orderList" select="pioneer.getOrderInfoList" column="userId" /> </resultMap> <select id="getOrderInfoList" resultMap="ResultOrderInfoMap"> select orderId, orderName, generateTime from test.order where userId = #userId# </select>
和上面的订单项是类似的,我们在查询用户时,也把用户所下的订单都给查出来。最后我们是要获取内系统的所有用户,那么使用下面的这个SQL:
<select id="getUserInfoList" resultMap="ResultUserInfoMap"> select userId, userName from user </select>
综上的XML表述了这么一个需求,就是查询系统的内的所有用户,以及它们所下的所有订单,并还要查出每个订单项。
那么示例程序就很简单了:
package ibatis; import ibatis.model.UserInfo; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import com.ibatis.common.resources.Resources; import com.ibatis.sqlmap.client.SqlMapClient; import com.ibatis.sqlmap.client.SqlMapClientBuilder; public class UserInfoDemo { private static String config = "ibatis/SqlMapConfig.xml"; private static Reader reader; private static SqlMapClient sqlMap; static { try { reader = Resources.getResourceAsReader(config); } catch (IOException e) { e.printStackTrace(); } sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader); } public static void main(String[] args) throws Exception { long start = System.currentTimeMillis(); ArrayList<UserInfo> userInfoList = (ArrayList<UserInfo>) sqlMap .queryForList("pioneer.getUserInfoList"); long end = System.currentTimeMillis(); System.out.println(userInfoList); System.out.println((end - start) + " ms"); }}
运行程序,我们可以得到如下输出:
查询得到的内容为:
[UserInfo [orderList=[OrderInfo [order=Order [generateTime=Mon Aug 27 21:35:27 CST 2012, orderId=1, orderItems=null, orderName=Mobile Phone], orderItemList=[OrderItem [itemName=Moto MB525, orderItemId=1, orderId=1, price=1000.0, quantity=1], OrderItem [itemName=Moto MB526, orderItemId=2, orderId=1, price=1200.0, quantity=1]]], OrderInfo [order=Order [generateTime=Mon Aug 27 22:28:38 CST 2012, orderId=2, orderItems=null, orderName=Laptop], orderItemList=[OrderItem [itemName=Lenovo X201, orderItemId=3, orderId=2, price=5000.0, quantity=1], OrderItem [itemName=Lenovo X220, orderItemId=4, orderId=2, price=7000.0, quantity=1]]]], user=User [age=0, email=null, mobile=null, password=null, userId=1, userName=Sarin]]]
他们都是以对象的形式来描述的,因为我们命没有查询user的email,mobile等信息,所以它们是null。
程序编写完了,但是问题随之而来。先看这么一大堆的输出,我们仅仅有1个用户的两个订单,每个订单仅包含2个项。如果我们的系统内有10000个用户,每个用户下了100个订单,每个订单有5项,那么一次查询结果就会生成500万个对象,显然在数据不是很多时,就已经带来了问题。首先这是一个数据库I/O的问题,大量数据库I/O和内存对象,降低了性能,消耗了资源。其次是N+1查询问题。
N+1问题也好理解,在本例中,我们要查询一个用户的N个订单,还要查询这N个订单中每个订单的N个订单项,就产生了N+1查询问题。
iBatis提供了针对每个问题的解决方案,但是却不能同时解决这两个问题。
针对数据库I/O,我们很容易想到延迟加载的特性,就是对于所有数据并不是一次全部查出,在需要的时候继续进行查询,那么就会大幅度减少数据库的I/O,开启iBatis的延迟加载特性非常简单,只需修改如下XML设置即可:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE sqlMapConfig PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-config-2.dtd"> <sqlMapConfig> ...... <settings useStatementNamespaces="true" lazyLoadingEnabled="true" /> ...... </sqlMapConfig>
也就是在settings中设置了lazyLoadingEnabled为true,如果想开启cglib的增强版,需要在类路径下添加相关jar包,并设置enhancementEnabled为true即可。但是要清楚一点,这些设置都是全局的设置,也就是说项目中的其它iBatis查询都将应用延迟加载特性。下面我们来测试一下代码:
可以看到,在仅有的几条数据时,所消耗的时间也大幅减少,说明延迟加载特性已经启用。
下面我们来看针对N+1查询问题的解决方案,那就是使用iBatis在resultMap中为我们提供的groupBy属性。我们将上面的XML文件修改如下:
<resultMap class="ibatis.model.UserInfo" id="ResultUserInfoMapN" groupBy="user.userId"> <result property="user.userId" column="userId" /> <result property="user.userName" column="userName"/> <result property="orderList" resultMap="pioneer.ResultOrderInfoMapN" /> </resultMap> <resultMap class="ibatis.model.OrderInfo" id="ResultOrderInfoMapN" groupBy="order.orderId"> <result property="order.orderId" column="orderId" /> <result property="order.orderName" column="orderName" /> <result property="order.generateTime" column="generateTime" /> <result property="orderItemList" resultMap="pioneer.ResultOrderItemMapN" /> </resultMap> <resultMap class="ibatis.model.OrderItem" id="ResultOrderItemMapN"> <result property="orderId" column="orderId" /> <result property="orderItemId" column="orderItemId" /> <result property="itemName" column="itemName" /> <result property="quantity" column="quantity" /> <result property="price" column="price" /> </resultMap> <select id="getUserInfoListN" resultMap="ResultUserInfoMapN"> select user.userId as userId, user.userName as userName, test.order.orderId as orderId, test.order.orderName as orderName, test.order.generateTime as generateTime, orderItem.* from user join test.order on user.userId=test.order.userId join orderItem on test.order.orderId=orderItem.orderId order by userId,test.order.orderId,orderItemId </select>
resultMap的配置使用了groupBy属性,其余和上面配置类似,而这次我们的查询语句就合并成一个连接查询,测试时,需要修改POJO中的数组变量为单对象变量,那么执行程序后,我们得到如下效果:
也可以看到查询效率的显著提升。
综上所述,延迟加载适用于大型数据集合,但是并非其中的每条记录都会被用到。此方法前期的性能大幅度提高,但是后期仍需加载所需数据。而N+1查询解决方案适用于小型数据集合或者是所有数据都肯定会被用到的数据集合,使得整体性能得到提高。
评论
2 楼
SunJK000
2012-10-09
hellostory 写道
用mybatis3的人越来越多,如果有就更好
支持
1 楼
hellostory
2012-09-05
用mybatis3的人越来越多,如果有就更好
发表评论
-
iBatis操作DDL和映射继承
2012-09-09 21:46 9195本文系iBatis开发详解系列文章之iBatis操作D ... -
iBatis中使用XML
2012-08-29 19:57 7633本文系iBatis开发详解系列文章之在iBatis中使 ... -
iBatis执行非查询语句(CRUD,函数和过程)
2012-08-26 21:40 9261CRUD操作中除了查询操作,其他都统一称为更新操作,因 ... -
Spring数据库访问之iBatis(二)
2012-06-10 13:56 5703接上文,我们继续来研究Spring和iBatis的整合 ... -
Spring数据库访问之iBatis(一)
2012-01-02 18:45 15866为了丰富博客专栏【Spring数据库访问系列】的内容, ... -
我的视频教程《中小企业OA系统》
2011-07-29 22:27 7689经过5个月的制作,和华章合作的《中小企业OA系统》Ja ... -
iBatis分页(基于Struts2和Freemarker)
2011-05-02 10:05 13035之前介绍过基于Hibernate分页的原理和设计,这里 ... -
Spring数据库访问之ORM(三)
2011-03-02 20:35 17551本文接上一篇继续研究。 之前我们使用的是Hib ... -
Spring数据库访问之ORM(二)
2011-02-16 13:19 23311本文接上一篇继续来研究Spring的ORM模块。 ... -
Spring数据库访问之ORM(一)
2011-01-27 10:54 30719Spring数据库访问中另外一大模块就是ORM,ORM ... -
Spring数据库访问之异常处理
2011-01-19 10:29 27432使用JDBC API时,很 ... -
Spring数据库访问(HSQL)(四)
2011-01-16 21:49 14452本文接上一篇继续研究Spring的JDBC模板。 ... -
Spring数据库访问(HSQL)(三)
2011-01-13 10:07 13384本文接上一篇继续研究JDBC模板。 之前说的都 ... -
Spring数据库访问(HSQL)(二)
2011-01-11 11:27 10697上一篇我们介绍了 ... -
Spring数据库访问(HSQL)(一)
2011-01-09 23:34 13745本部分主要介绍Spring的JDBC模板,JDBC模板 ... -
Spring 3之MVC & Security简单整合开发(三)
2010-12-03 19:04 21987本文接上一篇继续深入研究Security框架。 ... -
Spring 3之MVC & Security简单整合开发(二)
2010-12-01 20:29 59967本文接上一篇继续 ... -
Spring 3之MVC & Security简单整合开发(一)
2010-11-30 22:00 42419Spring的MVC模块是一种简洁的Web应用框架,实 ... -
iBatis查询select详解
2010-08-07 12:19 40468<select>是iBatis已经映射的语 ... -
iBatis查询API
2010-07-31 13:04 17628先说点基础的内容 ...
相关推荐
复杂类型集合的属性 避免N+1 Select(1:M和M:N) 组合键值或多个复杂参数属性 支持Parameter Map和Result Map的数据类型 缓存Mapped Statement结果集 只读 VS 可读写 Serializable可读写缓存 缓存类型 动态Mapped ...
6.2.1 复杂集合 101 6.2.2 延迟加载 104 6.2.3 避免N+1查询问题 105 6.3 继承 107 6.4 其他用途 109 6.4.1 使用语句类型和DDL 109 6.4.2 处理超大型数据集 109 6.5 小结 115 第7章 事务 116 7.1 事务是什么 116 ...
SQL Maps (com.ibatis.sqlmap.*)......................................................................................................5 SQL Map的概念........................................................
它把所有的SQL脚本以模板的方式集中到若干个XML配置文件里,用反射的方式向把C#类实体对象属性与SQL模板的参数绑定,动态生成参数化的SQL语句发送给数据库执行,查询的结果集也用反射的方式构造为对象集合返回给程序...
查询数据,前面已经讲过简单的,主要看查询出列表的 查询出列表,也就是返回list, 在我们这个例子中也就是 List<User> , 这种方式返回数据,需要在User.xml 里面配置返回的类型 resultMap, 注意不是 resultType, 而...
它把所有的SQL脚本以模板的方式集中到若干个XML配置文件里,用反射的方式向把C#类实体对象属性与SQL模板的参数绑定,动态生成参数化的SQL语句发送给数据库执行,查询的结果集也用反射的方式构造为对象集合返回给程序...
它把所有的SQL脚本以模板的方式集中到若干个XML配置文件里,用反射的方式向把C#类实体对象属性与SQL模板的参数绑定,动态生成参数化的SQL语句发送给数据库执行,查询的结果集也用反射的方式构造为对象集合返回给程序...
B .iBATIS自带的queryForList(String statementName,int start,int size)分页接口有性能隐患,不允许使用。 C .定义明确的sql查询语句,通过传入参数start和size来实现分页逻辑。 D .可使用存储过程写分页逻辑...
它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从...
☆ Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。 ☆ Spring Web 模块:...
2.3.3装配集合 2.3.4装配空值 2.4自动装配 2.4.1四种自动装配类型 2.4.2混合使用自动和手动装配 2.4.3何时采用自动装配 2.5控制bean创建 2.5.1bean范围化 2.5.2利用工厂方法来创建bean 2.5.3初始化和销毁...
2.3.3 装配集合 2.3.4 装配空值 2.4 自动装配 2.4.1 四种自动装配类型 2.4.2 混合使用自动和手动装配 2.4.3 何时采用自动装配 2.5 控制Bean创建 2.5.1 Bean范围化 2.5.2 利用工厂方法来创建Bean 2.5.3 ...
2.3.3 装配集合 2.3.4 装配空值 2.4 自动装配 2.4.1 四种自动装配类型 2.4.2 混合使用自动和手动装配 2.4.3 何时采用自动装配 2.5 控制Bean创建 2.5.1 Bean范围化 2.5.2 利用工厂方法来创建Bean 2.5.3 ...