JAVA

Mybatis的< resultMap>中 < id> 标签的作用

resultMap的作用


<resultMap>标签用于封装sql的查询结果,可以包装成一个简单POJO对象,也可以包装成我们自定义的对象,只要我们使用<result>子标签指定好查询结果的列和对象的属性之间的对应关系就好了。

官方原因


<id property="id" column="id"/>

是用于提高性能,但是在一些情况下,没有Id的话结果会出错。

我们对 id标签的理解是,它配置的字段为表的主键(联合主键时可以配置多个 id 标签),因为myBatis 中resultMap 只用于配置结果如何映射,并不知道这个表具体如何。 id 的唯一作用就是在嵌套的映射配置中判断数据是否相同,当配置id标签时, Mybatis只需要逐条比较所有数据中 id 标签配置的字段值是否相同即可。在配置嵌套结果查询时,配置 id 标签可以提高处理效率。 这样一来,如果有两条数据 userMap 部分的id相同 所以它们属于同一个用户,因此这条数据会合并到同一个用户中。id标签的设置还是很有必要的,如果仅由resultMap标签完成,当存在多条数据结果是,查询时间会十分的长,这时id标签设置就显得十分的有必要。

例子1


id是resultMap以及Collection的子标签,标记出作为 ID 的结果可以帮助提高整体性能。特别注意的是,id是当前命名空间中的一个唯一标识,用于标识一个结果映射。

如下图,itemId(商品id)字段值在数据库中不唯一,错误使用会导致只返回该订单某商品的一条记录。因为对于某个商品,麻辣味和五香味只是商品规格,其商品id是相同的。

例子2


MyBatis的resultMap只用于配置结果如何映射,id的唯一作用就是在嵌套的映射配置时判断数据是否相同,当配置id标签时,MyBatis只需要逐条比较所有数据中id标签字段值是否相同即可,可以提高处理效率。
为了更清楚地理解id 的作用,可以临时对userMap 的映射进行如下修改。

<resultMap id="userMap" type="test.mybatis.simple.model.SysUser">
<id property="userPassword" column="user_password"/>
<result property="userName" column="user_name"></result>
<result property="id" column="id"></result>
<result property="userEmail" column="user_email"></result>
<result property="userInfo" column="user_info"></result>
<result property="headImg" column="head_img" jdbcType="BLOB"></result>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"></result>
</resultMap>
<resultMap id="userRoleListMap" extends="userMap" type="SysUser">
<collection property="roleList" columnPrefix="role_" resultMap="test.mybatis.simple.mapper.RoleMapper.roleMap"/>
</resultMap>

在测试数据中,用户的密码都是123456 ,因此如果用密码作为id,所有结果将合并为1条数据。

用户数: 1
用户名:admin
角色名:管理员
角色名:普通用户

对比之前的正确结果,可以看到角色记录也合并了

用户数:2
用户名:admin
角色名称:管理员
角色名称:普通用户
用户名:test
角色名称:普通用户
  • id标签也可以配置多个,比如联合主键时
  • 很可能也会出现没有配置id的情况,这时MyBatis就会把resultMap中所有字段进行比较,如果所有字段的值都相同就合并,只要有一个字段值不同,就不合并。这时,当结果集字段数为M,记录数N,最少M×N次比较,相比配置id时的N次比较,效率相差更多,所以要尽可能配置id标签。
  • 在嵌套结果配置id属性时如果查询语句中没有查询id属性配置的列,就会导致id对应的值为null。这种情况下,所有值的id都相同,因此会使嵌套的集合中只有一条数据。所以在配置id列时,查询语句中必须包含该列。

例子3


给出这样一个场景,sql查询每个国家下的用户数,第一列是用户数,后面三列是一个国家对象所含的信息。

现在我要把结果集封装成如下自定义的对象集合。
其中的Country是一个简单类,有三个属性,和上图查询结果的后三列同名。

public class MyCountResult {
   private Long count;
   private Country country;
    }

在mapper文件中自定义resultMap(红色是因为省略了很长的限定名)

这样确实能把上面sql查询图的结果封装成 MyCountResult

  • 列count 对应 类的count属性
  • 剩下的列 对应 类的coutry嵌套类 (这里的列也复用了别的resultMap)

但是 返回的MyCountResult 对象却只有3个,而不是上面查询结果图中的十几个。
调试发现,count为0的对象只有1个!
这里是为什么呢?

原因:

<collection>标签,用于包装成集合,默认把id相同的封装在同一个集合内

在这里,由于没有指定 < id > 标签,导致mybatis把我的count列当成了id,而很不幸,count列有很多重复的0,所以把所有count为0的行封装成了一个集合,注入到MyCountResult类中,但是这个类中的country属性不是一个数组,所以mybatis只能把刚才的集合中的其中一个注入成country属性。

结语与反思:


既然官方要我们加入 id 标签,无论是性能还是正确性,我们都应该加入此标签。
而且作为id的列中的值必须是唯一的,也就是可以唯一标识这一行,例如刚才我的count列就有很多相同的0,也就失去了id这个词的意义。