iBatis開(kāi)發(fā)詳解(9)---查詢(xún)復(fù)雜集合

2018-10-14 10:37 更新

     通常我們使用iBatis的select查詢(xún)都是映射的簡(jiǎn)單對(duì)象,即便在一個(gè)查詢(xún)中連接多個(gè)表也是如此,那么既然iBatis是SQL Mapper,也就是說(shuō)它可以映射復(fù)雜集合,我們來(lái)看看如何讓對(duì)象模型向數(shù)據(jù)模型(關(guān)系型數(shù)據(jù)模型)靠攏。 


    假設(shè)在在線購(gòu)物應(yīng)用中,我們有用戶(hù)表User,訂單表Order和訂單項(xiàng)表OrderItem,它們之間存在的關(guān)聯(lián)是顯而易見(jiàn)的。用戶(hù)可以下訂單,而訂單中可以包含多個(gè)項(xiàng)。 

    我們的數(shù)據(jù)庫(kù)設(shè)計(jì)如下: 

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`)  
)  
  訂單項(xiàng)表為: 
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`)  
)  
    三個(gè)表之前通過(guò)userId和orderId進(jìn)行關(guān)聯(lián),這里僅做示例性說(shuō)明,并沒(méi)有添加物理外鍵關(guān)聯(lián)。 
    下面我們?cè)O(shè)計(jì)POJO來(lái)描述這三個(gè)對(duì)象: 
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 + "]";  
    }  
}  
  我們使用各個(gè)屬性連描述orderItem表的字段,下面是order對(duì)象: 
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ù)組來(lái)存放每個(gè)訂單的訂單項(xiàng),分別給出setter和getter方法。下面是user對(duì)象: 
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類(lèi)似,我們也使用數(shù)組來(lái)存放用戶(hù)所下的訂單。 
    那么實(shí)體對(duì)象和數(shù)據(jù)表我們就都有了,下面來(lái)編寫(xiě)iBatis的sqlMap文件來(lái)描述它們之間的關(guān)系,我們自定義resultMap來(lái)說(shuō)明: 
<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則是最簡(jiǎn)單的了,就是刻畫(huà)各個(gè)屬性即可。
<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>  
  在刻畫(huà)OrderInfo時(shí),我們加入了一個(gè)select查詢(xún),就是查到該訂單下的所有訂單項(xiàng),并使用數(shù)組保存起來(lái),那么上面這段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>  
   和上面的訂單項(xiàng)是類(lèi)似的,我們?cè)诓樵?xún)用戶(hù)時(shí),也把用戶(hù)所下的訂單都給查出來(lái)。最后我們是要獲取內(nèi)系統(tǒng)的所有用戶(hù),那么使用下面的這個(gè)SQL: 
<select id="getUserInfoList" resultMap="ResultUserInfoMap">  
    select  
        userId,  
        userName  
    from  
        user  
</select>  
   綜上的XML表述了這么一個(gè)需求,就是查詢(xún)系統(tǒng)的內(nèi)的所有用戶(hù),以及它們所下的所有訂單,并還要查出每個(gè)訂單項(xiàng)。 
   那么示例程序就很簡(jiǎn)單了: 
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");  
    }}  
    運(yùn)行程序,我們可以得到如下輸出: 
iBatis

 查詢(xún)得到的內(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]]] 


    他們都是以對(duì)象的形式來(lái)描述的,因?yàn)槲覀兠鼪](méi)有查詢(xún)user的email,mobile等信息,所以它們是null。 


    程序編寫(xiě)完了,但是問(wèn)題隨之而來(lái)。先看這么一大堆的輸出,我們僅僅有1個(gè)用戶(hù)的兩個(gè)訂單,每個(gè)訂單僅包含2個(gè)項(xiàng)。如果我們的系統(tǒng)內(nèi)有10000個(gè)用戶(hù),每個(gè)用戶(hù)下了100個(gè)訂單,每個(gè)訂單有5項(xiàng),那么一次查詢(xún)結(jié)果就會(huì)生成500萬(wàn)個(gè)對(duì)象,顯然在數(shù)據(jù)不是很多時(shí),就已經(jīng)帶來(lái)了問(wèn)題。首先這是一個(gè)數(shù)據(jù)庫(kù)I/O的問(wèn)題,大量數(shù)據(jù)庫(kù)I/O和內(nèi)存對(duì)象,降低了性能,消耗了資源。其次是N+1查詢(xún)問(wèn)題。 


    N+1問(wèn)題也好理解,在本例中,我們要查詢(xún)一個(gè)用戶(hù)的N個(gè)訂單,還要查詢(xún)這N個(gè)訂單中每個(gè)訂單的N個(gè)訂單項(xiàng),就產(chǎn)生了N+1查詢(xún)問(wèn)題。 
    iBatis提供了針對(duì)每個(gè)問(wèn)題的解決方案,但是卻不能同時(shí)解決這兩個(gè)問(wèn)題。 


    針對(duì)數(shù)據(jù)庫(kù)I/O,我們很容易想到延遲加載的特性,就是對(duì)于所有數(shù)據(jù)并不是一次全部查出,在需要的時(shí)候繼續(xù)進(jìn)行查詢(xún),那么就會(huì)大幅度減少數(shù)據(jù)庫(kù)的I/O,開(kāi)啟iBatis的延遲加載特性非常簡(jiǎn)單,只需修改如下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,如果想開(kāi)啟cglib的增強(qiáng)版,需要在類(lèi)路徑下添加相關(guān)jar包,并設(shè)置enhancementEnabled為true即可。但是要清楚一點(diǎn),這些設(shè)置都是全局的設(shè)置,也就是說(shuō)項(xiàng)目中的其它iBatis查詢(xún)都將應(yīng)用延遲加載特性。下面我們來(lái)測(cè)試一下代碼: 
iBatis

   可以看到,在僅有的幾條數(shù)據(jù)時(shí),所消耗的時(shí)間也大幅減少,說(shuō)明延遲加載特性已經(jīng)啟用。 
   下面我們來(lái)看針對(duì)N+1查詢(xún)問(wèn)題的解決方案,那就是使用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屬性,其余和上面配置類(lèi)似,而這次我們的查詢(xún)語(yǔ)句就合并成一個(gè)連接查詢(xún),測(cè)試時(shí),需要修改POJO中的數(shù)組變量為單對(duì)象變量,那么執(zhí)行程序后,我們得到如下效果: 

    也可以看到查詢(xún)效率的顯著提升。 
    綜上所述,延遲加載適用于大型數(shù)據(jù)集合,但是并非其中的每條記錄都會(huì)被用到。此方法前期的性能大幅度提高,但是后期仍需加載所需數(shù)據(jù)。而N+1查詢(xún)解決方案適用于小型數(shù)據(jù)集合或者是所有數(shù)據(jù)都肯定會(huì)被用到的數(shù)據(jù)集合,使得整體性能得到提高。 

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)