「Mybatis系列」Mybatis高級應用

1. 關聯查詢

舉例:由於一個訂單信息只會是一我的下的訂單,因此從查詢訂單信息出發,關聯查詢用戶信息爲一對一查詢。若是從用戶信息出發,查詢用戶下的訂單信息則爲一對多查詢,由於一個用戶能夠下多個訂單。java

1.1 一對一查詢

需求

查詢全部訂單信息,關聯查詢下單用戶信息。mysql

SQL語句

主信息:訂單表sql

從信息:用戶表數據庫

SELECT 
  orders.*,
  user.username,
  user.address
FROM
  orders LEFT JOIN user 
  ON orders.user_id = user.id

方法一:resultType

返回resultType方式比較簡單,也比較經常使用,就不作介紹了。session

方法二:resultMap

使用resultMap進行結果映射,定義專門的resultMap用於映射一對一查詢結果。mybatis

建立擴展po類

建立OrdersExt類(該類用於結果集封裝),加入User屬性,user屬性中用於存儲關聯查詢的用戶信息,由於訂單關聯查詢用戶是一對一關係,因此這裏使用單個User對象存儲關聯查詢的用戶信息。app

public class OrdersExt extends Orders {

    private User user;// 用戶對象
    // get/set。。。。
}
Mapper映射文件

在UserMapper.xml中,添加如下代碼:性能

<!-- 查詢訂單關聯用戶信息使用resultmap -->
    <resultMap type="OrdersExt" id="ordersAndUserRstMap">
        <id column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="number" property="number"/>
        <result column="createtime" property="createtime"/>
        <result column="note" property="note"/>
        <!-- 一對一關聯映射 -->
        <!-- 
        property:Orders對象的user屬性
        javaType:user屬性對應 的類型
         -->
        <association property="user" javaType="com.kkb.mybatis.po.User">
            <!-- column:user表的主鍵對應的列  property:user對象中id屬性-->
            <id column="user_id" property="id"/>
            <result column="username" property="username"/>
            <result column="address" property="address"/>
        </association>
    </resultMap>
    <select id="findOrdersAndUserRstMap" resultMap="ordersAndUserRstMap">
        SELECT
            o.id,
            o.user_id,
            o.number,
            o.createtime,
            o.note,
            u.username,
            u.address
        FROM
            orders o
        JOIN `user` u ON u.id = o.user_id
    </select>
  • association:表示進行一對一關聯查詢映射
  • property:表示關聯查詢的結果存儲在com.kkb.mybatis.po.Orders的user屬性中
  • javaType:表示關聯查詢的映射結果類型
Mapper接口

在UserMapper接口中,添加如下接口方法:測試

public List<OrdersExt> findOrdersAndUserRstMap() throws Exception;
測試代碼

在UserMapperTest測試類中,添加測試代碼:spa

public void testfindOrdersAndUserRstMap()throws Exception{
        //獲取session
        SqlSession session = sqlSessionFactory.openSession();
        //獲限mapper接口實例
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //查詢訂單信息
        List<OrdersExt> list = userMapper.findOrdersAndUserRstMap();
        System.out.println(list);
        //關閉session
        session.close();
    }
小結

使用resultMap進行結果映射時,具體是使用association完成關聯查詢的映射,將關聯查詢信息映射到pojo對象中。

1.2 一對多查詢

需求

查詢全部用戶信息及用戶關聯的訂單信息。

SQL語句

主信息:用戶信息

從信息:訂單信息

SELECT
    u.*, 
    o.id oid,
    o.number,
    o.createtime,
    o.note
FROM
    `user` u
LEFT JOIN orders o ON u.id = o.user_id

分析

在一對多關聯查詢時,只能使用resultMap進行結果映射:

一、一對多關聯查詢時,sql查詢結果有多條,而映射對象是一個。

二、resultType完成結果映射的方式的一條記錄映射一個對象。

三、resultMap完成結果映射的方式是以[主信息]爲主對象,[從信息]映射爲集合或者對象,而後封裝到主對象中。

修改po類

在User類中加入List<Orders> orders屬性。

Mapper映射文件

在UserMapper.xml文件中,添加如下代碼:

