MyBatis高级结果映射
本篇主要介绍
- association一对一查询
介绍-一对一映射
一对一映射因为不需要考虑是否存在重复数据,使用简单,可以直接使用MyBatis自动映射。
实践-自动映射
要求一个用户对应一个角色,类定义如下
public class SysUser implements Serializable {
private static final long serialVersionUID = 854674092994608168L;
.....
private SysRole role;
......
}
自动映射就是MyBatis将查询的结果通过别名映射到返回的对象上。复杂的属性映射如:将role.role_name映射给role.roleName上;MyBatis会先查找role属性,如果存在role属性就创建role对象,然后再role对象中继续查找roleName,将role_name的值绑定到role对象的roleName属性上。
mapper接口
/**
* 根据用户id获取用户信息和用户的角色信息
* @param id 用户id
* @return 用户和角色信息
*/
SysUser selectUserAndRoleById(Long id);
xml,注意其中role属性的roleName字段,我用的是数据库字段,role_name;其他字段都是驼峰规则,直接对应role对象的属性字段
<select id="selectUserAndRoleById" resultType="tk.mybatis.simple.model.SysUser">
select
u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
u.create_time,
sr.id "role.id",
sr.role_name "role.role_name",
sr.enabled "role.enabled",
sr.create_by "role.createBy",
sr.create_time "role.createTime"
from sys_user u
inner join sys_user_role sur on u.id = sur.user_id
inner join sys_role sr on sur.role_id = sr.id
where u.id =#{id}
</select>
测试方法:
public void selectUserAndRoleById() {
SqlSession sqlSession = getSqlSession();
try {
SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
SysUser sysUser = mapper.selectUserAndRoleById(1L);
Assert.assertNotNull(sysUser);
Assert.assertNotNull(sysUser.getRole());
LOGGER.info("用户信息:{}",sysUser.toString());
} finally {
sqlSession.close();
}
}
测试结果:能够发现role对象的roleName属性也有值,表示mybatis查询的属性列,会将下划线转化为驼峰规则。因为SysRole类中也有SysUser对象,发现最终role对象中的user属性值为空。
==> Preparing: select u.id, u.user_name, u.user_password, u.user_email, u.user_info, u.head_img, u.create_time, sr.id "role.id", sr.role_name "role.role_name", sr.enabled "role.enabled", sr.create_by "role.createBy", sr.create_time "role.createTime" from sys_user u inner join sys_user_role sur on u.id = sur.user_id inner join sys_role sr on sur.role_id = sr.id where u.id =?
==> Parameters: 1(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time, role.id, role.role_name, role.enabled, role.createBy, role.createTime
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 1, 管理员, 1, 1, 2021-01-28 21:58:32.0
<== Total: 1
用户信息:SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021, role=SysRole{id=1, roleName='管理员', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null}}
总结:这种通过一次查询将结果映射导不同对象的方式,被称为嵌套结果查询。
这种方式的好处是减少数据库查询次数,减轻数据库的压力,缺点是要写复杂的sql;由于要在应用服务器上将结果映射到不同类上,会增加服务器的压力。
实践-resultMap配置
该方法使用resultMap进行列名与对象属性映射,需要保证列名不可重复。
mapper,接口返回和上面的自动映射实践一致
/**
* 根据用户id获取用户信息和用户的角色信息
* @param id 用户id
* @return 用户和角色信息
*/
SysUser selectUserAndRoleById2(Long id);
xml,userRoleMap中对role对象关联的部分列名做了别名映射,因为resulMap中列名不能重复。resultMap中result标签与sql的映射关系可以看为:sql查询列名->result标签column属性名->result标签property属性->resultType类属性。
<resultMap id="userRoleMap" type="tk.mybatis.simple.model.SysUser">
<!--@Table sys_user-->
<result property="id" column="id" jdbcType="INTEGER"/>
<result property="userName" column="user_name" jdbcType="VARCHAR"/>
<result property="userPassword" column="user_password" jdbcType="VARCHAR"/>
<result property="userEmail" column="user_email" jdbcType="VARCHAR"/>
<result property="userInfo" column="user_info" jdbcType="VARCHAR"/>
<result property="headImg" column="head_img" jdbcType="OTHER"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<!--role相关属性-->
<result property="role.id" column="role_id"/>
<result property="role.roleName" column="role_name"/>
<result property="role.enabled" column="enabled"/>
<result property="role.createBy" column="create_by"/>
<result property="role.createTime" column="role_create_time" jdbcType="TIMESTAMP"/>
</resultMap>
<select id="selectUserAndRoleById2" resultMap="userRoleMap">
select
u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
u.create_time,
sr.id role_id,
sr.role_name,
sr.enabled,
sr.create_by ,
sr.create_time role_create_time
from sys_user u
inner join sys_user_role sur on u.id = sur.user_id
inner join sys_role sr on sur.role_id = sr.id
where u.id =#{id}
</select>
测试方法:和上面自动映射的测试方法一致,只修改查询的方法为selectUserAndRoleById2即可。
测试结果:也可以查询成功。
==> Preparing: select u.id, u.user_name, u.user_password, u.user_email, u.user_info, u.head_img, u.create_time, sr.id role_id, sr.role_name, sr.enabled, sr.create_by , sr.create_time role_create_time from sys_user u inner join sys_user_role sur on u.id = sur.user_id inner join sys_role sr on sur.role_id = sr.id where u.id =?
==> Parameters: 1(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time, role_id, role_name, enabled, create_by, role_create_time
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 1, 管理员, 1, 1, 2021-01-28 21:58:32.0
<== Total: 1
用户信息:SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021, role=SysRole{id=1, roleName='管理员', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null}}
总结:这个userRoleMap使用起来十分繁琐。可以使用resultMap的继承来简化userRoleMap。
使用如下的resultMap写法,继承基础resutlMap,extends="BaseResultMap"
,也可以得到上面相同的结果。
<resultMap id="userRoleMap" extends="BaseResultMap" type="tk.mybatis.simple.model.SysUser">
<!--role相关属性-->
<result property="role.id" column="role_id"/>
<result property="role.roleName" column="role_name"/>
<result property="role.enabled" column="enabled"/>
<result property="role.createBy" column="create_by"/>
<result property="role.createTime" column="role_create_time" jdbcType="TIMESTAMP"/>
</resultMap>
实践-association配置
association标签用于和一个复杂的类型进行关联,即用于一对一的关联配置。
mapper接口
/**
* 根据用户id获取用户信息和用户的角色信息
* @param id 用户id
* @return 用户和角色信息
*/
SysUser selectUserAndRoleById3(Long id);
xml,使用association标签来映射role属性,其中property就是SysUser中的SysRole属性role。columnPrefix表示SysRole类中属性对应的数据库列都有一个前缀。association标签内部的result标签中property指的就是SysRole类中的属性。MyBatis在映射结果时会自动使用前缀和column值得组合去sql查询的结果取值。
<resultMap id="userRoleMap3" extends="BaseResultMap" type="tk.mybatis.simple.model.SysUser">
<association property="role" javaType="tk.mybatis.simple.model.SysRole" columnPrefix="role_">
<!--role相关属性-->
<result property="id" column="id"/>
<result property="roleName" column="role_name"/>
<result property="enabled" column="enabled"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</association>
</resultMap>
<select id="selectUserAndRoleById3" resultMap="userRoleMap3">
select
u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
u.create_time,
sr.id role_id,
sr.role_name role_role_name,
sr.enabled role_enabled,
sr.create_by role_create_by,
sr.create_time role_create_time
from sys_user u
inner join sys_user_role sur on u.id = sur.user_id
inner join sys_role sr on sur.role_id = sr.id
where u.id =#{id}
</select>
association标签包含以下属性:
- property:对应被配置一对一属性对应实体类中的属性名,必填
- javaType:对应被配置一对一属性对应的Java类型
- resultMap:可以直接使用现有的resultMap
- columnPrefix:查询的列的前缀,配置前缀后,在子标签配置result的column时可以省略前缀
association标签中的resultMap可以使用现有的配置,例如上面的属性类型是SysRole,我们就可以使用SysRole对应的baseMap。如下的配置,也可以成功运行。
<resultMap id="userRoleMap3" extends="BaseResultMap" type="tk.mybatis.simple.model.SysUser">
<association property="role" javaType="tk.mybatis.simple.model.SysRole" columnPrefix="role_" resultMap="tk.mybatis.simple.mapper.SysRoleMapper.BaseResultMap">
</association>
</resultMap>
实践-association嵌套查询
除了上面的关联嵌套结果映射查询,还有一种利用简单的sql通过多次查询转化为我们需要的结果,这种方式与根据业务逻辑手动执行多次sql的方式类似。
association标签的嵌套查询常用的属性如下:
- select:查询标签的id,MyBatis会额外执行这个查询获取嵌套对象的结果
- column:列名,将主查询种的列的结果作为嵌套查询的参数,配置方式如
column={prop1=col1,prop2=col2}
,prop1和pro2将作为嵌套查询的参数 - fetchType:数据加载方式,可选值为lazy和eager,这个配置会覆盖全局的lazyLoadingEnabled配置
先在SysRoleMapper中添加一个通过主键查数据的方法
/**
* 通过主键查角色
* @param id 主键
* @return 角色
*/
SysRole queryById(Long id);
在SysUserMapper中添加一个接口
/**
* 通过association嵌套查询获取用户信息
* @param id 用户主键
* @return 用户和角色信息
*/
SysUser selectUserAndRoleByIdSelect(Long id);
xml,对resultMap中的association标签添加一个column属性,该属性中的{id=role_id}
表示select查询标签的查询参数id取值来自当前查询返回的role_id值。所以sql中必须要返回一个名为role_id的字段。
即column属性中等号左边的表示select标签的参数,等号右边的表示来源的值。该参数也可以为多个,如{id=role_id,name=role_name}
。
<resultMap id="userRoleMapSelect" extends="BaseResultMap" type="tk.mybatis.simple.model.SysUser">
<association property="role" javaType="tk.mybatis.simple.model.SysRole"
column="{id=role_id}" select="tk.mybatis.simple.mapper.SysRoleMapper.queryById">
</association>
</resultMap>
<select id="selectUserAndRoleByIdSelect" resultMap="userRoleMapSelect">
select
u.id,
u.user_name,
u.user_password,
u.user_email,
u.user_info,
u.head_img,
u.create_time,
sur.role_id
from sys_user u
inner join sys_user_role sur on u.id = sur.user_id
where u.id =#{id}
</select>
测试方法和上面的都是类似的,测试结果如下。执行了两次sql。
==> Preparing: select u.id, u.user_name, u.user_password, u.user_email, u.user_info, u.head_img, u.create_time, sur.role_id from sys_user u inner join sys_user_role sur on u.id = sur.user_id where u.id =?
==> Parameters: 1(Long)
<== Columns: id, user_name, user_password, user_email, user_info, head_img, create_time, role_id
<== Row: 1, admin, 123456, admin@mybatis.tk, <<BLOB>>, <<BLOB>>, 2021-01-28 21:57:32.0, 1
====> Preparing: SELECT r.id, r.role_name, r.enabled, r.create_by, r.create_time from sys_role r where r.id = ?
====> Parameters: 1(Long)
<==== Columns: id, role_name, enabled, create_by, create_time
<==== Row: 1, 管理员, 1, 1, 2021-01-28 21:58:32.0
<==== Total: 1
<== Total: 1
用户信息:SysUser{id=1, userName='admin', userPassword='123456', userEmail='admin@mybatis.tk', userInfo='管理员', headImg=null, createTime=Thu Jan 28 21:57:32 CST 2021, role=SysRole{id=1, roleName='管理员', enabled=1, createBy=1, createTime=Thu Jan 28 21:58:32 CST 2021, user=null}}
上面这种嵌套查询,想要在需要role属性的时候再去做查询,这就需要添加association的加载标签和修改mybatis-config.xml中的aggressiveLazyLoading值。
<resultMap id="userRoleMapSelect" extends="BaseResultMap" type="tk.mybatis.simple.model.SysUser">
<association property="role" javaType="tk.mybatis.simple.model.SysRole"
column="{id=role_id}" select="tk.mybatis.simple.mapper.SysRoleMapper.queryById" fetchType="lazy">
</association>
</resultMap>
<settings>
<setting name="logImpl" value="LOG4J"/>
<!-- 开启数据库列驼峰转化-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
aggressiveLazyLoading属性的含义是:当该参数设置为true时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载,反之,每种属性都将按需加载。
当我们需要在出发某些方法时,将所有的数据加载进来,aggressiveLazyLoading值为false时,我们可以使用lazyLoadTriggerMethods,该参数的含义是,当调用配置中的方法时,加载全部的延迟加载数据。
<settings>
<setting name="logImpl" value="LOG4J"/>
<!-- 开启数据库列驼峰转化-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>