MyBatis 07-高级查询
http://www.broadview.com.cn/book/3643
https://github.com/mybatis-book/book
一对一映射
使用自动映射处理一对一关系
假设一个用户只有一个角色
public class SysUser {
private Long id;
private String userName;
private String userPassword;
private String userEmail;
/**
* 用户角色
*/
private SysRole role;
// ...
}
MyBatis 支持多层嵌套,role.roleName 将会绑定到 role 对象的 roleName 属性上。
<select id="selectUserAndRoleById" resultType="tk.mybatis.simple.model.SysUser">
select
u.id,
u.user_name userName,
u.user_password userPassword,
u.user_email userEmail,
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>
使用 resultMap 配置一对一映射
<resultMap id="userMap" type="tk.mybatis.simple.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"/>
<!-- role 部分,避免列名重复,可能重复的列名增加了 "role_" 前缀,并在 SQL 中设置别名 -->
<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,
r.id role_id,
r.role_name role_name,
r.enabled enabled,
r.create_by 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>
相比自动映射,这样会更麻烦。
可简单的部分,MyBatis 支持 resultMap 映射继承的。
<resultMap id="userMap" type="tk.mybatis.simple.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="tk.mybatis.simple.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>
使用 resultMap 的 association 标签配置一对一映射
修改上面的配置
<resultMap id="userRoleMap" extends="userMap" type="tk.mybatis.simple.model.SysUser">
<association property="role" columnPrefix="role_" javaType="tk.mybatis.simple.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>
association 标签包含以下属性:
- property:对应实体类中的属性名
- javaType:属性对应的 Java 类型
- resultMap:可以直接使用现有的 resultMap,而不需要在这里配置
- columnPrefix:查询列的前缀,配置前缀后,在子标签配置 result 的 column 时可以省略前缀
<!-- 将 sys_role 相关所有列的别名修改为 "role_" 前缀 -->
<select id="selectUserAndRoleById2" resultMap="userRoleMap">
select
u.id,
u.user_name,
u.user_password,
u.user_email,
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>
使用 association
配置 resultMap 属性配置一个已经存在的 resultMap 映射。
<!-- 这个应该放到 RoleMapper.xml 更合理 -->
<resultMap id="roleMap" type="tk.mybatis.simple.model.SysRole">
<id 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 中,所有需要加上 roleMap 的命名空间 -->
<resultMap id="userRoleMap" extends="userMap" type="tk.mybatis.simple.model.SysUser">
<association property="role" columnPrefix="role_" resultMap="tk.mybatis.simple.mapper.RoleMapper.roleMap"/>
</resultMap>
association 标签的嵌套查询
association 标签的嵌套查询常用的属性如下:
select
:另一个映射查询的 id,MyBatis 会额外执行这个查询获取嵌套对象的结果column
:列名,将主查询中列的结果作为嵌套查询的参数,配置方式:column={prop1=col1, prop2=col2}
,prop1,prop2 将作为嵌套查询的参数fetchType
:数据加载方式,可选值为lazy
和eager
,分别为延迟加载和积极加载,将覆盖全局配置lazyLoadingEnabled
<resultMap id="userRoleMapSelect" extends="userMap" type="tk.mybatis.simple.model.SysUser">
<association property="role" fetchType="lazy"
select="tk.mybatis.simple.mapper.RoleMapper.selectRoleById"
column="{id=role_id}"/>
</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,
ur.role_id
from sys_user u
inner join sys_user_role ur on u.id = ur.user_id
where u.id = #{id}
</select>
<!-- RoleMapper.xml -->
<select id="selectRoleById" resultMap="roleMap">
select * from sys_role where id = #{id}
</select>
嵌套查询会多执行 SQL,查询出来的 N 条数据,就会执行 N+1 次查询,设置 fetchType="lazy"
,这样只有调用 getRole() 方法时,才会去执行嵌套查询。
另外还需设置 aggressiveLazyLoading
才会生效,在 mybatis-config.xml 中添加配置:
<settings>
<!-- 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
在和 spring 集成时,要确保只能在 Service 层调用延迟加载的属性,因为在 Control 层获取延迟加载的属性值,会因为 SqlSession 已经关闭而报错。
另外 MyBatis 还提供了 lazyLoadTriggerMethods
参数,指定哪个对象的方法触发一次延迟加载,默认值:equals,clone,hashCode,toString
。如执行了 equals 方法就会加载数据。
一对多映射
collection 集合的嵌套结果映射
和 association 类似,集合的嵌套结果映射就是指通过一次 SQL 查询将所有的结果查询出来,然后通过配置的结果映射,将数据映射到不同的对象中去。
public class SysUser {
// 原有属性
private List<SysRole> roleList;
// setter getter
}
<resultMap id="userRoleListMap" extends="userMap" type="tk.mybatis.simple.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"/>
<collection property="roleList" columnPrefix="role_" javaType="tk.mybatis.simple.model.SysRole">
<id 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"/>
</collection>
</resultMap>
只是把上文 [使用 resultMap 的 association 标签配置一对一映射] 中 association
改成 collection
。
collection 支持的属性以及属性的作用和 association 完全相同。
<!-- 使用 extends,简化 resultMap -->
<resultMap id="userRoleListMap" extends="userMap" type="tk.mybatis.simple.model.SysUser">
<collection property="roleList" columnPrefix="role_"
resultMap="tk.mybatis.simple.mapper.RoleMapper.roleMap"/>
</resultMap>
仿照 selectUserAndRoleById2 方法,创建 selectAllUserAndRoles
<select id="selectAllUserAndRoles" resultMap="userRoleListMap">
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
</select>
admin 用户,对应角色:管理员,普通用户
test 用户,对应角色:普通用户
SQL 执行结果数有 3 条,通过 selectAllUserAndRoles 查询出来用户数是 2。
MyBatis 在处理结果的时候,会判断结果是否相同,如果是相同的结果,则只会保留第一个结果。MyBatis 判断结果是否相同时,最简单的情况就是在映射配置中至少有一个 id 标签,在 userMap 中配置如下:
<id property='id' column='id'>
当配置 id 标签时,MyBatis 只需要逐条比较所以数据中 id 标签配置的字段值是否相同即可。在配置嵌套结果查询时,配置 id 标签可以提高处理效率。
在嵌套结果配置 id 属性时,如果查询语句中没有查询 id 属性配置的列,会导致 id 对应的值未 null,所有值 id 都相同,会使嵌套集合中只有一条数据。所以查询语句中必须包含该列。
如果将 id 标签改为 result,MyBatis 会对所有字段进行比较,当字段数为 M,查询结果为 N 条时,需要进行 M*N 次比较。
如果要配置一个相当复杂的映射,一定要从基础映射开始配置,每增加一些配置就进行对应的测试,在循序渐进的过程中更容易发现和解决问题。
collection 集合的嵌套查询
和 association 相似
有一个新的 ofType
属性。这个属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。
<!-- PrivilegeMapper.xml -->
<resultMap id="privilegeMap" type="tk.mybatis.simple.model.SysPrivilege">
<id property="id" column="id"/>
<result property="privilegeName" column="privilege_name"/>
<result property="privilegeUrl" column="privilege_url"/>
</resultMap>
<select id="selectPrivilegeByRoleId" resultMap="privilegeMap">
select p.*
from sys_privilege p
inner join sys_role_privilege rp on rp.privilege_id = p.id
where role_id = #{roleId}
</select>
<!-- RoleMapper.xml collection 中的 javaType 和 ofType 可以省略-->
<resultMap id="rolePrivilegeListMapSelect" extends="roleMap" type="tk.mybatis.simple.model.SysRole">
<collection property="privilegeList"
fetchType="lazy"
ofType="tk.mybatis.simple.model.SysPrivilege"
javaType="ArrayList"
select="tk.mybatis.simple.mapper.PrivilegeMapper.selectPrivilegeByRoleId"
column="{roleId=id}"/>
</resultMap>
<select id="selectRoleByUserId" resultMap="rolePrivilegeListMapSelect">
select
r.id, r.role_name, r.enabled,
r.create_by, r.create_time
from sys_role r
inner join sys_user_role ur on ur.role_id = r.id
where ur.user_id = #{userId}
</select>
<!-- UserMapper.xml -->
<resultMap id="userRoleListMapSelect" extends="userMap" type="tk.mybatis.simple.model.SysUser">
<collection property="roleList"
fetchType="lazy"
ofType="tk.mybatis.simple.model.SysRole"
select="tk.mybatis.simple.mapper.RoleMapper.selectRoleByUserId"
column="{userId=id}"/>
</resultMap>
<select id="selectAllUserAndRolesSelect" resultMap="userRoleListMapSelect">
select
u.id, u.user_name, u.user_password,
u.user_email, u.user_info,
u.head_img, u.create_time
from sys_user u
where u.id = #{id}
</select>
鉴别器映射
鉴别器很像 Java 中的 switch 语句
discriminator 标签常用的两个属性:
- column:该属性用于设置要进行鉴别比较值的列
- javaType:该属性用于指定列的类型,保证使用相同的 Java 类型来比较值。
discriminator 标签可以有 1 个或者多个 case 标签,case 标签包含以下三个属性:
- value:该值未 discriminator 指定 column 用来匹配的值。
- resultMap:当 column 的值和 value 的值匹配时,可以配置使用 resultMap 指定的映射,resultMap 优先级 高于 resultType。
- resultType:当 column 的值和 value 的值匹配时,用于配置使用 resultType 指定的映射。
<resultMap id="rolePrivilegeListMapChoose" type="tk.mybatis.simple.model.SysRole">
<discriminator column="enabled" javaType="int">
<case value="1" resultMap="rolePrivilegeListMapSelect"/>
<case value="0" resultMap="roleMap"/>
</discriminator>
</resultMap>
角色的属性 enable 值为 1 的时候表示状态可用,为 0 不可用。当不可用时,只能获取角色的基本信息,不能获得角色的权限信息。
<select id="selectRoleByUserIdChoose" resultMap="rolePrivilegeListMapChoose">
select
r.id, r.role_name, r.enabled,
r.create_by, r.create_time
from sys_role r
inner join sys_user_role ur on ur.role_id = r.id
where ur.user_id = #{userId}
</select>
鉴别器特殊的地方
<resultMap id="rolePrivilegeListMapChoose" type="tk.mybatis.simple.model.SysRole">
<discriminator column="enabled" javaType="int">
<case value="1" resultMap="rolePrivilegeListMapSelect"/>
<case value="0" resultType="SysRole">
<id property="id" column="id"/>
<result property="roleName" column="role_name"/>
</case>
</discriminator>
</resultMap>
使用 resultType,并在 case 中配置了两个属性的映射,这时 MyBatis 只会对列举出来的配置进行映射,不像使用 resultMap 配置时会自动映射其他的字段。
鉴别器很少使用,尽可能避免使用。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:MyBatis 07-高级查询
文章字数:3.2k
本文作者:Bin
发布时间:2019-11-17, 21:04:43
最后更新:2019-12-03, 20:27:57
原始链接:http://coolview.github.io/2019/11/17/MyBatis/MyBatis%2007-%E9%AB%98%E7%BA%A7%E6%9F%A5%E8%AF%A2/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。