5.MyBatis映射器结果映射

1.映射工作原理

<select id="selectUsers" resultType="map">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

上述语句只是简单地将所有的列映射到 HashMap 的键上,但是 HashMap 并不是一个很好的领域模型。

而我们的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为领域模型。MyBatis 对两者都提供了支持。无论是自动映射还是手动映射,都可以将数据库的列映射到JavaBean 或 POJO 的属性。

1.1自动映射

在简单的场景下,MyBatis 可以为你自动映射,不需要手动映射(显式resultMap元素)。

package com.someapp.model;
public class User {
  private int id;
  private String username;
  private String hashedPassword;

  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getUsername() {
    return username;
  }
  public void setUsername(String username) {
    this.username = username;
  }
  public String getHashedPassword() {
    return hashedPassword;
  }
  public void setHashedPassword(String hashedPassword) {
    this.hashedPassword = hashedPassword;
  }
}
<!-- mybatis-config.xml 中 -->
<typeAlias type="com.someapp.model.User" alias="User"/>

<!-- SQL 映射 XML 中 -->
<select id="selectUsers" resultType="User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。 这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性。

通常数据库列使用大写字母组成的单词命名,单词间用下划线分隔;而 Java 属性一般遵循camelCase命名约定。为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase 设置为 true。

如果数据库列名与JavaBean属性名不相同,可以配置数据库列名的别名为JavaBean属性名达到匹配。

<select id="selectUsers" resultType="User">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password     as "hashedPassword"
  from some_table
  where id = #{id}
</select>

有三种自动映射等级:

自动映射描述
NONE禁用自动映射。只对手动映射的属性进行映射。
PARTIAL(默认)除被定义在内部(join)的嵌套结果映射以外的属性进行自动映射。
FULL自动映射所有属性。要谨慎使用 FULL

1.2手动映射

对于复杂一点的语句,需要手动映射(显式resultMap元素)。

虽然上面的例子不用显式配置 resultMap。 但为了讲解,我们来看看如果在刚刚的示例中,显式使用外部的 resultMap 会怎样,这也是解决列名不匹配的另外一种方式。

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>
<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

1.3混合映射

甚至在提供了手动映射(显式resultMap元素)后,自动映射也能工作。在这种情况下,对于每一个结果映射,在 ResultSet 出现的列,如果没有设置手动映射,将被自动映射。在自动映射处理完毕后,再处理手动映射。 在下面的例子中,id 和 userName 列将被自动映射,hashed_password 列将根据配置进行映射。

<select id="selectUsers" resultMap="userResultMap">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password
  from some_table
  where id = #{id}
</select>
<resultMap id="userResultMap" type="User">
  <result property="password" column="hashed_password"/>
</resultMap>

2.resultMap元素

resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。

<!-- 非常复杂的语句 -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
  select
       B.id as blog_id,
       B.title as blog_title,
       B.author_id as blog_author_id,
       A.id as author_id,
       A.username as author_username,
       A.password as author_password,
       A.email as author_email,
       A.bio as author_bio,
       A.favourite_section as author_favourite_section,
       P.id as post_id,
       P.blog_id as post_blog_id,
       P.author_id as post_author_id,
       P.created_on as post_created_on,
       P.section as post_section,
       P.subject as post_subject,
       P.draft as draft,
       P.body as post_body,
       C.id as comment_id,
       C.post_id as comment_post_id,
       C.name as comment_name,
       C.comment as comment_text,
       T.id as tag_id,
       T.name as tag_name
  from Blog B
       left outer join Author A on B.author_id = A.id
       left outer join Post P on B.id = P.blog_id
       left outer join Comment C on P.id = C.post_id
       left outer join Post_Tag PT on PT.post_id = P.id
       left outer join Tag T on PT.tag_id = T.id
  where B.id = #{id}
