【筆記】Mybatis高階查詢(三)--使用<association>標籤實現巢狀查詢及延遲載入
阿新 • • 發佈:2018-11-01
<association>
標籤實現巢狀查詢,需要用到以下屬性:
select:另一個對映查詢的ID,Mybatis會額外執行這個查詢獲取巢狀物件的結果。
column:列名或別名,將主查詢中列的結果作為巢狀查詢的引數,配置方式如column={prop1=col1,prop2=col2},prop1和prop2作為巢狀查詢的引數。
fetchType:資料的載入方式,可選值為lazy和eager,分別為延遲載入和積極載入。
以下例子將使用上一節的功能,用巢狀查詢方式進行配置。
1. <association>
巢狀查詢
- 在SysUserMapper.xml中增加以下resultMap和selectUserAndRoleByIdSel方法
<!-- 使用resultMap的association標籤進行巢狀查詢 --> <resultMap id="userRoleMapSelect" extends="userMap" type="ex.mybatis.rbac.model.SysUser"> <!-- 巢狀查詢role,column配置的是巢狀查詢SQL的引數,當有多個時用逗號隔開, fetchType="lazy"實現延遲載入 --> <association property="role" column="{id=role_id}" select="ex.mybatis.rbac.mapper.SysRoleMapper.selectByPrimaryKey"/> </resultMap> <!-- 使用resultMap的association標籤進行巢狀查詢 --> <select id="selectUserAndRoleByIdSel" resultMap="userRoleMapSelect"> select u.id, u.user_name, u.user_password, u.user_email, u.create_time, u.user_info, u.head_img, ur.role_id from sys_user u inner join sys_user_role ur on u.id = ur.user_id where u.id = #{id} </select>
注意與上一節對比,發現關聯表中已沒有了sys_role表了,因為不是通過一個SQL獲取全部資訊的。角色資訊通過SysRoleMapper.xml的selectByPrimaryKey查詢
- 在SysUserMapper介面中增加selectUserAndRoleByIdSel方法
/** * 假設一個使用者只有一個角色(使用resultMap的association標籤進行巢狀查詢) * @param id * @return */ SysUser selectUserAndRoleByIdSel(Long id);
- 在UserMaperTest類中增加selectUserAndRoleByIdSel測試方法
@Test
public void testSelectUserAndRoleByIdSel() {
// 獲取SqlSession
SqlSession sqlSession = openSession();
try {
// 獲取SysUserMapper介面
SysUserMapper userMapper = sqlSession.getMapper(SysUserMapper.class);
// 呼叫selectUserAndRoleByIdSel方法
SysUser user = userMapper.selectUserAndRoleByIdSel(1001L);
// user不為空
Assert.assertNotNull(user);
// role不為空
Assert.assertNotNull(user.getRole());
System.out.println();
} finally {
sqlSession.close();
}
}
- 測試結果
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - ==> Preparing: select u.id, u.user_name, u.user_password, u.user_email, u.create_time, u.user_info, u.head_img, ur.role_id from sys_user u inner join sys_user_role ur on u.id = ur.user_id where u.id = ?
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - ==> Parameters: 1001(Long)
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - <== Columns: id, user_name, user_password, user_email, create_time, user_info, head_img, role_id
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - <== Row: 1001, test, 123456, [email protected], 2018-10-02 17:17:11.0, <<BLOB>>, <<BLOB>>, 2
[ex.mybatis.rbac.mapper.SysRoleMapper.selectByPrimaryKey] - ====> Preparing: select id, role_name, enabled, create_by, create_time from sys_role where id = ?
[ex.mybatis.rbac.mapper.SysRoleMapper.selectByPrimaryKey] - ====> Parameters: 2(Long)
[ex.mybatis.rbac.mapper.SysRoleMapper.selectByPrimaryKey] - <==== Columns: id, role_name, enabled, create_by, create_time
[ex.mybatis.rbac.mapper.SysRoleMapper.selectByPrimaryKey] - <==== Row: 2, 普通使用者, 0, 1, 2018-10-01 18:27:37.0
[ex.mybatis.rbac.mapper.SysRoleMapper.selectByPrimaryKey] - <==== Total: 1
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - <== Total: 1
2. <association>
巢狀查詢N+1問題
通過上面的例子發現,第一個SQL的查詢結果只有一條,所以根據這條資料的role_id關聯了另外一個查詢,因此執行了2次SQL。如果查詢不一定用到SysRole資料呢?如果查詢出來並沒有使用,那不就白浪費了一次查詢嗎?如果第一個SQL查詢的結果不是1條而是N條,就會出現N+1的問題。
主SQL查詢一次,查出N條資料,這N條結果要各自執行一次查詢。
解決N+1問題可以通過延遲載入方式。延遲載入可通過
<association>
的fetchType屬性為lasy實現。
以下例子為延遲載入用法,要實現延遲載入還需要在mybatis.xml加入全域性配置aggressiveLazyLoading=false(3.4.5後的版本預設為false,之前的版本預設為true,當為true時延遲載入無效),如下:
<settings>
<!-- 延遲載入配置,3.4.5版本預設為false,當為true時<association>標籤的fetchType="lazy"無效,要生效必須為false-->
<setting name="aggressiveLazyLoading" value="false" />
</settings>
- 把上面例子的resultMap的
<association>
標籤加上fetchType="lazy"
,這樣設定後,只有當呼叫getRole()方法獲取role時,才會去執行sys_role查詢操作,如下:
<!-- 使用resultMap的association標籤進行巢狀查詢 -->
<resultMap id="userRoleMapSelect" extends="userMap" type="ex.mybatis.rbac.model.SysUser">
<!-- 巢狀查詢role,column配置的是巢狀查詢SQL的引數,當有多個時用逗號隔開, fetchType="lazy"實現延遲載入 -->
<association property="role" fetchType="lazy" column="{id=role_id}" select="ex.mybatis.rbac.mapper.SysRoleMapper.selectByPrimaryKey"/>
</resultMap>
- 延時載入測試程式碼
@Test
public void testSelectUserAndRoleByIdSel() {
// 獲取SqlSession
SqlSession sqlSession = openSession();
try {
// 獲取SysUserMapper介面
SysUserMapper userMapper = sqlSession.getMapper(SysUserMapper.class);
// 呼叫selectUserAndRoleByIdSel方法
SysUser user = userMapper.selectUserAndRoleByIdSel(1001L);
// user不為空
Assert.assertNotNull(user);
System.out.println("呼叫user.getRole()");
System.out.println(user.getRole());
} finally {
sqlSession.close();
}
}
- 測試結果(不呼叫user.getRole(),不會執行sys_role的selectByPrimaryKey方法)
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - ==> Preparing: select u.id, u.user_name, u.user_password, u.user_email, u.create_time, u.user_info, u.head_img, ur.role_id from sys_user u inner join sys_user_role ur on u.id = ur.user_id where u.id = ?
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - ==> Parameters: 1001(Long)
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - <== Columns: id, user_name, user_password, user_email, create_time, user_info, head_img, role_id
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - <== Row: 1001, test, 123456, [email protected], 2018-10-02 17:17:11.0, <<BLOB>>, <<BLOB>>, 2
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - <== Total: 1
- 測試結果(呼叫user.getRole(),會執行sys_role的selectByPrimaryKey方法)
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - ==> Preparing: select u.id, u.user_name, u.user_password, u.user_email, u.create_time, u.user_info, u.head_img, ur.role_id from sys_user u inner join sys_user_role ur on u.id = ur.user_id where u.id = ?
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - ==> Parameters: 1001(Long)
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - <== Columns: id, user_name, user_password, user_email, create_time, user_info, head_img, role_id
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - <== Row: 1001, test, 123456, [email protected], 2018-10-02 17:17:11.0, <<BLOB>>, <<BLOB>>, 2
[ex.mybatis.rbac.mapper.SysUserMapper.selectUserAndRoleByIdSel] - <== Total: 1
呼叫user.getRole()
[ex.mybatis.rbac.mapper.SysRoleMapper.selectByPrimaryKey] - ==> Preparing: select id, role_name, enabled, create_by, create_time from sys_role where id = ?
[ex.mybatis.rbac.mapper.SysRoleMapper.selectByPrimaryKey] - ==> Parameters: 2(Long)
[ex.mybatis.rbac.mapper.SysRoleMapper.selectByPrimaryKey] - <== Columns: id, role_name, enabled, create_by, create_time
[ex.mybatis.rbac.mapper.SysRoleMapper.selectByPrimaryKey] - <== Row: 2, 普通使用者, 0, 1, 2018-10-01 18:27:37.0
[ex.mybatis.rbac.mapper.SysRoleMapper.selectByPrimaryKey] - <== Total: 1
SysRole [id=2, roleName=普通使用者, enabled=disabled, createBy=1, createTime=Mon Oct 01 18:27:37 CST 2018]
特別注意:Mybatis延遲載入是通過動態代理實現的,這些額外的查詢是通過SqlSession去執行巢狀SQL。所以需要確保SqlSession不被關閉的時候執行。否則會因為SqlSession已關閉導致出錯。
因為我們把全域性配置aggressiveLazyLoading設為了false,所有的都fetchType="lazy"都是延遲載入了。如果需要一次性載入所有資料怎麼辦?
Mybatis提供了引數lazyLoadTriggerMetgods解決這個問題。這個引數的作用是當呼叫配置中的方法時,載入全部延時載入的資料。預設值的方法為“equals, clone, hashCode, toString”。因此在使用預設值的情況下只要調其中一個方法即可。