【笔记】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”。因此在使用默认值的情况下只要调其中一个方法即可。

猜你喜欢

转载自blog.csdn.net/q283614346/article/details/83246440