</select>
<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
  <constructor>
    <idArg column="blog_id" javaType="int"/>
  </constructor>
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
    <result property="favouriteSection" column="author_favourite_section"/>
  </association>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <association property="author" javaType="Author"/>
    <collection property="comments" ofType="Comment">
      <id property="id" column="comment_id"/>
    </collection>
    <collection property="tags" ofType="Tag" >
      <id property="id" column="tag_id"/>
    </collection>
    <discriminator column="draft" javaType="int">
      <case value="1" resultType="DraftPost"/>
    </discriminator>
  </collection>
</resultMap>
resultMap元素的属性描述
id此命名空间中的唯一标识符,可用于引用此结果映射。
typeJava类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考MyBatis配置中的typeAliases部分)。
autoMapping如果已经配置了这个属性,MyBatis 将开启或关闭此 ResultMap 的自动映射。此属性覆盖全局 autoMappingBehavior。默认值为未设置(unset)。

3.id和result元素

id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能。

result – 注入到字段或 JavaBean 属性的普通结果。

idresult元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。

<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
id和result元素的属性描述
property映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性,否则 MyBatis 将会寻找给定名称的字段(field)。无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
javaType一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考MyBatis配置中的typeAliases部分)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcType所支持的 JDBC 类型参见下面的“支持的 JDBC 类型”表格。 只需要在可能执行insert、update和delete的且nullable(可为空)的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值(nullable)的列指定这个类型。
typeHandler我们在前面讨论过默认的类型处理器。使用这个属性,你可以在逐个映射的基础上覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。

支持的 JDBC 类型

为了以后可能的使用场景,MyBatis 通过内置的 jdbcType 枚举类型支持下面的 JDBC 类型。

BITFLOATCHARTIMESTAMPOTHERUNDEFINED
TINYINTREALVARCHARBINARYBLOBNVARCHAR
SMALLINTDOUBLELONGVARCHARVARBINARYCLOBNCHAR
INTEGERNUMERICDATELONGVARBINARYBOOLEANNCLOB
BIGINTDECIMALTIMENULLCURSORARRAY

4.constructor元素

constructor – 用于在实例化类时,注入结果到类的构造方法中。

idArg – ID 参数;标记出作为 ID 的结果可以帮助提高整体性能。

arg – 将被注入到构造方法的一个普通结果。

constructor元素:MyBatis 支持私有属性和私有 JavaBean 属性来完成注入,但有一些人更青睐于通过构造方法进行注入,constructor 元素就是为这一些人而生的。

<constructor>
   <idArg column="id" javaType="int" name="id" />
   <arg column="age" javaType="_int" name="age" />
   <arg column="username" javaType="String" name="username" />
</constructor>
constructor元素的属性描述
column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
javaType一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考MyBatis配置中的typeAliases部分)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行insert、update和delete的nullable(可为空)的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值(nullable)的列指定这个类型。
typeHandler我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。
select另一个映射语句的 ID,它将加载此属性映射所需的复杂类型。从 column 属性中指定的列中检索的值将作为参数传递给目标 select 语句。有关更多信息,请参见association元素。
resultMap这是 ResultMap 的 ID,可以将此参数的嵌套结果映射到适当的对象图中。
这是使用对另一个 select 语句的调用的替代方法。它允许您将多个表join在一起成为一个ResultSet。这样的ResultSet将包含重复的重复数据组,这些数据需要被分解并正确映射到嵌套对象图。为了促进这一点,MyBatis 允许您将结果映射“链接”在一起,以处理嵌套的结果。有关更多信息,请参阅下面的association元素。
name构造方法形参的名字。从 3.4.3 版本开始,通过指定具体的参数名,你可以以任意顺序写入 arg 元素。

5.association元素

association元素:处理“has-one”类型的关系。

<association property="author" column="blog_author_id" javaType="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
</association>
association元素的属性描述
property映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
javaType一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考MyBatis配置中的typeAliases部分)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcTypeJDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行insert、update和delete的nullable(可为空)的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值(nullable)的列指定这个类型。
typeHandler我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。

关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:Nested Select(嵌套查询) 和 Nested Results(嵌套结果)。

5.1Nested Select for Association

Nested Select:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。

性能缺陷一:由于分多次查询数据,而不是一次查询,在大型数据查询时,这会引发“N+1 查询问题”。

