MyBatis使用 resultMap的association 标签实现懒加载

首先 , 做好初始化工作 ,  建立三张表 , sys_user ,sys_role ,sys_user_role , 建表语句如下

CREATE TABLE `sys_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `user_name` varchar(64) DEFAULT NULL COMMENT '用户名',
  `password` varchar(64) DEFAULT NULL COMMENT '密码',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `info` text COMMENT '简介',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';

CREATE TABLE `sys_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色id',
  `role_name` varchar(64) DEFAULT NULL COMMENT '角色名',
  `enabled` tinyint(1) DEFAULT NULL COMMENT '有效表示;0无效,1有效',
  `create_by` int(11) DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COMMENT='角色表';

CREATE TABLE `sys_user_role` (
  `user_id` int(11) NOT NULL,
  `role_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf-8;

随后创建好对应的实体类 SysUser 和 SysRole , 以及SysUserMapper , SysRoleMapper 接口 和 接口对应的映射XML文件 , 准备工作做好之后, 就开始使用MyBatis中 resultMap的association 标签实现懒加载吧 !

在SysUserMapper接口中添加一个方法selectUserAndRoleById , 对应的SQL 语句如下

<select id="selectUserAndRoleById" resultMap="userRoleMap">
		select u.id , u.user_name as userName , u.password , u.email,
		u.info , u.create_time as createTime , 
        ur.role_id as roleId from sys_user u
		left join sys_user_role ur on u.id =ur.user_id where u.id = #{id}
	</select>

可以看到这个select标签使用得是resultMap , 这个resultMap是这样写的

<resultMap type="SysUser" id="userRoleMap" extends="userMap">
		<association property="sysRole" column="{id=roleId}" 
			select="com.xc.mybtais.mapper.SysRoleMapper.selectSysRoleById"
			fetchType="lazy">
		</association>
	</resultMap>

这个resultMap继承了一个id = userMap的resultMap , 其实 只是把sys_user对应的实体关系抽了出去而已

<resultMap type="SysUser" id="userMap">
		<id property="id" column="id"/>
		<result property="userName" column="user_name"/>
		<result property="password" column="password"/>
		<result property="email" column="email"/>
		<result property="info" column="info"/>
		<result property="createTime" column="create_time"/>
	</resultMap>

主要回到id = userRoleMap的resultMap, 这个resultMap使用了association标签 , 该标签指定了四个属性

      property : SysUser实体类中对应的角色属性 , 我这里叫SysRole

      column  :  列名(或别名) , 将主查询的列的结果作为嵌套查询的参数 ,   配置方式如 column ={prop1=col1,prop2=col2} , prop1和                           prop2将作为嵌套查询的参数

      select : 另一个映射查询的 id , 即另外一个 select 标签的 id , 不一定存在于当前XML中 ,但是一定要能被MyBatis加载 (此处我放                     在了SysRoleMapper.xml中), MyBatis会额外执行这个查询获取嵌套对象的结果 . (此处贴出该 select 标签)

<select id="selectSysRoleById" resultType="SysRole">
	 	select id , role_name as roleName , enabled ,
	 	create_by as createBy , create_time as createTime
	 	from sys_role where id = #{id}
	</select>

       fentchType : 数据加载方式 ,可选值为 lazy 和 eager , 分别是延迟加载和积极加载 , 这个配置会覆盖全局的 lazyLoadingEnabled                               配置 

此时 , 我们可以看到 ,在主查询中 , 即 selectUserAndRoleById 这个select 语句 , 其只查了 sys_user表和 sys_user_role 表 , 并没有查 sys_role 表 , 但是它取出了 sys_user_role表中的一个字段 role_id ( as roleId ), 这个字段用在了 association标签的 column属性中 , 可以看到这个column="{id=roleId}" ,其中 id 指的是 association标签的select属性所代表的方法的参数 ,而 roleId 指的是从 selectUserAndRoleById 这个select 语句中得到的 role_id (as roleId) , 所以其意义就是 将主查询中得到的结果中的某一列值作为嵌套的查询的参数 

至此 , SQL方面就写完了, 使用JUNIT来测试一下吧 

先插入几条记录

insert into sys_user(id,user_name) values(1,'admin')
insert into sys_role(id,role_name) values(1,'admin')
insert into sys_user_role(user_id,role_id) values(1,1)
    @Test
	public void testSysUserAndRoleLazyLoading() {
		SysUserMapper userMapper = getMapper(SysUserMapper.class);
		SysUser user = userMapper.selectUserAndRoleById(1);
		System.out.println(user);
		System.out.println("------------------lazy loading ----------------------");
		System.out.println(user.getSysRole());
	}

预期结果是 , 先执行 selectUserAndRoleById 这个 sql , 等到调用 user.getRole()方法的时候才会去执行 selectSysRoleById 这个sql

点击运行 , 结果却是这样的

Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
Opening JDBC Connection
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@782663d3]
==>  Preparing: select u.id , u.user_name as userName , u.password , u.email, u.info , u.create_time as createTime , ur.role_id as roleId from sys_user u left join sys_user_role ur on u.id =ur.user_id where u.id = ? 
==> Parameters: 1(Integer)
<==    Columns: id, userName, password, email, info, createTime, roleId
<==        Row: 1, admin, admin, null, <<BLOB>>, 2018-07-11 22:30:38.0, 1
====>  Preparing: select id , role_name as roleName , enabled , create_by as createBy , create_time as createTime from sys_role where id = ? 
====> Parameters: 1(Integer)
<====    Columns: id, roleName, enabled, createBy, createTime
<====        Row: 1, admin, 1, null, 2018-07-11 22:32:21.0
<====      Total: 1
<==      Total: 1
SysUser [id=1, userName=admin]
------------------lazy loading ----------------------
SysRole [id=1, roleName=admin]

可以看到 , 两个SQL先后执行 了 , 说明并没有 成功懒加载 . 此时 , 我们想到在mybatis全局配置中的一项参数 aggressiveLazyLoading , 这个配置的意义是 , 当该参数 为true , 对任意延迟属性的调用会使带有延迟加载属性的对象完整加载 , 反之 ,按需加载 ; 其默认值为 true , 此时我们修改该参数属性为false

<settings>
		<setting name="logImpl" value="STDOUT_LOGGING"/>
		
		<setting name="aggressiveLazyLoading" value="false"/>
		
	</settings>

修改之后再次执行该JUNIT Test ,发现结果还是和上次一模一样(结果见上图) , 为什么还会这样呢 ? 经过多次调查 , 发现mybatis还有一项全局配置参数 , lazyLoadTriggerMethods , 其意义是 , 当调用配置中的方法时 , 加载全部的延迟加载数据 . 默认 equals , clone , hashCode ,toString ; 很不幸 , 我的Test方法中有这样一行代码 , System.out.println(user); 此时调用了toString 方法 , 所以也加载了全部的延迟加载数据 , 此时我们在修改mybatis配置

<settings>
		<setting name="logImpl" value="STDOUT_LOGGING"/>
		
		<setting name="aggressiveLazyLoading" value="false"/>
        
        <setting name="lazyLoadTriggerMethods" value="equals"/>
		
	</settings>

此时 , 修改成只在调用了 equals 方法的时候才加载 , ok , 在执行一次吧

Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
Opening JDBC Connection
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@782663d3]
==>  Preparing: select u.id , u.user_name as userName , u.password , u.email, u.info , u.create_time as createTime , ur.role_id as roleId from sys_user u left join sys_user_role ur on u.id =ur.user_id where u.id = ? 
==> Parameters: 1(Integer)
<==    Columns: id, userName, password, email, info, createTime, roleId
<==        Row: 1, admin, admin, null, <<BLOB>>, 2018-07-11 22:30:38.0, 1
<==      Total: 1
SysUser [id=1, userName=admin]
------------------lazy loading ----------------------
==>  Preparing: select id , role_name as roleName , enabled , create_by as createBy , create_time as createTime from sys_role where id = ? 
==> Parameters: 1(Integer)
<==    Columns: id, roleName, enabled, createBy, createTime
<==        Row: 1, admin, 1, null, 2018-07-11 22:32:21.0
<==      Total: 1
SysRole [id=1, roleName=admin]

此时,可以清晰的看到 , 只有在我调用了 user.getSysRole() 方法的时候 , 才去执行了 selectSysRoleById 这个sql , 所以至此 ,可以宣布延迟加载问题已实现 .

但是 , 还有一些细节需要提醒 , 使用延迟加载有时候会经常遇到一些莫名其妙的问题:有些时候延迟加载可以得到数据,有些时候延迟加载就会报错,为什么会出现这种情况呢?
         MyBatis 延迟加载是通过动态代理实现的,当调用配置为延迟加载的属性方法时, 动态代理的操作会被触发,这些额外的操作就是通过 MyBatis 的 SqlSession去执行嵌套 SQL 的 。由于在和某些框架集成时, SqlSession 的生命周期交给了框架来管理,因此当对象超出SqlSession 生命周期调用时,会由于链接关闭等问题而抛出异常 。 在和 Spring 集成时,要确保只能在 Service 层调用延迟加载的属性 。 当结果从 Service 层返回至 Controller 层时, 如果获取延迟加载的属性值,会因为 SqlSession已经关闭而抛出异常 。

最后的最后 , 终于写完了......

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值