<resultMap type="user" id="userAndOrderRstMap">
        <!-- 用戶信息映射 -->
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="birthday" column="birthday"/>
        <result property="sex" column="sex"/>
        <result property="address" column="address"/>
        <!-- 一對多關聯映射 -->
        <collection property="orders" ofType="orders">
            <id property="id" column="oid"/>    
            <result property="userId" column="id"/>
            <result property="number" column="number"/>
            <result property="createtime" column="createtime"/>
            <result property="note" column="note"/>
        </collection>
    </resultMap>
    <select id="findUserAndOrderRstMap" resultMap="userAndOrderRstMap">
        SELECT
        u.*,
    o.id oid,
        o.number,
        o.createtime,
        o.note
        FROM
        `user` u
        LEFT JOIN orders o ON u.id = o.user_id
    </select>

Collection標籤:定義了一對多關聯的結果映射。

  • property="orders":關聯查詢的結果集存儲在User對象的上哪一個屬性。
  • ofType="orders":指定關聯查詢的結果集中的對象類型即List中的對象類型。此處可使用別名,也可使用全限定名。

Mapper接口

// resultMap入門
public List<User> findUserAndOrdersRstMap() throws Exception;

測試代碼

@Test
    public void testFindUserAndOrdersRstMap() {
        SqlSession session = sqlSessionFactory.openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        List<User> result = userMapper.findUserAndOrdersRstMap();
        for (User user : result) {
            System.out.println(user);
        }
        session.close();
    }

2. 延遲加載

2.1 什麼是延遲加載

  • MyBatis中的延遲加載,也稱爲懶加載,是指在進行關聯查詢時,按照設置延遲規則推遲對關聯對象的select查詢。延遲加載能夠有效的減小數據庫壓力。
  • Mybatis的延遲加載,須要經過resultMap標籤中的association和collection子標籤才能演示成功。
  • Mybatis的延遲加載,也被稱爲是嵌套查詢,對應的還有嵌套結果的概念,能夠參考一對多關聯的案例。
  • 注意:MyBatis的延遲加載只是對關聯對象的查詢有延遲設置,對於主加載對象都是直接執行查詢語句的sql

2.2 延遲加載的分類

MyBatis根據對關聯對象查詢的select語句的執行時機,分爲三種類型:直接加載、侵入式加載與深度延遲加載

  • 直接加載: 執行完對主加載對象的select語句,立刻執行對關聯對象的select查詢。
  • 侵入式延遲:執行對主加載對象的查詢時,不會執行對關聯對象的查詢。但當要訪問主加載對象的某個屬性(該屬性不是關聯對象的屬性)時,就會立刻執行關聯對象的select查詢。
  • 深度延遲:執行對主加載對象的查詢時,不會執行對關聯對象的查詢。訪問主加載對象的詳情時也不會執行關聯對象的select查詢。只有當真正訪問關聯對象的詳情時,纔會執行對關聯對象的select查詢。
延遲加載策略須要在Mybatis的全局配置文件中,經過<settings>標籤進行設置。

2.3 案例準備

查詢訂單信息及它的下單用戶信息。

2.4 直接加載

經過對全局參數:lazyLoadingEnabled進行設置,默認就是false。

<settings>
    <!-- 延遲加載總開關 -->
    <setting name="lazyLoadingEnabled" value="false"/>
</settings>

2.5 侵入式延遲加載

<settings>
    <!-- 延遲加載總開關 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 侵入式延遲加載開關 -->
    <setting name="aggressiveLazyLoading" value="true"/>
</settings>

2.6 深度延遲加載

<settings>
    <!-- 延遲加載總開關 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 侵入式延遲加載開關 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

2.7 N+1問題

  • 深度延遲加載的使用會提高性能。
  • 若是延遲加載的表數據太多,此時會產生N+1問題,主信息加載一次算1次,而從信息是會根據主信息傳遞過來的條件,去查詢從表屢次。

3. 動態SQL

動態SQL的思想:就是使用不一樣的動態SQL標籤去完成字符串的拼接處理、循環判斷。

解決的問題是:

  1. 在映射文件中,會編寫不少有重疊部分的SQL語句,好比SELECT語句和WHERE語句等這些重疊語句,該如何處理
  2. SQL語句中的where條件有多個,可是頁面只傳遞過來一個條件參數,此時會發生問題。

3.1 if標籤

綜合查詢的案例中,查詢條件是由頁面傳入,頁面中的查詢條件可能輸入用戶名稱,也可能不輸入用戶名稱。

