前面我们讲解了一下关于MyBatis的动态SQL查询,对MyBatis的基本用法有了大致的了解,如果忘记了可以去复习一下,MyBatis学习——动态SQL。今天我们将讲解一下MyBatis的高级查询知识点。
前言:在关系型数据库中,我们经常要处理一对一 、一对多的关系。在面对这种关系的时候,我们可能要写多个方法分别查询这些数据,然后再组合到一起。这种处理方式特别适合用在大型系统上,由于分库分表,这种用法可以减少表之间的关联查询,方便系统进行扩展。但是在一般的企业级应用中,使用MyBatis的高级结果映射便可以轻松地处理这种一对一 、 一对多的关系。
比如,现在我有两张表,一张表示用户相关信息,另一张表示用户角色相关信息,一个用户可以对应多个角色。
下面的内容我们将根据这两张表用户与角色的内容进行讲解。
比如,这里我们使用了role表的查询,并关联user表,将我们需要的结果全部都查询出来,然后再在对应的实体中进行映射,所以在User实体当中,我们对role进行映射。查询如下:
我们在User实体中对Role进行映射。如下:
当我们查询一对一的关联数据时,就可以自动映射到role实体上去。MyBatis还支持复杂的属性映射 , 可以多层嵌套。 例如,将 role.role 映射到 role.role上。 MyBatis 会先查找role属性,如果存在role属性就创建 role对象, 然后在role对象中继续查找role,将role的值绑定到role对象的role属性上。
在上面我们直接通过在实体中进行了映射,除此之外,我们还可以在resultMap中进行配置,对多个不同的属性进行配置。
同时因为MyBatis是支持resultMap映射继承的, 因此要先简化上面的resultMap配置。我们可以把角色抽出来单独作为一个resultMap,然后继承(在resultMap中使用extends标签继承)用户属性的resultMap即可。由于这个内容比较简单,这里就不过多叙述。
在resultMap中,assocation标签用于和一个复杂的类型进行关联, 即用于一对一的关联配置。
assocation标签包含以下属性:
因此上面的resultMap可以更改如下:
注意:这里是带表前缀的,所以在查出的结果对应列中需要加入role_前缀。
同时我们还可以进一步精简,如下:
association标签的嵌套查询常用的属性如下:
比如下面的查询结果配置,同时我们也在select中配置了子查询。
子查询在RoleDao.xml配置,如下:
因为第一个SQL的查询结果只有一条,role.id关联了另一个查询,因此执行了两次SQL。
如果查询的不是1条数据,而是N条数据,那就会出现N+1问题,主SQL会查询一次,查询出N条结果, 这N条结果要各自执行一次查询,那就需要进行N次查询。 如何解决这个问题呢?
在上面介绍association标签的属性时, 介绍了fetchType数据加载方式, 这个方式可以帮我们实现延迟加载,解决N+1的问题。
在 MyBatis 的全局配置中, 有一个参数为 aggressiveLazyLoading 。这个参数的含义是,当该参数设置为 true时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载,反之, 每种属性都将按需加载。
因此当我们使用MyBatis懒加载时,需要配置aggressiveLazyLoading为false。如下:
按照上面的介绍,需要把fetchType设置为lazy(这里设置lazy,相当于把lazyLoadingEnabled设置为true),这样设置后,只有当调用getRole()方法获取role的时候, MyBatis才会执行嵌套查询去获取数据。
但是有些时候还是需要在触发某方法时将所有的数据都加载进来 , 而我们己经将aggressiveLazyLoading设置为 false ,这种情况又该怎么解决呢?
MyBatis 仍然提供了参数 lazyLoadTriggerMethods 帮助解决这个问题, 这个参数的含义是, 当调用配置中的方法时, 加载全部的延迟加载数据。 默认值为” equals , clone ,hashCode ,toString ” 。
在前文我们介绍一对一的映射关系中,有多种实现方式,但是一对多的实现方式只有一种方式————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 roleList;
// set/get方法
...
}
然后我们添加关联查询,如下:
接着在UserDao中添加selectAllUserAndRoles方法。
SQL执行的结果数有 3 条, 后面输出的用户数是 2 , 本来查询出的3条结果经过MyBatis对collection数据的处理后,变成了两条。
因为这里使用了collection,它会把集合的数据合并。那么MyBatis的合并规则如何呢?
MyBatis 判断结果是否相同时, 最简单的情况就是在映射配置中 至少有一个id标签 , 在userMap中配置如下。
我们对 id (构造方法中为 idArg )的理解一般是 , 它配置的字段为表的主键(联合主键时可以配置多个 id 标签), 因为MyBatis的 resultMap 只用于配置结果如何映射 , 并不知道这个表具体如何。 id 的唯一作用就是 在嵌套的映射配置时判断数据是否相同, 当配置 id 标签时, MyBatis只需要逐条比较所有数据中 id 标签配置的字段值是否相同即可。 在配置嵌套结果查询时 , 配置 id 标签可以提高处理效率。
因为前两条数据的userMap部分的 id 相同 , 所以它们属于同一个用户, 因此这条数据会合并到同一个用户中。
虽然 association 和 collection 标签是分开介绍的 , 但是这两者可以组合使用或者互相嵌套使用 , 也可以使用 符合自己需要的任何数据结 构, 不需要局限于数据库表之间的关联关系 。
集合的嵌套查询也association类似,这里不过多讲述,这是大致提示一下。
同样的我们添加相关查询,如下:
同理,我们在RoleDao.xml中配置selectRoleByUserId的查询,如下:
同理因为所有嵌套查询都 配置为延迟加载 , 因此不存在 N+1 的问题 。
有时一个单独的数据库查询会返回很多不同数据类型(希望有些关联)的结果集。
鉴别器标签就是用来处理这种情况的。
discriminator标签常用的两个属性:
discriminator标签可以有1个或多个case标签,case标签包含以下三个属性:
比如,像上面的enabled为不同的值时会根据情况返回不同的resultMap作为结果。
在discriminator中也可以使用javaType返回属性。
鉴别器是一种很少使用的方式 , 在使用前一定要完全掌握, 没有把握 的情况下要尽可能避免使用。
关于MyBatis的高级结果映射的介绍到此为止,更多详细的用法可以去查阅相关文档。
上面的代码示例:mybatis-demo