本系列文章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標籤去完成字串的拼接處理、迴圈判斷。
解決的問題是:
在對映檔案中,會編寫很多有重疊部分的SQL語句,比如SELECT語句和WHERE語句等這些重疊語句,該如何處理
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。
公眾號內文章都是博主原創,並且會一直更新。如果你想見證或和博主一起成長,歡迎關注!