1. 程式人生 > >【筆記】Mybatis高階查詢(三)--使用<association>標籤實現巢狀查詢及延遲載入

【筆記】Mybatis高階查詢(三)--使用<association>標籤實現巢狀查詢及延遲載入

<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”。因此在使用預設值的情況下只要調其中一個方法即可。