《Mybatis从入门到精通》读书笔记(三)
第六章. Mybatis高级查询
在关系型数据库中,我们经常要处理一对一,一对多的关系。在面对这种关系的时候,我们可能要写多个方法分别查询这些数据,然后再组合到一起。这种处理方式特别适合用在大型系统上,由于分库分表,这种用法可以减少表之间的关联查询,方便系统进行扩展。但是在一般的企业应用中,使用Mybatis的高级结果映射便可以轻松地处理这种一对一、一对多的关系。
6.1. 高级结果映射
6.1.1. 一对一映射
1. 使用自动映射处理一对一关系
使用自动映射就是通过别名让Mybatis自动将值匹配到对应的字段上,简单的别名映射如user_name对应userName。除此之外Mybatis还支持复杂的属性映射,可以多层嵌套,例如将role.role_name映射到role.roleName上。Mybatis会先查找role属性,如果存在role属性就创建role对象,然后在role对象中继续查找roleName,将role_name的值绑定到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,
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>
注意上述方法中sys_role查询列的别名都是“role.”前缀,通过这种方式将role的属性都映射到了SysUser的role属性上。
2. 使用resultMap的association标签配置一对一映射
<!-- <cache-ref namespace="tk.mybatis.simple.mapper.RoleMapper"/> -->
<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">
<!-- 2. 直接引用RoleMapper里的resultMap -->
<association property="role" columnPrefix="role_" resultMap="tk.mybatis.simple.mapper.RoleMapper.roleMap"/>
<!-- 1. 直接罗列出来sys_role表字段和SysRole属性的映射关系 -->
<!--<association property="role" 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"/>
</association>-->
</resultMap>
<mapper namespace="tk.mybatis.simple.mapper.RoleMapper">
<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>
</mapper>
association标签包含包含以下属性:
- property:对应实体类中的属性名,必填项
- javaType:属性对应的Java类型
- resultMap:可以直接使用现有的resultMap,而不需要在这里配置
- columnPrefix:查询列的前缀,配置前缀后,在子标签配置result的column时可以省略前缀
3. association标签的嵌套查询
上面介绍的都属于嵌套的结果映射,需要关联多个表将所有需要的值一次性查询出来。这种方式的好处是减少数据库查询次数,减轻数据库的压力,缺点是要写很复杂的SQL,并且当嵌套结果更复杂时,不容易一次写正确,由于要在应用服务器上将结果映射到不同的类上,因此也会增加应用服务器的压力。当一次会使用到嵌套结果,并且整个复杂的SQL执行速度很快时,建议使用关联的嵌套结果映射。
除了这两种通过复杂的SQL查询获取结果,还可以利用简单的SQL通过多次查询转化为我们需要的结果,这种方式与根据业务逻辑手动执行多次SQL的方式很像,最后会将结果组合成一个对象。
association标签的嵌套查询常用的属性如下:
- select:另一个映射查询的id,Mybatis会额外执行这个查询获取嵌套对象的结果。
- column:列名(别名)将住查询中列的结果作为嵌套查询的参数,配置方式如column={prop1=col1,prop2=col2},prop1和prop2将作为嵌套查询的参数。
- fetchType:数据加载方式,可选值为lazy和eager,分别为延迟加载和积极加载,这个配置会覆盖全局的lazyLoadingEnabled配置
UserMapper.xml配置:
<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>
3.1. 实现延迟加载
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 | true | false | false |
aggressiveLazyLoading | 当开启时,任何方法的调用都会使带有延迟加载属性的对象完整加载。当关闭时,每个属性会按需加载 | true | false | false (true in ≤3.4.1) |
lazyLoadTriggerMehods | 指定触发后可以立即加载所有延迟加载数据的方法列表。 | 用逗号分隔的方法列表。 | equals,clone,hashCode,toString |
(1)全局配置(mybatis-config.xml)关闭aggressiveLazyLoading选项:
<settings>
<!-- 其他配置 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
(2)在嵌套查询配置中设置fetchType为懒加载(lazy):
<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>
(3)如果设置为延迟加载后又需要在某种场景下触发加载所有的延迟加载数据,通过调用lazyLoadTriggerMehods参数指定的方法即可!
许多对延迟加载原理不太熟悉的朋友会经常遇到一些莫名其妙的问题:有些时候延迟加载可以得到数据,有些时候延迟加载就会报错,为什么会出现这种情况呢?
Mybaits延迟加载是通过动态代理实现的,当调用配置为延迟加载的属性方法时,动态代理的操作会被触发,这些额外的操作就是通过Mybatis的SqlSession去执行嵌套SQL的。由于在和某些框架集成时,SqlSession的生命周期交给了框架来管理,因此当对象超出SqlSession生命周期调用时,会由于链接关闭等问题而抛出异常。在和Spring集成时,要确保只能在Service层调用延迟加载属性。当结果从Service返回Controller层时,如果获取延迟加载的属性值,会因为SqlSession已经关闭而抛出异常。
6.1.2. 一对多映射
一对多映射只有两种映射方式,都是使用collection标签进行的。
1. collection集合的嵌套结果映射
UserMapper.xml:
<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="userRoleListMap" extends="userMap" type="tk.mybatis.simple.model.SysUser">
<collection property="roleList" columnPrefix="role_"
resultMap="tk.mybatis.simple.mapper.RoleMapper.rolePrivilegeListMap"/>
</resultMap>
<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,
p.id role_privilege_id,
p.privilege_name role_privilege_privilege_name,
p.privilege_url role_privilege_privilege_url
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
inner join sys_role_privilege rp on rp.role_id = r.id
inner join sys_privilege p on p.id = rp.privilege_id
</select>
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"/>
<association property="createInfo" javaType="tk.mybatis.simple.model.CreateInfo">
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</association>
</resultMap>
<resultMap id="rolePrivilegeListMap" extends="roleMap" type="tk.mybatis.simple.model.SysRole">
<collection property="privilegeList" columnPrefix="privilege_"
resultMap="tk.mybatis.simple.mapper.PrivilegeMapper.privilegeMap"/>
</resultMap>
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>
<resultMap/>中<id/>标签的唯一作用就是在嵌套的结果集映射中判断记录是否相同(从而进行每一级对象的合并)。当配置<id/>标签时,Mybatis只需要逐条比较每一级对象的id属性值是否相同即可。在进行嵌套结果集映射时,配置<id/>标签可以提高处理效率。
如果没有<resultMap/>中没有配置<id/>标签,Mybaits就会把<resultMap/>中配置的所有字段进行比较,如果所有的字段值都相同就进行合并,只要有一个字段值不同,就不合并。
在嵌套结果集映射配置id属性时,如果查询语句中没有查询id列,就会导致id属性值为null,这样所有记录的id属性值都相同,因而在合并时所有的记录都相同从而只会保留一条数据(第一条)。所以在resultMap中配置id列时,查询语句中必须包含该列。
Mybaits会对嵌套查询的每一级对象都进行属性比较。Mybaits会首先比较顶层的对象,如果SysUser部分相同,保留一个(第一个),然后继续比较SysRole部分,如果SysRole不同,就会增加一个SysRole,两个SysRole相同就保留前一个。假设SysRole还有下一级,仍然按照该规则去比较。
2. collection集合的嵌套查询
UserMapper.xml:
<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="userRoleListMapSelect" extends="userMap" type="tk.mybatis.simple.model.SysUser">
<collection property="roleList"
fetchType="lazy"
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>
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"/>
<association property="createInfo" javaType="tk.mybatis.simple.model.CreateInfo">
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</association>
</resultMap>
<resultMap id="rolePrivilegeListMapSelect" extends="roleMap" type="tk.mybatis.simple.model.SysRole">
<collection property="privilegeList"
fetchType="lazy"
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>
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>
6.1.3. 鉴别器映射
待续...
6.2. 存储过程
待续...