`
sarin
  • 浏览: 1748485 次
  • 性别: Icon_minigender_1
  • 来自: 大连
博客专栏
E3b14d1f-4cc5-37dd-b820-b6af951740bc
Spring数据库访问系列...
浏览量:172858
C2083dc5-6474-39e2-993e-263652d27795
Android学习笔记
浏览量:366623
5f40a095-b33c-3e8e-8891-606fcf3b8d27
iBatis开发详解
浏览量:188354
B272a31d-e7bd-3eff-8cc4-c0624ee75fee
Objective-C学习...
浏览量:98808
社区版块
存档分类
最新评论

iBatis查询复杂集合

阅读更多
    本文系iBatis开发详解系列文章之在iBatis查询复杂集合
    通常我们使用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查询解决方案适用于小型数据集合或者是所有数据都肯定会被用到的数据集合,使得整体性能得到提高。
  • 大小: 35 KB
  • 大小: 36.4 KB
  • 大小: 35.7 KB
12
0
分享到:
评论
2 楼 SunJK000 2012-10-09  
hellostory 写道
用mybatis3的人越来越多,如果有就更好

支持
1 楼 hellostory 2012-09-05  
用mybatis3的人越来越多,如果有就更好

相关推荐

    iBatis SQL Maps开发指南.pdf

    复杂类型集合的属性 避免N+1 Select(1:M和M:N) 组合键值或多个复杂参数属性 支持Parameter Map和Result Map的数据类型 缓存Mapped Statement结果集 只读 VS 可读写 Serializable可读写缓存 缓存类型 动态Mapped ...

    iBATIS实战

    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 ...

    iBATIS 帮助文档

    SQL Maps (com.ibatis.sqlmap.*)......................................................................................................5 SQL Map的概念........................................................

    AppFramework_V1.0

    它把所有的SQL脚本以模板的方式集中到若干个XML配置文件里,用反射的方式向把C#类实体对象属性与SQL模板的参数绑定,动态生成参数化的SQL语句发送给数据库执行,查询的结果集也用反射的方式构造为对象集合返回给程序...

    springmybatis

    查询数据,前面已经讲过简单的,主要看查询出列表的 查询出列表,也就是返回list, 在我们这个例子中也就是 List&lt;User&gt; , 这种方式返回数据,需要在User.xml 里面配置返回的类型 resultMap, 注意不是 resultType, 而...

    AppFramework_V1.0_New

    它把所有的SQL脚本以模板的方式集中到若干个XML配置文件里,用反射的方式向把C#类实体对象属性与SQL模板的参数绑定,动态生成参数化的SQL语句发送给数据库执行,查询的结果集也用反射的方式构造为对象集合返回给程序...

    AppFramework数据库访问组件_代码生成插件_V1.1.rar

    它把所有的SQL脚本以模板的方式集中到若干个XML配置文件里,用反射的方式向把C#类实体对象属性与SQL模板的参数绑定,动态生成参数化的SQL语句发送给数据库执行,查询的结果集也用反射的方式构造为对象集合返回给程序...

    阿里巴巴编码规范 基础技能认证 考题分析(考题+答案).docx

    B .iBATIS自带的queryForList(String statementName,int start,int size)分页接口有性能隐患,不允许使用。 C .定义明确的sql查询语句,通过传入参数start和size来实现分页逻辑。 D .可使用存储过程写分页逻辑...

    基于maven项目的SSM框架与layu前端框架的代码

    它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从...

    Spring面试题

    ☆ Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。 ☆ Spring Web 模块:...

    Spring in Action(第2版)中文版

    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初始化和销毁...

    Spring in Action(第二版 中文高清版).part2

    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 ...

    Spring in Action(第二版 中文高清版).part1

    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 ...

Global site tag (gtag.js) - Google Analytics