最近在讀劉增輝老師所著的《MyBatis從入門到精通》一書,很有收穫,於是將自己學習的過程以部落格形式輸出,如有錯誤,歡迎指正,如幫助到你,不勝榮幸!
本篇部落格主要講解使用association標籤實現巢狀查詢的方法。
1. 明確需求
仍然延用上篇部落格中的需求:根據使用者id查詢使用者資訊的同時獲取該使用者的角色資訊(假設一個員工只能擁有一個角色)。
在上篇部落格中,我們分別使用了3種方式來實現這個需求,但這3個需求都有一個共同點,就是我們使用了多表查詢,即查詢一次資料庫就獲取到我們想要的所有資料。
有的同學就說了,我不喜歡多表查詢的方式,資料量大的時候會影響效能,這麼簡單的需求,我可以拆分成兩步啊,第一步,先根據使用者id查詢出使用者的資訊和使用者的角色id(仍然要關聯表,只是由3張表關聯減為了2張表關聯),第二步,根據第一步查詢出的角色id再去查詢角色資訊。
這種方式當然可以,而且使用業務程式碼就能實現這個邏輯,不過本篇部落格我們不講這種方式,而是通過association標籤來實現。
2. 實現方式
因為我們需要根據角色id查詢角色的資訊,所以我們需要先在SysRoleMapper.xml中新增如下查詢:
<select id="selectRoleById" resultMap="roleMap">
SELECT * FROM sys_role WHERE id = #{id}
</select>
這裡的roleMap就是我們在上篇部落格中定義的,程式碼如下:
<resultMap id="roleMap" type="com.zwwhnly.mybatisaction.model.SysRole">
<id property="id" column="id"/>
<result property="roleName" column="role_name"/>
<result property="enabled" column="enabled"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>
然後,在介面SysUserMapper中新增如下方法:
/**
* 根據使用者id獲取使用者資訊和使用者的角色資訊,巢狀查詢方式
*
* @param id
* @return
*/
SysUserExtend selectUserAndRoleByIdSelect(Long id);
接著,在對應的SysUserMapper.xml中新增如下程式碼:
<resultMap id="userRoleMapSelect" type="com.zwwhnly.mybatisaction.model.SysUserExtend" extends="sysUserMap">
<association property="sysRole"
select="com.zwwhnly.mybatisaction.mapper.SysRoleMapper.selectRoleById"
column="{id=role_id}"/>
</resultMap>
<select id="selectUserAndRoleByIdSelect" resultMap="userRoleMapSelect">
SELECT u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
u.create_time,
ur.role_id
FROM sys_user u
INNER JOIN sys_user_role ur ON u.id = ur.user_id
WHERE u.id = #{id}
</select>
可以發現,我們給association標籤新增了select="com.zwwhnly.mybatisaction.mapper.SysRoleMapper.selectRoleById"
,引用的就是我們在SysRoleMapper.xml中定義的查詢。
還新增了column="{id=role_id}"
,這裡的id就是com.zwwhnly.mybatisaction.mapper.SysRoleMapper.selectRoleById需要的引數名稱,role_id是引數值,名稱要和上面的查詢中的最後一列保持一致。
注意事項:如果是多個引數的話,可以使用column="{id=role_id,name=role_name}"
這樣的格式。
3. 單元測試
在SysUserMapperTest類中新增測試方法如下:
@Test
public void testSelectUserAndRoleByIdSelect() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);
SysUserExtend sysUserExtend = sysUserMapper.selectUserAndRoleByIdSelect(1001L);
Assert.assertNotNull(sysUserExtend);
Assert.assertNotNull(sysUserExtend.getSysRole());
} finally {
sqlSession.close();
}
}
執行測試程式碼,測試通過,輸出日誌如下:
DEBUG [main] - ==> Preparing: SELECT u.id, u.user_name, u.user_password, u.user_email, u.create_time, ur.role_id FROM sys_user u INNER JOIN sys_user_role ur ON u.id = ur.user_id WHERE u.id = ?
DEBUG [main] - ==> Parameters: 1001(Long)
TRACE [main] - <== Columns: id, user_name, user_password, user_email, create_time, role_id
TRACE [main] - <== Row: 1001, test, 123456, test@mybatis.tk, 2019-06-27 18:21:07.0, 2
DEBUG [main] - ====> Preparing: SELECT * FROM sys_role WHERE id = ?
DEBUG [main] - ====> Parameters: 2(Long)
TRACE [main] - <==== Columns: id, role_name, enabled, create_by, create_time
TRACE [main] - <==== Row: 2, 普通使用者, 1, 1, 2019-06-27 18:21:12.0
DEBUG [main] - <==== Total: 1
DEBUG [main] - <== Total: 1
從日誌可以看出,分別執行了2次查詢,查詢了兩次資料庫。
4. 延遲載入
有的同學可能會說,返回的角色資訊我不一定用啊,每次都查詢一次資料庫,好浪費效能啊,能不能在我使用到角色資訊即獲取sysRole屬性時再去查詢資料呢?答案當然是能,那麼如何實現呢?
實現延遲載入需要使用association標籤的fetchType屬性,該屬性有lazy和eager兩個值,分別代表延遲載入和積極載入。
所以上面的配置就要修改成:
<association property="sysRole"
fetchType="lazy"
select="com.zwwhnly.mybatisaction.mapper.SysRoleMapper.selectRoleById"
column="{id=role_id}"/>
為了能看到效果,我們在測試方法中新增一行輸出語句:
System.out.println("呼叫sysUserExtend.getSysRole()");
Assert.assertNotNull(sysUserExtend.getSysRole());
再次執行測試方法,發現輸出日誌和預期的不一樣,在獲取sysRole屬性前還是查詢了2次資料庫,這是為什麼呢?
這是因為MyBatis的全域性配置中,有一個aggressiveLazyLoading引數,如果這個引數的值為ture,會使帶有延遲載入屬性的物件完整載入,如果為false,則會按需載入,這個引數預設值為ture(3.4.5版本開始預設值改為false),而截止目前,我們使用的版本為3.3.1。
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.1</version>
</dependency>
所以我們要在mybatis-config.xml中新增如下配置:
<settings>
<!--其他配置-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
再次執行測試方法,發現輸出日誌和預期的一樣了:
DEBUG [main] - ==> Preparing: SELECT u.id, u.user_name, u.user_password, u.user_email, u.create_time, ur.role_id FROM sys_user u INNER JOIN sys_user_role ur ON u.id = ur.user_id WHERE u.id = ?
DEBUG [main] - ==> Parameters: 1001(Long)
TRACE [main] - <== Columns: id, user_name, user_password, user_email, create_time, role_id
TRACE [main] - <== Row: 1001, test, 123456, test@mybatis.tk, 2019-06-27 18:21:07.0, 2
DEBUG [main] - <== Total: 1
呼叫sysUserExtend.getSysRole()
DEBUG [main] - ==> Preparing: SELECT * FROM sys_role WHERE id = ?
DEBUG [main] - ==> Parameters: 2(Long)
TRACE [main] - <== Columns: id, role_name, enabled, create_by, create_time
TRACE [main] - <== Row: 2, 普通使用者, 1, 1, 2019-06-27 18:21:12.0
DEBUG [main] - <== Total: 1
有的同學可能又會說,你現在把全域性的aggressiveLazyLoading改為了false,我能不能在觸發某個方法時將所有的資料都載入進來呢?答案當然是能(不然怎麼往下寫,哈哈),那麼如何實現呢?
MyBatis提供了引數lazyLoadTriggerMethods,這個引數的含義是,當呼叫配置中的方法時,載入全部的延遲載入資料,預設值為“equals,clone,hashCode,toString”。
簡單修改下測試方法的程式碼:
System.out.println("呼叫sysUserExtend.equals(null)");
sysUserExtend.equals(null);
System.out.println("呼叫sysUserExtend.getSysRole()");
Assert.assertNotNull(sysUserExtend.getSysRole());
再次執行測試方法,輸出的部分日誌如下:
呼叫sysUserExtend.equals(null)
DEBUG [main] - ==> Preparing: SELECT * FROM sys_role WHERE id = ?
DEBUG [main] - ==> Parameters: 2(Long)
TRACE [main] - <== Columns: id, role_name, enabled, create_by, create_time
TRACE [main] - <== Row: 2, 普通使用者, 1, 1, 2019-06-27 18:21:12.0
DEBUG [main] - <== Total: 1
呼叫sysUserExtend.getSysRole()
從日誌可以看出,呼叫equals方法後,就觸發了延遲載入屬性的查詢。
5. 總結
使用association標籤實現巢狀查詢,用到的屬性總結如下:
1)select:另一個對映查詢的id,MyBatis會額外執行這個查詢獲取巢狀物件的結果。
2)column:將主查詢中列的結果作為巢狀查詢的引數,配置方式如column="{prop1=col1,prop2=col2}",prop1和prop2將作為巢狀查詢的引數。
3)fetchType:資料載入方式,可選值為lazy和eager,分別為延遲載入和積極載入。
4)如果要使用延遲載入,除了將fetchType設定為lazy,還需要注意全域性配置aggressiveLazyLoading的值應該為false。這個引數在3.4.5版本之前預設值為ture,從3.4.5版本開始預設值改為false。
5)MyBatis提供的lazyLoadTriggerMethods引數,支援在觸發某方法時直接觸發延遲載入屬性的查詢,如equals()方法。
6. 原始碼及參考
原始碼地址:https://github.com/zwwhnly/mybatis-action.git,歡迎下載。
劉增輝《MyBatis從入門到精通》