首先 , 做好初始化工作 , 建立三张表 , 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已经关闭而抛出异常 。
最后的最后 , 终于写完了......