一、一对一映射
假设在权限系统中,一个用户只能拥有一个角色,即用户和角色之间的关系限制为一对一的关系。
1.1 使用自动映射处理一对一关系
一个用户拥有一个角色,我们在用户类SysUser中添加角色类SysRole字段。如下:
public class SysUser {
...原有字段
/**
* 用户角色
*/
private SysRole role;
public SysRole getRole() {
return role;
}
public void setRole(SysRole role) {
this.role = role;
}
}
使用自动映射就是通过别名让MyBatis 自动将值匹配到对应的宇段上, 简单的别名映射如user_name 对应userName。除此之外MyBatis还支持复杂的属性映射,可以多层嵌套,例如将role . role_name 映射到role.roleName 上。 MyBatis 会先查找role 属性,如果存在 role 属性就创建role 对象,然后在role 对象中继续查找roleName,将 role_name 的值绑定到role对象的roleName 属性上。
示例:在UserMapper.xml映射文件中添加如下代码:
<select id="selectUserAndRoleByid" resultType="com.wyf.mybaties.model.SysUser">
SELECT u.id,
u.user_name userName,
u.user_password userPassword,
u.user_email userEmail,
u.user_info userInfo,
u.head_img headImg,
u.create_time createTime,
r.id "role.id",
r.role_name "role.roleName",
r.enabled "role.enabled",
r.create_by "role.createBy",
r.create_time "role.createTime"
from sys_user u
inner join sys_user_role ur on u.id = ur.user_id
inner join sys_role r on ur.role_id = r.id
where u.id = #{id}
</select>
在对应的接口文件中添加方法:
/**
* 根据用户 id 获取用户信息和用户的角色信息
*
* @param id
* @return
*/
SysUser selectUserAndRoleByid(Long id);
测试代码:
/**
* 一对一关系
*/
@Test
public void selectUserAndRoleById(){
//获取SqlSession
SqlSession sqlSession = getSqlSession();
try{
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
SysUser user = userMapper.selectUserAndRoleByid(1001L);
System.out.println(user.getRole().getRoleName());
}finally {
sqlSession.close();
}
}
1.2 使用resultMap配置一对一映射
除了使用 MyBatis 的自动映射来处理一对一嵌套外,还可以在XML 映射文件中配置结果映射。
在UserMapper.xml映射文件中本就存在一个userMap 的映射配置,因此 userRoleMap 只需要继承userMap,然后添加role 特有的配置即可, userRoleMap 修改后的代码如下:
<resultMap id="userMap" type="com.wyf.mybaties.model.SysUser">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="userPassword" column="user_password"/>
<result property="userEmail" column="user_email"/>
<result property="userInfo" column="user_info"/>
<result property="headImg" column="head_img" jdbcType="BLOB"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>
<resultMap id="userRoleMap" extends="userMap" type="com.wyf.mybaties.model.SysUser">
<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>
使用继承不仅使配置更简单,而且当对主表 userMap 进行修改时也只需要修改一处。
1.3 使用resultMap的association 标签配置一对一映射
在resultMap 中, association 标签用于和一个复杂的类型进行关联,即用于一对一的关联配置。修改上例代码如下:
<resultMap id="userRoleMap" extends="userMap" type="com.wyf.mybaties.model.SysUser">
<association property="role" columnPrefix="role_" javaType="com.wyf.mybaties.model.SysRole">
<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>
association 标签包含以下属性:
- property:对应实体类中的属性名,必填项。
- javaType: 属性对应的Java 类型。
- resultMap: 可以直接使用现有的resultMap,而不需要在这里配置。
- columnPrefix:查询列的前缀,配置前缀后,在子标签配置result 的. column 时可以省略前缀。
配置columnPrefix=” role_”,在写SQL 的时候,和sys_role 表相关的查询列的别名都要有“role_”前缀,在内部result配置 column 时,需要配置成去掉前缀的列名, MyBatis 在映射结果时会自动使用前缀和column 值的组合去SQL查询的结果中取值。
因为配置了列的前缀,因此还需要修改SQL,代码如下。
<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,
r.id role_id,
r.role_name "role_role_name",
r.enabled "role_enabled",
r.create_by "role_create_by",
r.create_time "role_create_time"
from sys_user u
inner join sys_user_role ur on u.id = ur.user_id
inner join sys_role r on ur.role_id = r.id
where u.id = #{id}
</select>
注意和 sys_role 相关列的别名(数据库表列名),都已经改成了“role_”前缀,特别注意role_name 增加前缀后为role_role_name。
使用 association 配置时还可以使用 resultMap 属性配置成一个已经存在的resultMap 映射,如下:
我们在RoleMapper.xml映射文件中添加如下resultMap:
<resultMap id="roleMap" type="com.wyf.mybaties.model.SysRole">
<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"/>
</resultMap>
在UserMapper.xml文件中添加如下代码:
<resultMap id="userRoleMap" extends="userMap" type="com.wyf.mybaties.model.SysUser">
<association property="role" columnPrefix="role_" resultMap="com.wyf.mybaties.mapper.RoleMapper.roleMap"/>
</resultMap>
roleMap定义在RoleMapper.xml文件中,因此我们在UserMapper.xml中,指定resultMap时应该指定其正确的地址。
1.4 association标签的嵌套查询
前面3 种通过复杂的SQL 查询(多表联结查询)获取结果,在数据量大的时候可能会影响性能,除了上述3种多表查询,还可以利用简单的SQL 通过多次查询转换为我们需要的结果,这种方式与根据业务逻辑于动执行多次SQL 的方式相像,最后会将结果组合成一个对象。
1)我们在RoleMapper.xml映射文件中添加如下查询:
<select id="selectRoleByid" resultMap="roleMap">
select * from sys_role where id = #{id}
</select>
2)在UserMapper.xml映射文件中添加如下代码:
<resultMap id="userRoleMapSelect" extends="userMap" type="com.wyf.mybaties.model.SysUser">
<association property="role"
fetchType="lazy"
select="com.wyf.mybaties.mapper.RoleMapper"
column="{id=role_id}"/>
</resultMap>
association 标签的嵌套查询常用的属性如下:
- select:另一个映射查询的id, MyBatis 会额外执行这个查询获取嵌套对象的结果。
- fetchType:数据加载方式,可选值为lazy 和eager,分别为延迟加载和积极加载,这个配置会覆盖全局的lazyLoadingEnabled 配置。
- column:列名(或别名),将主查询中列的结果作为嵌套查询的参数,配置方式如:
column={prop1=col1 , prop2=col2}, prop1 和prop2 将作为嵌套查询的参数。
<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,
ur.role_id
from sys_user u
inner join sys_user_role ur on u.id = ur.user_id
where u.id = #{id}
</select>
3)在对应接口添加方法:
/**
* 根据用户id获取用户信息和用户的角色信息,嵌套查询方式
*
* @param id
* @return
*/
SysUser selectUserAndRoleByIdSelect(Long id);
测试代码如下:
@Test
public void selectUserAndRoleByIdSelect(){
//获取SqlSession
SqlSession sqlSession = getSqlSession();
try{
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
SysUser user = userMapper.selectUserAndRoleByIdSelect(1001L);
System.out.println(user.getRole().getRoleName());
}finally {
sqlSession.close();
}
}
上述association嵌套查询执行时,首先执行 select u.id, u.user_name……语句(查询获取到SysUser信息),查询结果只有一条,根据这一条数据的role_id关联另一个查询select * from sys_role……(查询获取SysUser的属性role即SysRole类的信息),因此执行了两次SQL查询。
这里有一些问题,首先,我们是否会用到SysRole呢?如果查询出来并没有使用,那不就白白浪费了一次查询吗?其次,如果查询的不是1条数据,而是N条数据,那就会出现N+1问题,主SQL 会查询一次,查询出N条结果,这N条结果要各自执行一次查询,那就需要进行N次查询。 如何解决这个问题呢?
前面我们介绍association 标签的属性时,介绍了 fetchType 数据加载方式,这个方式可以帮我们实现延迟加载,解决 N+l 的问题。按照上面的介绍,需要把 fetchType 设置为lazy,这样设置后,只有当调用 getRole()方法获取 role 的时候, MyBatis 才会执行嵌套查询去获取数据。
二、结束
本文是MyBatis的学习笔记,主要介绍一对一映射的相关内容。