性能缺陷二:为了解决性能缺陷一,开启延迟加载嵌套数据,但是如果在返回了第一次查询后立即获取嵌套数据,此时会立即触发延迟加载,这时性能也会变得很糟糕。

因此,可以使用下面介绍的Nested Results和Multiple ResultSets解决“N+1 查询问题”。

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<resultMap id="blogResult" type="Blog">
  <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>

<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
Nested Select的属性描述
column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。值被作为输入参数传递给嵌套语句。注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
select另一个映射语句的 ID,它将加载此属性映射所需的复杂类型。从 column 属性中指定的列中检索的值将作为参数传递给目标 select 语句。注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
fetchType可选的。有效值为 lazy 和 eager。如果已经配置了这个属性,它将取代此映射的
lazyLoadingEnabled全局配置参数。

5.2Nested Results for Association

Nested Results:使用嵌套的结果映射来处理连接结果的重复子集。

<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    B.author_id     as blog_author_id,
    A.id            as author_id,
    A.username      as author_username,
    A.password      as author_password,
    A.email         as author_email,
    A.bio           as author_bio
  from Blog B left outer join Author A on B.author_id = A.id
  where B.id = #{id}
</select>
//分离写法
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>

<resultMap id="authorResult" type="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
  <result property="password" column="author_password"/>
  <result property="email" column="author_email"/>
  <result property="bio" column="author_bio"/>
</resultMap>

//嵌套写法
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="username" column="author_username"/>
    <result property="password" column="author_password"/>
    <result property="email" column="author_email"/>
    <result property="bio" column="author_bio"/>
  </association>
</resultMap>
Nested Results的属性描述
resultMap这是 ResultMap 的 ID,可以将此参数的嵌套结果映射到适当的对象图中。
这是使用对另一个 select 语句的调用的替代方法。它允许您将多个表join在一起成为一个ResultSet。这样的ResultSet将包含重复的重复数据组,这些数据需要被分解并正确映射到嵌套对象图。为了促进这一点,MyBatis 允许您将结果映射“链接”在一起,以处理嵌套的结果。
columnPrefix当join多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。指定 columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。
notNullColumn默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为,指定后,Mybatis 将只在这些列中任意一列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。默认值:未设置(unset)。
autoMapping如果已经配置了这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。注意,本属性对外部的结果映射无效,所以不能搭配 select 或 resultMap 元素使用。默认值:未设置(unset)。

5.3Multiple ResultSets for Association

某些数据库允许存储过程(stored procedures)返回多个结果集,或一次性执行多个语句,每个语句返回一个结果集。 我们可以利用这个特性,在不使用连接(join)的情况下,只访问数据库一次就能获得相关数据。

