为方便理解,本章涉及示例代码已上传至 gitee
==>获取示例代码请点击这里。。。
拉取示例代码时,请拉取所有分支,master 分支只是做了示例的初始化
MyBatis 的高级结果映射主要是针对一对一,一对多的表关系结果映射。
一对一结果映射
在使用多表联查的时候,想要将联查出来的另一个表的数据映射到主表映射对象上,需要在主表的实体对象中增加一个从表的对象属性,并添加 set、get 方法。
实现一对一映射共有三种方式:
- SQL 别名方式实现映射
- SQL 别名与 resultMap 标签配合实现映射
- association 标签实现映射
通过 SQL 别名方式实现映射
示例代码如下:
// 实体类
public class SysUser {
// 从表对象属性
private SysRole role;
public SysRole getRole() {
return role;
}
public void setRole(SysRole role) {
this.role = role;
}
}
XMl SQL :
<select id="selectUserInfoAndRoleInfo" resultType="SysUser">
select
user.id,user.user_name,user.user_email,
role.id "role.id",role.role_name "role.roleName",role.enabled "role.enabled"
from sys_user user
inner join sys_user_role userRole on user.id = userRole.user_id
inner join sys_role role on userRole.role_id = role.id
where
1 = 1
<if test="userId != null" >
and user.id = #{userId}
</if>
</select>
SysUserMapper.class
public interface SysUserMapper {
SysUser selectUserInfoAndRoleInfo(@Param("userId") Integer id);
}
之后,调试代码便可正常查询。
SQL 别名与 resultMap 标签配合实现映射
相比 通过 SQL 别名方式实现映射 与 resultMap 标签配合 方式只需要改动一下 XML 中的查询 SQL 的返回类型和 resultMap 标签中的值映射,如下:
<resultMap id="UserEntity" type="SysUser" >
<id column="id" property="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" />
<result property="role.id" column="role_id" />
<result property="role.roleName" column="roleName" />
<result property="role.enabled" column="enabled" />
<result property="role.createBy" column="role.createBy" />
<result property="role.createTime" column="role.createTime" />
</resultMap>
<select id="selectUserInfoAndRoleInfoUseTag" resultMap="UserEntity">
select
user.id,
user.user_name,
user.user_email,
role.id "role_id",
role.role_name "roleName",
role.enabled "enabled",
role.create_By "role.createby",
role.create_time "role.createTime"
from sys_user user
inner join sys_user_role userRole on user.id = userRole.user_id
inner join sys_role role on userRole.role_id = role.id
where
1 = 1
<if test="userId != null" >
and user.id = #{userId}
</if>
</select>
之后,增加一个接口方法,方法名与 select 标签的 id 对应,便可调用测试了。
association 标签实现映射
首先,先介绍一下 association 标签中常用到的属性值:
- property : 子表结果要映射到的对象属性
- columnPrefix : 对应的 SQL 查询字段的列名前缀
- resultMap:可以使用现有的 resultMap ,不需要再次配置,引用时为被引用 resultMap 的 id,如果在同一个 XML 文件中,仅 id 即可,再其他 XML 文件中,则需要 namespace + id 来进行引用
- select : 子查询时执行对应的 SQL,通过其 value 和已有的 select 标签的 id 对应来进行关联,如果在同一个 XML 文件中,仅 id 即可,再其他 XML 文件中,则需要 namespace + id 来进行引用
- column : 列名/别名,将主查询中列的结果作为嵌套查询的参数,column = “{子查询条件列名 = 主查询列名/别名}”,当子查询参数为多个时,用逗号隔开,如:column = “{子查询条件列名 = 主查询列名/别名,子查询条件列名 = 主查询列名/别名}”
- fetchType : 值分为 lazy|eager;设置为 lazy,则表示只有当调用实体类对象中的子查询对象时,才会去真正执行子查询
association 标签的使用分为两种情况:
-
不使用子查询
-
使用子查询
不使用子查询的方式:
示例如下:
<resultMap id="UserEntity" type="SysUser" >
<id column="id" property="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" />
<!-- <result property="role.id" column="role_id" />-->
<!-- <result property="role.roleName" column="roleName" />-->
<!-- <result property="role.enabled" column="enabled" />-->
<!-- <result property="role.createBy" column="role.createBy" />-->
<!-- <result property="role.createTime" column="role.createTime" />-->
<association property="role" columnPrefix="role_" >
<result property="id" column="id" />
<result property="roleName" column="role_name" />
<result property="enabled" column="enabled" />
<result property="createBy" column="role_createBy" />
<result property="createTime" column="role_createTime" />
</association>
</resultMap>
以上的这个 resultMap 是以 SQL 别名与 resultMap 标签配合实现映射 这一节中的 XML 为模板,进行改造的,只是注释掉了原来直接 result 对应的返回 role 属性上的映射,新增了 association 标签,将原来 role 属性的字段映射放到 association 标签中,由于 association 设置了 columnPrefixcolumnPrefix 属性,该属性对应 SQL 别名列前缀,故此,SQL 中对应列的列别名应该是 columnPrefixcolumnPrefix + association 标签下 result 标签的 column 属性值;SQL 如下:
<select id="selectUserInfoAndRoleInfoUseTag2" resultMap="UserEntity">
select
user.id,
user.user_name,
user.user_email,
role.id "role_id", <!-- 求留意此处的 role_id:其中 role_ 对应 <association> 标签的 columnPrefix 值,id 对应 <association> 标签下 <result> 的 column 的值;以下 role 表中的别名属性同理 -->
role.role_name "role_role_name",
role.enabled "role_enabled",
role.create_By "role_role_createBy",
role.create_time "role_role_createTime"
from sys_user user
inner join sys_user_role userRole on user.id = userRole.user_id
inner join sys_role role on userRole.role_id = role.id
where
1 = 1
<if test="userId != null" >
and user.id = #{userId}
</if>
</select>
配置完后,便可以正常使用。
使用子查询:
示例如下:
<resultMap id="UserRoleEntity" type="SysUser" ><!-- autoMapping="true"-->
<id column="id" property="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" />
<association property="role" column="{id = role_id}" select="com.mybatis.simple.mapper.SysRoleMapper.selectById">
</association>
</resultMap>
简单设置之后,便可正常使用了。
这里,我们使用的是子查询的方式,通过查询出主表的数据后,将主表查询出来的 role_id 字段的值作为 com.mybatis.simple.mapper.SysRoleMapper.selectById 查询的值,通过 column="{id = role_id}" 来对应,再进行一次查询,所以,控制台会打印出两条 SQL 查询语句。
下面再来看一下 fatchType 属性,我们在上面的 association 标签中增加 fatchType = “lazy” 属性,如下:
<association property="role" column="{id = role_id}" select="com.mybatis.simple.mapper.SysRoleMapper.selectById" fetchType="lazy" >
</association>
<!-- 还需要将对应的 SQL 修改为 查询多条数据,因为当查询只返回一条数据时,MyBatis 查询单条数据和多条数据都会调用 selectList 的方法,只不过单挑数据的返会最后会调用一个 list.get(0) 来进行返回,而在这一步,会默认调用子查询,这里为了方便看设置的懒加载的效果,我们将 SQL 修改为查询多条数据 -->
<select id="selectUserInfoAndRoleInfoUseTag2" resultMap="UserRoleEntity" ><!-- resultMap="UserEntity" -->
select
user.id,
user.user_name,
user.user_email,
role.id "role_id"
from sys_user user
inner join sys_user_role userRole on user.id = userRole.user_id
inner join sys_role role on userRole.role_id = role.id
where
1 = 1
<if test="list != null" >
and user.id in
<foreach collection="list" open="(" close=")" separator="," item="userId" >
#{userId}
</foreach>
</if>
</select>
// 接口方法如下:
List<SysUser> selectUserInfoAndRoleInfoUseTag2(List<Integer> userId);
在测试类中打个断点,运行,可以看到只有我们在调用 userList.get(0).getRole() 时,才会进行子查询,
我的测试方法如下:
@Test
public void selectUserInfoAndRoleInfoUseTag2() {
SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
List<Integer> list = new ArrayList<>();
list.add(1001);
list.add(3);
List<SysUser> userList = mapper.selectUserInfoAndRoleInfoUseTag2(list);
System.out.println("====================主查询结束===============");
userList.get(0).getRole();
Assert.assertNotNull(userList);
}
控制台打印效果如下:
DEBUG [main] - ==> Preparing: select user.id, user.user_name, user.user_email, role.id "role_id" from sys_user user inner join sys_user_role userRole on user.id = userRole.user_id inner join sys_role role on userRole.role_id = role.id where 1 = 1 and user.id in ( ? , ? )
DEBUG [main] - ==> Parameters: 1001(Integer), 3(Integer)
TRACE [main] - <== Columns: id, user_name, user_email, role_id
TRACE [main] - <== Row: 1001, test, test@mybatis.com, 2
TRACE [main] - <== Row: 3, 猪八戒, BJ@xiyouji.com, 1
DEBUG [main] - <== Total: 2
====================主查询结束===============
DEBUG [main] - ==> Preparing: select id,role_name roleName,enabled enabled,create_by createBy,create_time createTime from sys_role where id = ?
DEBUG [main] - ==> Parameters: 2(Long)
TRACE [main] - <== Columns: id, roleName, enabled, createBy, createTime
TRACE [main] - <== Row: 2, 普通用户, 1, 1, 2020-06-13 21:15:49
DEBUG [main] - <== Total: 1
一对多结果映射
MyBatis 的 一对多结果映射是通过使用 collection 标签来完成的,collection 标签的属性基本与 association 一致。
下面通过两个示例来看一下:
不使用子查询
<resultMap id="UserRoleUseCollectionTag" type="SysUser" extends="sysUser">
<collection property="roleList" columnPrefix="role_" resultMap="com.mybatis.simple.mapper.SysRoleMapper.RoleEntity">
</collection>
</resultMap>
<select id="userRoleByCollection" resultMap="UserRoleUseCollectionTag">
select
user.id,
user.user_name,
user.user_email,
role.id "role_id",
role.role_name "role_role_name",
role.enabled "role_enabled",
role.create_by "role_createBy"
from sys_user user
inner join sys_user_role userRole on user.id = userRole.user_id
inner join sys_role role on userRole.role_id = role.id
where
1 = 1
<if test="list != null" >
and user.id in
<foreach collection="list" open="(" close=")" separator="," item="userId" >
#{userId}
</foreach>
</if>
</select>
List<SysUser> userRoleByCollection(List<Integer> userId);
然后测试,便可以看到,控制台一共查询出来四条数据;经 MyBatis 的 collection 标签合并处理后,userList.size() 为 2 ,Role 的数据被合并到对应的 user 对象的 roleIist 属性中。
DEBUG [main] - ==> Preparing: select user.id, user.user_name, user.user_email, role.id "role_id", role.role_name "role_role_name", role.enabled "role_enabled", role.create_by "role_createBy" from sys_user user inner join sys_user_role userRole on user.id = userRole.user_id inner join sys_role role on userRole.role_id = role.id where 1 = 1 and user.id in ( ? , ? )
DEBUG [main] - ==> Parameters: 1004(Integer), 1(Integer)
TRACE [main] - <== Columns: id, user_name, user_email, role_id, role_role_name, role_enabled, role_createBy
TRACE [main] - <== Row: 1, admin, admin@mybatis.com, 1, 管理员, 1, 1
TRACE [main] - <== Row: 1, admin, admin@mybatis.com, 2, 普通用户, 1, 1
TRACE [main] - <== Row: 1004, 孙悟空, SWK@xiyouji.com, 1, 管理员, 1, 1
TRACE [main] - <== Row: 1004, 孙悟空, SWK@xiyouji.com, 2, 普通用户, 1, 1
DEBUG [main] - <== Total: 4
userList.size() : 2
上面的示例中,是通过列名映射的方式,来完成一对多关系的映射的;在 collectio 标签使用过程中,其是通过上一级的 resultMap 的 id 标签设置的列的值来确定数据是否一致的,如果 resultMap 中未设置 id 标签,则会去比较上一级的所有的列,如果相同则合并,不同则不合并。
使用子查询
使用 collection 的子查询,需要在原有的基础上做一些改动,将表关系的关联放到子查询的 SQL 中,示例如下:
SysUserMapper.xml
<resultMap id="UserRoleUseCollectionTag2" type="SysUser" extends="sysUser">
<collection property="roleList" column="{userId = id}" select="com.mybatis.simple.mapper.SysRoleMapper.selectByUserId">
</collection>
</resultMap>
<select id="userRoleByCollection2" resultMap="UserRoleUseCollectionTag2">
select
user.id,
user.user_name,
user.user_email
from sys_user user
where
1 = 1
<if test="list != null" >
and user.id in
<foreach collection="list" open="(" close=")" separator="," item="userId" >
#{userId}
</foreach>
</if>
</select>
SysRoleMapper.xml 对应的 SQL 如下:
<select id="selectByUserId" resultType="SysRole">
select
id,
role_name roleName,
enabled enabled,
create_by createBy,
create_time createTime
from sys_role role
inner join sys_user_role userRole on userRole.role_id = role.id
where userRole.user_id = #{userId}
</select>
之后,便可以通过测试代码进行测试。
上面的示例可以得知,在主查询中,不用关心关联子表的问题,只用查询主表,之后通过 collection 标签中设置的查询方法进行联查,将联查操作放到子查询中完成。collection 标签的懒加载属性设置与 association 标签的设置方法相同,这里不做介绍。