MyBatis学习——高级结果映射

前面我们讲解了一下关于MyBatis的动态SQL查询,对MyBatis的基本用法有了大致的了解,如果忘记了可以去复习一下,MyBatis学习——动态SQL。今天我们将讲解一下MyBatis的高级查询知识点。

前言:在关系型数据库中,我们经常要处理一对一 、一对多的关系。在面对这种关系的时候,我们可能要写多个方法分别查询这些数据,然后再组合到一起。这种处理方式特别适合用在大型系统上,由于分库分表,这种用法可以减少表之间的关联查询,方便系统进行扩展。但是在一般的企业级应用中,使用MyBatis的高级结果映射便可以轻松地处理这种一对一 、 一对多的关系。

比如,现在我有两张表,一张表示用户相关信息,另一张表示用户角色相关信息,一个用户可以对应多个角色。

用户表:
MyBatis学习——高级结果映射_第1张图片

角色表:
MyBatis学习——高级结果映射_第2张图片

下面的内容我们将根据这两张表用户与角色的内容进行讲解。

一、一对一映射

1、使用自动映射处理一对一关系(对象与数据库字段映射)

比如,这里我们使用了role表的查询,并关联user表,将我们需要的结果全部都查询出来,然后再在对应的实体中进行映射,所以在User实体当中,我们对role进行映射。查询如下:

 

我们在User实体中对Role进行映射。如下:
MyBatis学习——高级结果映射_第3张图片
当我们查询一对一的关联数据时,就可以自动映射到role实体上去。MyBatis还支持复杂的属性映射 , 可以多层嵌套。 例如,将 role.role 映射到 role.role上。 MyBatis 会先查找role属性,如果存在role属性就创建 role对象, 然后在role对象中继续查找role,将role的值绑定到role对象的role属性上。

2、使用resultMap配置一对一映射

在上面我们直接通过在实体中进行了映射,除此之外,我们还可以在resultMap中进行配置,对多个不同的属性进行配置。

    
        
        
        
        
        
        
        
        
        
    

同时因为MyBatis是支持resultMap映射继承的, 因此要先简化上面的resultMap配置。我们可以把角色抽出来单独作为一个resultMap,然后继承(在resultMap中使用extends标签继承)用户属性的resultMap即可。由于这个内容比较简单,这里就不过多叙述。

3、使用resultMap的association标签配置一对一映射

在resultMap中,assocation标签用于和一个复杂的类型进行关联, 即用于一对一的关联配置。

assocation标签包含以下属性:

  • property:对应实体类中的属性名,必填项。
  • javaType:属性对应的Java类型。
  • resultMap:可以直接使用现有的resultMap,而不需要在这里配置。
  • columPrefix:查询列的前缀,配置前缀后,在子标签result的colum时可以省略前缀。

因此上面的resultMap可以更改如下:

 
        
            
            
            
        
    

注意:这里是带表前缀的,所以在查出的结果对应列中需要加入role_前缀。

同时我们还可以进一步精简,如下:

    
        
        
        
    


	
        
    
4、association标签的嵌套查询

association标签的嵌套查询常用的属性如下:

  • select:另一个映射查询的id, MyBatis会额外执行这个查询获取嵌套对象的结果。
  • column:列名(或别名),将主查询中列的结果作为嵌套查询的参数,配置方式如column={propl=coll,prop2=col2}, propl和prop2将作为嵌套查询的参数。
  • fetchType:数据加载方式,可选值为lazy和eager ,分别为延迟加载和积极加载,这个配置会覆盖全局的 lazyLoadingEnabled配置。

比如下面的查询结果配置,同时我们也在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 ” 。

二、一对多映射

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 roleList;

   // set/get方法
    ...
}

然后我们添加关联查询,如下:

   
        
   
    
    
   

接着在UserDao中添加selectAllUserAndRoles方法。

SQL执行的结果数有 3 条, 后面输出的用户数是 2 , 本来查询出的3条结果经过MyBatis对collection数据的处理后,变成了两条。

collection

因为这里使用了collection,它会把集合的数据合并。那么MyBatis的合并规则如何呢?

MyBatis 判断结果是否相同时, 最简单的情况就是在映射配置中 至少有一个id标签 , 在userMap中配置如下。


我们对 id (构造方法中为 idArg )的理解一般是 , 它配置的字段为表的主键(联合主键时可以配置多个 id 标签), 因为MyBatis的 resultMap 只用于配置结果如何映射 , 并不知道这个表具体如何。 id 的唯一作用就是 在嵌套的映射配置时判断数据是否相同, 当配置 id 标签时, MyBatis只需要逐条比较所有数据中 id 标签配置的字段值是否相同即可。 在配置嵌套结果查询时 , 配置 id 标签可以提高处理效率。

因为前两条数据的userMap部分的 id 相同 , 所以它们属于同一个用户, 因此这条数据会合并到同一个用户中。

虽然 association 和 collection 标签是分开介绍的 , 但是这两者可以组合使用或者互相嵌套使用 , 也可以使用 符合自己需要的任何数据结 构, 不需要局限于数据库表之间的关联关系 。

2、collection集合的嵌套查询

集合的嵌套查询也association类似,这里不过多讲述,这是大致提示一下。

同样的我们添加相关查询,如下:

    
        
    
    
    

同理,我们在RoleDao.xml中配置selectRoleByUserId的查询,如下:


同理因为所有嵌套查询都 配置为延迟加载 , 因此不存在 N+1 的问题 。

三、鉴别器映射

有时一个单独的数据库查询会返回很多不同数据类型(希望有些关联)的结果集。

鉴别器标签就是用来处理这种情况的。

discriminator标签常用的两个属性:

  • column:该属性用于设置要进行鉴别比较值的列。
  • javaType:该属性用于指定列的类型, 保证使用相同的Java类型来比较值。

discriminator标签可以有1个或多个case标签,case标签包含以下三个属性:

  • value:该值为discriminator指定column用来匹配的值。
  • resultMap:当column的值和value的值匹配时,可以配置使用resultMap指定的映射, resultMap优先级高于resultType。
  • resultType:当column的值和value的值匹配时,用于配置使用resultType指定的映射。

    
        
        
    

比如,像上面的enabled为不同的值时会根据情况返回不同的resultMap作为结果。

在discriminator中也可以使用javaType返回属性。


    
        
        
         
         
        
    

鉴别器是一种很少使用的方式 , 在使用前一定要完全掌握, 没有把握 的情况下要尽可能避免使用。

关于MyBatis的高级结果映射的介绍到此为止,更多详细的用法可以去查阅相关文档。

上面的代码示例:mybatis-demo

参考

  • 《MyBatis从入门到精通》

你可能感兴趣的:(MyBatis)