<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
  {call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>
<resultMap id="blogResult" type="Blog">
  <id property="id" column="id" />
  <result property="title" column="title"/>
  <association property="author" javaType="Author" resultSet="authors" column="author_id" foreignColumn="id">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="password" column="password"/>
    <result property="email" column="email"/>
    <result property="bio" column="bio"/>
  </association>
</resultMap>
Multiple ResultSets的属性描述
column当使用多个结果集时,此属性指定将与foreignColumn相关的列(用逗号分隔),以标识关系的父节点和子节点。
foreignColumn标识包含外键的列的名称,其值将与父类型的column属性中指定的列的值匹配。
resultSet标识将从中加载此复杂类型的结果集的名称。

6.collection元素

<collection property="posts" ofType="domain.blog.Post">
  <id property="id" column="post_id"/>
  <result property="subject" column="post_subject"/>
  <result property="body" column="post_body"/>
</collection>

6.1Nested Select for Collection

<resultMap id="blogResult" type="Blog">
  <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>

<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>

<select id="selectPostsForBlog" resultType="Post">
  SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>

6.2Nested Results for Collection

<select id="selectBlog" resultMap="blogResult">
  select
  B.id as blog_id,
  B.title as blog_title,
  B.author_id as blog_author_id,
  P.id as post_id,
  P.subject as post_subject,
  P.body as post_body,
  from Blog B
  left outer join Post P on B.id = P.blog_id
  where B.id = #{id}
</select>
//分离写法
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="subject" column="post_subject"/>
    <result property="body" column="post_body"/>
  </collection>
</resultMap>

//嵌套写法
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>

<resultMap id="blogPostResult" type="Post">
  <id property="id" column="id"/>
  <result property="subject" column="subject"/>
  <result property="body" column="body"/>
</resultMap>

6.3Multiple ResultSets for Collection

<select id="selectBlog" resultSets="blogs,posts" resultMap="blogResult">
  {call getBlogsAndPosts(#{id,jdbcType=INTEGER,mode=IN})}
</select>
<resultMap id="blogResult" type="Blog">
  <id property="id" column="id" />
  <result property="title" column="title"/>
  <collection property="posts" ofType="Post" resultSet="posts" column="id" foreignColumn="blog_id">
    <id property="id" column="id"/>
    <result property="subject" column="subject"/>
    <result property="body" column="body"/>
  </collection>
</resultMap>

7.discriminator元素

discriminator – 使用结果值来决定使用哪个 resultMap

鉴别器(discriminator)的概念很好理解——它很像 Java 语言中的 switch 语句。

一个鉴别器的定义需要指定 columnjavaType 属性。column 指定了 MyBatis 查询被比较值的地方。 而 javaType 用来确保使用正确的相等测试(虽然很多情况下字符串的相等测试都可以工作)。

如果它匹配任意一个鉴别器的 case,就会使用这个 case 指定的结果映射,剩余的鉴别器块外的结果映射将被忽略(除非它是扩展的)。

如果不能匹配任何一个 case,MyBatis 就只会使用鉴别器块外定义的结果映射。

<resultMap id="vehicleResult" type="Vehicle">
  <id property="id" column="id" />
  <result property="vin" column="vin"/>
  <result property="year" column="year"/>
  <result property="make" column="make"/>
  <result property="model" column="model"/>
  <result property="color" column="color"/>
  <discriminator javaType="int" column="vehicle_type">
    <case value="1" resultMap="carResult"/>
    <case value="2" resultMap="truckResult"/>
    <case value="3" resultMap="vanResult"/>
    <case value="4" resultMap="suvResult"/>
  </discriminator>
</resultMap>
//只使用匹配的这个case指定的结果映射,剩余的鉴别器块外的id、year、make、model、color结果映射将被忽略
<resultMap id="carResult" type="Car">
  <result property="doorCount" column="door_count" />
</resultMap>
//既使用匹配的这个case指定的结果映射,剩余的鉴别器块外的id、year、make、model、color结果映射也会被使用
<resultMap id="carResult" type="Car" extends="vehicleResult">
  <result property="doorCount" column="door_count" />
</resultMap>

原创文章,作者:huoxiaoqiang,如若转载,请注明出处:https://www.huoxiaoqiang.com/java/mybatis/17690.html

(0)
上一篇 2022年9月4日 23:31
下一篇 2022年9月5日 19:50

相关推荐

  • 1.MyBatis工作原理

    1.目录结构 假设Spring Boot项目src目录结构为src/main/java/com/example/demo。 模型层:1层,模型层。 数据访问层:2层,DAO接口层和DAO实现层。 服务层:2层,服务接口层和服务实现层。 控制器层:1层,控制器层。 视图层:1层,视图层。 2.mybatis-config…

    MyBatis教程 2022年9月1日
    01850
  • 2.MyBatis配置

    1.configuration(配置)结构 configuration(配置) properties(属性) settings(设置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境配置) env…

    MyBatis教程 2022年9月2日
    01850
  • 4.MyBatis映射器语句

    1.select元素 select – SELECT映射查询语句。 select元素的属性 描述 id 在命名空间中唯一的标识符,可以被用来引用这条语句。 parameterType 将会传入这条语句的参数的Java类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHand…

    MyBatis教程 2022年9月4日
    02080

发表回复

登录后才能评论