概述
前面我们讲解了一下关于MyBatis的动态SQL查询,对MyBatis的基本用法有了大致的了解,如果忘记了可以去复习一下,MyBatis学习——动态SQL。今天我们将讲解一下MyBatis的高级查询知识点。
前言:在关系型数据库中,我们经常要处理一对一 、一对多的关系。在面对这种关系的时候,我们可能要写多个方法分别查询这些数据,然后再组合到一起。这种处理方式特别适合用在大型系统上,由于分库分表,这种用法可以减少表之间的关联查询,方便系统进行扩展。但是在一般的企业级应用中,使用MyBatis的高级结果映射便可以轻松地处理这种一对一 、 一对多的关系。
比如,现在我有两张表,一张表示用户相关信息,另一张表示用户角色相关信息,一个用户可以对应多个角色。
用户表:
角色表:
下面的内容我们将根据这两张表用户与角色的内容进行讲解。
一、一对一映射
1、使用自动映射处理一对一关系(对象与数据库字段映射)
比如,这里我们使用了role表的查询,并关联user表,将我们需要的结果全部都查询出来,然后再在对应的实体中进行映射,所以在User实体当中,我们对role进行映射。查询如下:
<select id="selectUserAndRoleById" resultType="User">
select
u.*,
r.id "role.id",
r.role "role.role",
r.enabled "role.enabled"
from user u
inner join role r on u.id = r.user_id
where u.id = #{id}
</select>
我们在User实体中对Role进行映射。如下:
当我们查询一对一的关联数据时,就可以自动映射到role实体上去。MyBatis还支持复杂的属性映射 , 可以多层嵌套。 例如,将 role.role 映射到 role.role上。 MyBatis 会先查找role属性,如果存在role属性就创建 role对象, 然后在role对象中继续查找role,将role的值绑定到role对象的role属性上。
2、使用resultMap配置一对一映射
在上面我们直接通过在实体中进行了映射,除此之外,我们还可以在resultMap中进行配置,对多个不同的属性进行配置。
<resultMap id="userMap" type="net.anumbrella.mybatis.entity.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="email" column="email"/>
<result property="expried" column="expried"/>
<result property="disabled" column="disabled"/>
<result property="role.id" column="role.id"/>
<result property="role.role" column="role.role"/>
<result property="role.enabled" column="role.enabled"/>
</resultMap>
同时因为MyBatis是支持resultMap映射继承的, 因此要先简化上面的resultMap配置。我们可以把角色抽出来单独作为一个resultMap,然后继承(在resultMap中使用extends标签继承)用户属性的resultMap即可。由于这个内容比较简单,这里就不过多叙述。
3、使用resultMap的association标签配置一对一映射
在resultMap中,assocation标签用于和一个复杂的类型进行关联, 即用于一对一的关联配置。
assocation标签包含以下属性:
- property:对应实体类中的属性名,必填项。
- javaType:属性对应的Java类型。
- resultMap:可以直接使用现有的resultMap,而不需要在这里配置。
- columPrefix:查询列的前缀,配置前缀后,在子标签result的colum时可以省略前缀。
因此上面的resultMap可以更改如下:
<resultMap id="userRoleMap" extends="userMap" type="net.anumbrella.mybatis.entity.User">
<association property="role" columnPrefix="role_" javaType="net.anumbrella.mybatis.entity.Role">
<result property="role.id" column="role.id"/>
<result property="role.role" column="role.role"/>
<result property="role.enabled" column="role.enabled"/>
</association>
</resultMap>
注意:这里是带表前缀的,所以在查出的结果对应列中需要加入role_前缀。
同时我们还可以进一步精简,如下:
<resultMap id="roleMap" type="net.anumbrella.mybatis.entity.Role">
<id property="id" column="id"/>
<result property="role" column="role"/>
<result property="enabled" column="enabled"/>
</resultMap>
<resultMap id="userRoleMap" extends="userMap" type="net.anumbrella.mybatis.entity.User">
<association property="role" columnPrefix="role_" resultMap="net.anumbrella.mybatis.dao.RoleDao.roleMap"/>
</resultMap>
4、association标签的嵌套查询
association标签的嵌套查询常用的属性如下:
- select:另一个映射查询的id, MyBatis会额外执行这个查询获取嵌套对象的结果。
- column:列名(或别名),将主查询中列的结果作为嵌套查询的参数,配置方式如column={propl=coll,prop2=col2}, propl和prop2将作为嵌套查询的参数。
- fetchType:数据加载方式,可选值为lazy和eager ,分别为延迟加载和积极加载,这个配置会覆盖全局的 lazyLoadingEnabled配置。
比如下面的查询结果配置,同时我们也在select中配置了子查询。
<resultMap id="userRoleMapSelect" extends="userMap" type="net.anumbrella.mybatis.entity.User">
<association property="role"
fetchType="lazy"
select="net.anumbrella.mybatis.dao.RoleDao.selectRoleById"
column="{id=role.id}"/>
</resultMap>
子查询在RoleDao.xml配置,如下:
<select id="selectRoleById" resultMap="roleMap">
select * from role where id = #{id}
</select>
因为第一个SQL的查询结果只有一条,role.id关联了另一个查询,因此执行了两次SQL。
如果查询的不是1条数据,而是N条数据,那就会出现N+1问题,主SQL会查询一次,查询出N条结果, 这N条结果要各自执行一次查询,那就需要进行N次查询。 如何解决这个问题呢?
在上面介绍association标签的属性时, 介绍了fetchType数据加载方式, 这个方式可以帮我们实现延迟加载,解决N+1的问题。
在 MyBatis 的全局配置中, 有一个参数为 aggressiveLazyLoading 。这个参数的含义是,当该参数设置为 true时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载,反之, 每种属性都将按需加载。
因此当我们使用MyBatis懒加载时,需要配置aggressiveLazyLoading为false。如下:
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
</configuration>
按照上面的介绍,需要把fetchType设置为lazy(这里设置lazy,相当于把lazyLoadingEnabled设置为true),这样设置后,只有当调用getRole()方法获取role的时候, MyBatis才会执行嵌套查询去获取数据。
但是有些时候还是需要在触发某方法时将所有的数据都加载进来 , 而我们己经将aggressiveLazyLoading设置为 false ,这种情况又该怎么解决呢?
MyBatis 仍然提供了参数 lazyLoadTriggerMethods 帮助解决这个问题, 这个参数的含义是, 当调用配置中的方法时, 加载全部的延迟加载数据。 默认值为” equals , clone ,hashCode ,toString ” 。
二、一对多映射
1、collection集合的嵌套结果映射
在前文我们介绍一对一的映射关系中,有多种实现方式,但是一对多的实现方式只有一种方式————collection。
和association类似集合的嵌套结果映射就是指通过一次SQL查询将所有的结果查询出来, 然后通过配置的结果映射, 将数据映射到不同的对象中去。
比如,我们在User实体中添加roleList对象,同时添加set/get方法。
public class User implements Serializable{
private Long id;
private String username;
private String password;
private Integer expired;
private Integer disabled;
private String email;
private Role role;
private List<Role> roleList;
// set/get方法
...
}
然后我们添加关联查询,如下:
<resultMap id="userRoleListMap" extends="userMap" type="net.anumbrella.mybatis.entity.User">
<collection property="roleList" columnPrefix="role_"
resultMap="net.anumbrella.mybatis.dao.RoleDao.roleMap"/>
</resultMap>
<select id="selectAllUserAndRoles" resultMap="userRoleListMap">
select
u.*,
r.id "role_id",
r.role "role_role",
r.enabled "role_enabled"
from user u
inner join role r on u.id = r.user_id
</select>
接着在UserDao中添加selectAllUserAndRoles方法。
SQL执行的结果数有 3 条, 后面输出的用户数是 2 , 本来查询出的3条结果经过MyBatis对collection数据的处理后,变成了两条。
因为这里使用了collection,它会把集合的数据合并。那么MyBatis的合并规则如何呢?
MyBatis 判断结果是否相同时, 最简单的情况就是在映射配置中 至少有一个id标签 , 在userMap中配置如下。
<id property =” id ” column=” id ” />
我们对 id (构造方法中为 idArg )的理解一般是 , 它配置的字段为表的主键(联合主键时可以配置多个 id 标签), 因为MyBatis的 resultMap 只用于配置结果如何映射 , 并不知道这个表具体如何。 id 的唯一作用就是 在嵌套的映射配置时判断数据是否相同, 当配置 id 标签时, MyBatis只需要逐条比较所有数据中 id 标签配置的字段值是否相同即可。 在配置嵌套结果查询时 , 配置 id 标签可以提高处理效率。
因为前两条数据的userMap部分的 id 相同 , 所以它们属于同一个用户, 因此这条数据会合并到同一个用户中。
虽然 association 和 collection 标签是分开介绍的 , 但是这两者可以组合使用或者互相嵌套使用 , 也可以使用 符合自己需要的任何数据结 构, 不需要局限于数据库表之间的关联关系 。
2、collection集合的嵌套查询
集合的嵌套查询也association类似,这里不过多讲述,这是大致提示一下。
同样的我们添加相关查询,如下:
<resultMap id="userRoleListMapSelect" extends="userMap" type="net.anumbrella.mybatis.entity.User">
<collection property="roleList"
fetchType="lazy"
select="net.anumbrella.mybatis.dao.RoleDao.selectRoleByUserId"
column="{userId=id}"/>
</resultMap>
<select id="selectAllUserAndRolesSelect" resultMap="userRoleListMapSelect">
select
u.id,
u.username,
u.password,
u.email,
u.expired,
u.disabled
from user u
where u.id = #{id}
</select>
同理,我们在RoleDao.xml中配置selectRoleByUserId的查询,如下:
<select id="selectRoleByUserId" resultMap="roleMap">
select * from role where user_id = #{userId}
</select>
同理因为所有嵌套查询都 配置为延迟加载 , 因此不存在 N+1 的问题 。
三、鉴别器映射
有时一个单独的数据库查询会返回很多不同数据类型(希望有些关联)的结果集。
鉴别器标签就是用来处理这种情况的。
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="net.anumbrella.mybatis.entity.Role">
<discriminator column="enabled" javaType="int">
<case value="1" resultMap="rolePrivilegeListMapSelect"/>
<case value="0" resultMap="roleMap"/>
</discriminator>
</resultMap>
比如,像上面的enabled为不同的值时会根据情况返回不同的resultMap作为结果。
在discriminator中也可以使用javaType返回属性。
<resultMap id="rolePrivilegeListMapChoose" type="net.anumbrella.mybatis.entity.Role">
<discriminator column="enabled" javaType="int">
<case value="1" resultMap="rolePrivilegeListMapSelect"/>
<case value="0" resultMap="roleMap"/>
<case value="0" resultType="net.anumbrella.mybatis.entity.Role>
<id property ="id" column=“id” />
<result property ="role" column="role" />
</case>
</discriminator>
</resultMap>
鉴别器是一种很少使用的方式 , 在使用前一定要完全掌握, 没有把握 的情况下要尽可能避免使用。
关于MyBatis的高级结果映射的介绍到此为止,更多详细的用法可以去查阅相关文档。
上面的代码示例:mybatis-demo
参考
- 《MyBatis从入门到精通》
最后
以上就是风中跳跳糖为你收集整理的MyBatis学习——高级结果映射的全部内容,希望文章能够帮你解决MyBatis学习——高级结果映射所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复