「Mybatis系列」Mybatis高階應用

雙哥發表於2020-02-21

本系列文章Github 後端進階指南 已收錄,此專案正在完善中,歡迎star。

1. 關聯查詢

舉例:因為一個訂單資訊只會是一個人下的訂單,所以從查詢訂單資訊出發,關聯查詢使用者資訊為一對一查詢。如果從使用者資訊出發,查詢使用者下的訂單資訊則為一對多查詢,因為一個使用者可以下多個訂單。

1.1 一對一查詢

需求

查詢所有訂單資訊,關聯查詢下單使用者資訊。

SQL語句

主資訊:訂單表

從資訊:使用者表

SELECT 
  orders.*,
  user.username,
  user.address
FROM
  orders LEFT JOIN user 
  ON orders.user_id = user.id
複製程式碼

方法一:resultType

返回resultType方式比較簡單,也比較常用,就不做介紹了。

方法二:resultMap

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

建立擴充套件po類

建立OrdersExt類(該類用於結果集封裝),加入User屬性,user屬性中用於儲存關聯查詢的使用者資訊,因為訂單關聯查詢使用者是一對一關係,所以這裡使用單個User物件儲存關聯查詢的使用者資訊。

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測試類中,新增測試程式碼:

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進行結果對映:

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

2、resultType完成結果對映的方式的一條記錄對映一個物件。

3、resultMap完成結果對映的方式是以[主資訊]為主物件,[從資訊]對映為集合或者物件,然後封裝到主物件中。

修改po類

在User類中加入List 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的全域性配置檔案中,通過標籤進行設定。

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>
複製程式碼

注意:

1、如果引用其它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。

本系列文章Github 後端進階指南 已收錄,此專案正在完善中,歡迎star。

公眾號內文章都是博主原創,並且會一直更新。如果你想見證或和博主一起成長,歡迎關注!

相關文章