<select id="findUserList" parameterType="queryVo" resultType="user">
        SELECT * FROM user where 1=1
        <if test="user != null">
            <if test="user.username != null and user.username != ''">
                AND username like '%${user.username}%'
            </if>
        </if>
    </select>

注意:要作『不等於空』字符串校驗。

3.2 where標籤

上邊的sql中的1=1,雖然能夠保證sql語句的完整性:可是存在性能問題。Mybatis提供where標籤解決該問題。

代碼修改以下:

<select id="findUserList" parameterType="queryVo" resultType="user">
        SELECT * FROM user
        <!-- where標籤會處理它後面的第一個and -->
        <where>
            <if test="user != null">
                <if test="user.username != null and user.username != ''">
                    AND username like '%${user.username}%'
                </if>
            </if>
        </where>
    </select>

3.3 sql片斷

在映射文件中可以使用sql標籤將重複的sql提取出來,而後使用include標籤引用便可,最終達到sql重用的目的,具體實現以下:

  • 原映射文件中的代碼:

    <select id="findUserList" parameterType="queryVo" resultType="user">
            SELECT * FROM user
            <!-- where標籤會處理它後面的第一個and -->
            <where>
                <if test="user != null">
                    <if test="user.username != null and user.username != ''">
                        AND username like '%${user.username}%'
                    </if>
                </if>            
            </where>
        </select>
  • 將where條件抽取出來:
<sql id="query_user_where">
        <if test="user != null">
            <if test="user.username != null and user.username != ''">
                AND username like '%${user.username}%'
            </if>
        </if>
</sql>
  • 使用include引用:
<!-- 使用包裝類型查詢用戶 使用ognl從對象中取屬性值,若是是包裝對象可使用.操做符來取內容部的屬性 -->
    <select id="findUserList" parameterType="queryVo" resultType="user">
        SELECT * FROM user
        <!-- where標籤會處理它後面的第一個and -->
        <where>
            <include refid="query_user_where"></include>
        </where>

    </select>

注意:

一、若是引用其它mapper.xml的sql片斷,則在引用時須要加上namespace,以下:

<include refid="namespace.sql片斷」/>

3.4 foreach

需求

綜合查詢時,傳入多個id查詢用戶信息,用下邊兩個sql實現:

SELECT * FROM USER WHERE username LIKE '%老郭%' AND (id =1 OR id =10 OR id=16)

SELECT * FROM USER WHERE username LIKE '%老郭%'  AND  id  IN (1,10,16)

POJO

在pojo中定義list屬性ids存儲多個用戶id,並添加getter/setter方法

Mapper映射文件

<sql id="query_user_where">
        <if test="user != null">
            <if test="user.username != null and user.username != ''">
                AND username like '%${user.username}%'
            </if>
        </if>
        <if test="ids != null and ids.size() > 0">
            <!-- collection:指定輸入的集合參數的參數名稱 -->
            <!-- item:聲明集合參數中的元素變量名 -->
            <!-- open:集合遍歷時,須要拼接到遍歷sql語句的前面 -->
            <!-- close:集合遍歷時,須要拼接到遍歷sql語句的後面 -->
            <!-- separator:集合遍歷時,須要拼接到遍歷sql語句之間的分隔符號 -->
            <foreach collection="ids" item="id" open=" AND id IN ( "
                close=" ) " separator=",">
                #{id}
            </foreach>
        </if>
    </sql>

測試代碼

在UserMapperTest測試代碼中,修改testFindUserList方法,以下:

@Test
    public void testFindUserList() throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 得到mapper的代理對象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 建立QueryVo對象
        QueryVo queryVo = new QueryVo();
        // 建立user對象
        User user = new User();
        user.setUsername("老郭");

        queryVo.setUser(user);

        List<Integer> ids = new ArrayList<Integer>();
        ids.add(1);// 查詢id爲1的用戶
        ids.add(10); // 查詢id爲10的用戶
        queryVo.setIds(ids);

        // 根據queryvo查詢用戶
        List<User> list = userMapper.findUserList(queryVo);
        System.out.println(list);
        sqlSession.close();
    }

注意事項

若是parameterType不是POJO類型,而是List或者Array的話,那麼foreach語句中,collection屬性值須要固定寫死爲list或者array。

相關文章
相關標籤/搜索