通常我們使用iBatis的select查詢都是映射的簡單對象,即便在一個查詢中連接多個表也是如此,那么既然iBatis是SQL Mapper,也就是說它可以映射復(fù)雜集合,我們來看看如何讓對象模型向數(shù)據(jù)模型(關(guān)系型數(shù)據(jù)模型)靠攏。
我們的數(shù)據(jù)庫設(shè)計如下:
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進行關(guān)聯(lián),這里僅做示例性說明,并沒有添加物理外鍵關(guān)聯(lián)。 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) + "]";
}
}
使用數(shù)組來存放每個訂單的訂單項,分別給出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 + "]";
}
}
<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查詢,就是查到該訂單下的所有訂單項,并使用數(shù)組保存起來,那么上面這段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>
和上面的訂單項是類似的,我們在查詢用戶時,也把用戶所下的訂單都給查出來。最后我們是要獲取內(nèi)系統(tǒng)的所有用戶,那么使用下面的這個SQL: <select id="getUserInfoList" resultMap="ResultUserInfoMap">
select
userId,
userName
from
user
</select>
綜上的XML表述了這么一個需求,就是查詢系統(tǒng)的內(nèi)的所有用戶,以及它們所下的所有訂單,并還要查出每個訂單項。 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");
}}
運行程序,我們可以得到如下輸出: 查詢得到的內(nèi)容為:
[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個項。如果我們的系統(tǒng)內(nèi)有10000個用戶,每個用戶下了100個訂單,每個訂單有5項,那么一次查詢結(jié)果就會生成500萬個對象,顯然在數(shù)據(jù)不是很多時,就已經(jīng)帶來了問題。首先這是一個數(shù)據(jù)庫I/O的問題,大量數(shù)據(jù)庫I/O和內(nèi)存對象,降低了性能,消耗了資源。其次是N+1查詢問題。
N+1問題也好理解,在本例中,我們要查詢一個用戶的N個訂單,還要查詢這N個訂單中每個訂單的N個訂單項,就產(chǎn)生了N+1查詢問題。
iBatis提供了針對每個問題的解決方案,但是卻不能同時解決這兩個問題。
針對數(shù)據(jù)庫I/O,我們很容易想到延遲加載的特性,就是對于所有數(shù)據(jù)并不是一次全部查出,在需要的時候繼續(xù)進行查詢,那么就會大幅度減少數(shù)據(jù)庫的I/O,開啟iBatis的延遲加載特性非常簡單,只需修改如下XML設(shè)置即可:
<?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中設(shè)置了lazyLoadingEnabled為true,如果想開啟cglib的增強版,需要在類路徑下添加相關(guān)jar包,并設(shè)置enhancementEnabled為true即可。但是要清楚一點,這些設(shè)置都是全局的設(shè)置,也就是說項目中的其它iBatis查詢都將應(yīng)用延遲加載特性。下面我們來測試一下代碼: 可以看到,在僅有的幾條數(shù)據(jù)時,所消耗的時間也大幅減少,說明延遲加載特性已經(jīng)啟用。
下面我們來看針對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中的數(shù)組變量為單對象變量,那么執(zhí)行程序后,我們得到如下效果: 也可以看到查詢效率的顯著提升。
綜上所述,延遲加載適用于大型數(shù)據(jù)集合,但是并非其中的每條記錄都會被用到。此方法前期的性能大幅度提高,但是后期仍需加載所需數(shù)據(jù)。而N+1查詢解決方案適用于小型數(shù)據(jù)集合或者是所有數(shù)據(jù)都肯定會被用到的數(shù)據(jù)集合,使得整體性能得到提高。
更多建議: