MyBatis從入門到精通(十二):使用collection标簽實作嵌套查詢
2019-07-17 15:12 by 申城異鄉人, ... 閱讀, ... 評論, 收藏, 編輯
最近在讀劉增輝老師所著的《MyBatis從入門到精通》一書,很有收獲,于是将自己學習的過程以部落格形式輸出,如有錯誤,歡迎指正,如幫助到你,不勝榮幸!
本篇部落客要講解使用collection标簽實作嵌套查詢的方法。
1. 需求更新
在上篇部落格中,我們實作了需求:根據使用者id查詢使用者資訊的同時擷取使用者擁有的角色。
因為角色可以擁有多個權限,是以本篇部落格我們更新需求為:根據使用者id查詢使用者資訊的同時擷取使用者擁有的角色以及角色包含的權限。
2. 實作方式
因為我們需要使用到權限表的映射,是以我們需要先在SysPrivilegeMapper.xml中添加如下映射:
<resultMap id="sysPrivilegeMap" type="com.zwwhnly.mybatisaction.model.SysPrivilege">
<id property="id" column="id"/>
<result property="privilegeName" column="privilege_name"/>
<result property="privilegeUrl" column="privilege_url"/>
</resultMap>
一般情況下不建議修改資料庫表對應的實體類,是以這裡我們建立類SysRoleExtend,讓它繼承SysRole類,并添加如下字段:
package com.zwwhnly.mybatisaction.model;
import java.util.List;
public class SysRoleExtend extends SysRole {
/**
* 角色包含的權限清單
*/
private List<SysPrivilege> sysPrivilegeList;
public List<SysPrivilege> getSysPrivilegeList() {
return sysPrivilegeList;
}
public void setSysPrivilegeList(List<SysPrivilege> sysPrivilegeList) {
this.sysPrivilegeList = sysPrivilegeList;
}
}
然後在SysRoleMapper.xml中建立如下映射:
<resultMap id="rolePrivilegeListMap" extends="roleMap"
type="com.zwwhnly.mybatisaction.model.SysRoleExtend">
<collection property="sysPrivilegeList" columnPrefix="privilege_"
resultMap="com.zwwhnly.mybatisaction.mapper.SysPrivilegeMapper.sysPrivilegeMap"/>
</resultMap>
這裡的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>
com.zwwhnly.mybatisaction.mapper.SysPrivilegeMapper.sysPrivilegeMap
就是我們剛剛在SysPrivilegeMapper.xml中建立的映射sysPrivilegeMap。
然後,需要将上篇部落格中的userRoleListMap修改為:
<resultMap id="userRoleListMap" type="com.zwwhnly.mybatisaction.model.SysUserExtend" extends="sysUserMap">
<collection property="sysRoleList" columnPrefix="role_"
resultMap="com.zwwhnly.mybatisaction.mapper.SysRoleMapper.rolePrivilegeListMap">
</collection>
</resultMap>
并且要修改上篇部落格中id為selectAllUserAndRoles的select标簽代碼,因為要關聯角色權限關系表和權限表:
<select id="selectAllUserAndRoles" resultMap="userRoleListMap">
SELECT u.id,
u.user_name,
u.user_password,
u.user_email,
u.create_time,
r.id role_id,
r.role_name role_role_name,
r.enabled role_enabled,
r.create_by role_create_by,
r.create_time role_create_time,
p.id role_privilege_id,
p.privilege_name role_privilege_privilege_name,
p.privilege_url role_privilege_privilege_url
FROM sys_user u
INNER JOIN sys_user_role ur ON u.id = ur.user_id
INNER JOIN sys_role r ON ur.role_id = r.id
INNER JOIN sys_role_privilege rp ON rp.role_id = r.id
INNER JOIN sys_privilege p ON p.id = rp.privilege_id
</select>
注意事項:
這裡sys_privilege表的列名的别名字首為
role_privilege_
,這是因為userRoleListMap中collection的columnPrefix屬性為
role_
,并且指定的
com.zwwhnly.mybatisaction.mapper.SysRoleMapper.rolePrivilegeListMap
中collection的columnPrefix屬性為
privilege_
,是以這裡的字首需要疊加,就變成了
role_privilege_
。
3. 單元測試
修改上篇部落格中建的測試方法testSelectAllUserAndRoles()代碼為:
@Test
public void testSelectAllUserAndRoles() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);
List<SysUserExtend> sysUserList = sysUserMapper.selectAllUserAndRoles();
System.out.println("使用者數:" + sysUserList.size());
for (SysUserExtend sysUser : sysUserList) {
System.out.println("使用者名:" + sysUser.getUserName());
for (SysRoleExtend sysRoleExtend : sysUser.getSysRoleList()) {
System.out.println("角色名:" + sysRoleExtend.getRoleName());
for (SysPrivilege sysPrivilege : sysRoleExtend.getSysPrivilegeList()) {
System.out.println("權限名:" + sysPrivilege.getPrivilegeName());
}
}
}
} finally {
sqlSession.close();
}
}
運作測試代碼,測試通過,輸出日志如下:
DEBUG [main] - ==> Preparing: SELECT u.id, u.user_name, u.user_password, u.user_email, u.create_time, r.id role_id, r.role_name role_role_name, r.enabled role_enabled, r.create_by role_create_by, r.create_time role_create_time, p.id role_privilege_id, p.privilege_name role_privilege_privilege_name, p.privilege_url role_privilege_privilege_url FROM sys_user u INNER JOIN sys_user_role ur ON u.id = ur.user_id INNER JOIN sys_role r ON ur.role_id = r.id INNER JOIN sys_role_privilege rp ON rp.role_id = r.id INNER JOIN sys_privilege p ON p.id = rp.privilege_id
DEBUG [main] - ==> Parameters:
TRACE [main] - <== Columns: id, user_name, user_password, user_email, create_time, role_id, role_role_name, role_enabled, role_create_by, role_create_time, role_privilege_id, role_privilege_privilege_name, role_privilege_privilege_url
TRACE [main] - <== Row: 1, admin, 123456, [email protected], 2019-06-27 18:21:07.0, 1, 管理者, 1, 1, 2019-06-27 18:21:12.0, 1, 使用者管理, /users
TRACE [main] - <== Row: 1, admin, 123456, [email protected], 2019-06-27 18:21:07.0, 1, 管理者, 1, 1, 2019-06-27 18:21:12.0, 2, 角色管理, /roles
TRACE [main] - <== Row: 1, admin, 123456, [email protected], 2019-06-27 18:21:07.0, 1, 管理者, 1, 1, 2019-06-27 18:21:12.0, 3, 系統日志, /logs
TRACE [main] - <== Row: 1, admin, 123456, [email protected], 2019-06-27 18:21:07.0, 2, 普通使用者, 1, 1, 2019-06-27 18:21:12.0, 4, 人員維護, /persons
TRACE [main] - <== Row: 1, admin, 123456, [email protected], 2019-06-27 18:21:07.0, 2, 普通使用者, 1, 1, 2019-06-27 18:21:12.0, 5, 機關維護, /companies
TRACE [main] - <== Row: 1001, test, 123456, [email protected], 2019-06-27 18:21:07.0, 2, 普通使用者, 1, 1, 2019-06-27 18:21:12.0, 4, 人員維護, /persons
TRACE [main] - <== Row: 1001, test, 123456, [email protected], 2019-06-27 18:21:07.0, 2, 普通使用者, 1, 1, 2019-06-27 18:21:12.0, 5, 機關維護, /companies
DEBUG [main] - <== Total: 7
使用者數:2
使用者名:admin
角色名:管理者
權限名:使用者管理
權限名:角色管理
權限名:系統日志
角色名:普通使用者
權限名:人員維護
權限名:機關維護
使用者名:test
角色名:普通使用者
權限名:人員維護
權限名:機關維護
從日志可以看出,不僅查詢出了使用者擁有的角色資訊,也查詢出了角色包含的權限資訊。
4. 延遲加載
有的同學可能會說,傳回的角色資訊和權限資訊我不一定用啊,每次關聯這麼多表查詢一次資料庫,好影響性能啊,能不能在我使用到角色資訊即擷取sysRoleList屬性時再去資料庫查詢呢?答案當然是能,那麼如何實作呢?
實作延遲加載需要使用collection标簽的fetchType屬性,該屬性有lazy和eager兩個值,分别代表延遲加載和積極加載。
由于需要根據角色Id擷取該角色對應的所有權限資訊,是以我們要先在SysPrivilegeMapper.xml中定義如下查詢:
<select id="selectPrivilegeByRoleId" resultMap="sysPrivilegeMap">
SELECT p.*
FROM sys_privilege p
INNER JOIN sys_role_privilege rp ON rp.privilege_id = p.id
WHERE rp.role_id = #{roleId}
</select>
然後在SysRoleMapper.xml中添加如下查詢:
<resultMap id="rolePrivilegeListMapSelect" extends="roleMap"
type="com.zwwhnly.mybatisaction.model.SysRoleExtend">
<collection property="sysPrivilegeList" fetchType="lazy"
column="{roleId=id}"
select="com.zwwhnly.mybatisaction.mapper.SysPrivilegeMapper.selectPrivilegeByRoleId"/>
</resultMap>
<select id="selectRoleByUserId" resultMap="rolePrivilegeListMapSelect">
SELECT
r.id,
r.role_name,
r.enabled,
r.create_by,
r.create_time
FROM sys_role r
INNER JOIN sys_user_role ur ON ur.role_id = r.id
WHERE ur.user_id = #{userId}
</select>
上面的
column="{roleId=id}"
中,roleId指的是select指定的方法selectPrivilegeByRoleId的參數,id指的是查詢selectRoleByUserId中查詢出的角色id。
然後在SysUserMapper.xml中添加如下查詢:
<resultMap id="userRoleListMapSelect" extends="sysUserMap"
type="com.zwwhnly.mybatisaction.model.SysUserExtend">
<collection property="sysRoleList" fetchType="lazy"
select="com.zwwhnly.mybatisaction.mapper.SysRoleMapper.selectRoleByUserId"
column="{userId=id}"/>
</resultMap>
<select id="selectAllUserAndRolesSelect" resultMap="userRoleListMapSelect">
SELECT
u.id,
u.user_name,
u.user_password,
u.user_email,
u.create_time
FROM sys_user u
WHERE u.id = #{id}
</select>
上面的
column="{userId=id}"
中,userId指的是select指定的方法selectRoleByUserId的參數,id指的是查詢selectAllUserAndRolesSelect中查詢出的使用者id。
然後,在SysUserMapper接口中,添加如下方法:
/**
* 通過嵌套查詢擷取指定使用者的資訊以及使用者的角色和權限資訊
*
* @param id
* @return
*/
SysUserExtend selectAllUserAndRolesSelect(Long id);
最後,在SysUserMapperTest類中添加如下測試方法:
@Test
public void testSelectAllUserAndRolesSelect() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);
SysUserExtend sysUserExtend = sysUserMapper.selectAllUserAndRolesSelect(1L);
System.out.println("使用者名:" + sysUserExtend.getUserName());
for (SysRoleExtend sysRoleExtend : sysUserExtend.getSysRoleList()) {
System.out.println("角色名:" + sysRoleExtend.getRoleName());
for (SysPrivilege sysPrivilege : sysRoleExtend.getSysPrivilegeList()) {
System.out.println("權限名:" + sysPrivilege.getPrivilegeName());
}
}
} finally {
sqlSession.close();
}
}
運作測試方法,輸出日志如下:
DEBUG [main] - ==> Preparing: SELECT u.id, u.user_name, u.user_password, u.user_email, u.create_time FROM sys_user u WHERE u.id = ?
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <== Columns: id, user_name, user_password, user_email, create_time
TRACE [main] - <== Row: 1, admin, 123456, [email protected], 2019-06-27 18:21:07.0
DEBUG [main] - <== Total: 1
使用者名:admin
DEBUG [main] - ==> Preparing: SELECT r.id, r.role_name, r.enabled, r.create_by, r.create_time FROM sys_role r INNER JOIN sys_user_role ur ON ur.role_id = r.id WHERE ur.user_id = ?
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <== Columns: id, role_name, enabled, create_by, create_time
TRACE [main] - <== Row: 1, 管理者, 1, 1, 2019-06-27 18:21:12.0
TRACE [main] - <== Row: 2, 普通使用者, 1, 1, 2019-06-27 18:21:12.0
DEBUG [main] - <== Total: 2
角色名:管理者
DEBUG [main] - ==> Preparing: SELECT p.* FROM sys_privilege p INNER JOIN sys_role_privilege rp ON rp.privilege_id = p.id WHERE rp.role_id = ?
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <== Columns: id, privilege_name, privilege_url
TRACE [main] - <== Row: 1, 使用者管理, /users
TRACE [main] - <== Row: 2, 角色管理, /roles
TRACE [main] - <== Row: 3, 系統日志, /logs
DEBUG [main] - <== Total: 3
權限名:使用者管理
權限名:角色管理
權限名:系統日志
角色名:普通使用者
DEBUG [main] - ==> Preparing: SELECT p.* FROM sys_privilege p INNER JOIN sys_role_privilege rp ON rp.privilege_id = p.id WHERE rp.role_id = ?
DEBUG [main] - ==> Parameters: 2(Long)
TRACE [main] - <== Columns: id, privilege_name, privilege_url
TRACE [main] - <== Row: 4, 人員維護, /persons
TRACE [main] - <== Row: 5, 機關維護, /companies
DEBUG [main] - <== Total: 2
權限名:人員維護
權限名:機關維護
仔細分析上面的日志,會發現隻有在使用到了角色資訊和權限資訊時,才執行了對應的資料庫查詢。
需要注意的是,延遲加載依賴于MyBatis全局配置中的aggressiveLazyLoading,在之前的部落格講解association标簽時,我們已經将其配置為了false,是以這裡的執行結果符合我們的預期:
<settings>
<!--其他配置-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
關于該參數的詳細講解,請檢視MyBatis從入門到精通(十):使用association标簽實作嵌套查詢。
5. 總結
使用collection标簽實作嵌套查詢,用到的屬性總結如下:
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從入門到精通》
7. 最後
打個小廣告,歡迎掃碼關注微信公衆号:「申城異鄉人」,不定期分享Java技術幹貨,讓我們一起進步。