MyBatis的创建初衷是因为一个想法:数据库并不总是符合你的期望或需求。虽然我们希望每个数据库都能达到完美的第三范式或BCNF,但实际上并非如此。并且如果可能的话,希望单个数据库可以完美地映射到使用它的所有应用程序,但事实上也不行。ResultMap就是MyBatis提供的解决这个问题的答案。

例如,我们如何映射这个语句呢?

<!-- Very Complex Statement -->
<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的完整示例(假设Author、Blog、Post、Comments和Tags都是类型别名)。请仔细查看它,但不用担心,我们将逐步解释每一步。虽然乍一看可能有些吓人,但实际上非常简单。

<!-- Very Complex Result Map -->
<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 javaType="int" column="draft">
      <case value="1" resultType="DraftPost"/>
    </discriminator>
  </collection>
</resultMap>

The resultMap元素具有一些子元素和值得探讨的结构。以下是对resultMap元素的概念性视图。

resultMap
  • constructor - 用于在实例化类时将结果注入到构造函数中
    • idArg - ID参数;将结果标记为ID将有助于提高整体性能。
    • arg - 一个普通的结果注入到构造函数中。
  • id –  一个ID结果;将结果标记为ID将有助于提高整体性能。
  • result – 一个普通的结果注入到字段或JavaBean属性中。
  • association –  一个复杂类型的关联;许多结果会汇总到这个类型中。
    • 嵌套的结果映射 - 关联本身是resultMap,或者可以引用一个resultMap。
  • collection – 一个复杂类型的集合。
    • 嵌套的结果映射 - 集合本身是resultMap,或者可以引用一个resultMap。
  • discriminator – ​​​​​​​使用结果值来确定使用哪个resultMap。
    • case –  ​​​​​​​一个case是基于某个值的resultMap。
      • 嵌套的结果映射 - case本身也是一个resultMap,因此可以包含许多相同的元素,或者可以引用外部的resultMap。

 ResultMap 属性

Attribute Description
id 在此命名空间中用于引用该结果映射的唯一标识符。
type 一个完全限定的Java类名,或者一个类型别名(请查看上面的表格以获取内置类型别名的列表)。
autoMapping 如果存在,MyBatis将为此ResultMap启用或禁用自动映射。该属性会覆盖全局的autoMappingBehavior设置。默认值:未设置。

 最佳实践是逐步构建ResultMap。单元测试在这里非常有帮助。如果你试图一次性构建像上面那样庞大的ResultMap,很可能会出错,并且很难进行处理。从简单的开始,一步一步演化。并且要进行单元测试!使用框架的一个缺点是它们有时候是一个黑盒子(无论是开源还是闭源)。为了确保你达到了预期的行为,编写单元测试是你最好的选择。同时,在提交错误时也有帮助。

id & result
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>

 这些是最基本的结果映射。id和result都将单个列值映射到简单数据类型(String、int、double、Date等)的单个属性或字段。

这两者之间唯一的区别是,id会将结果标记为标识属性,在比较对象实例时使用。这有助于提高整体性能,尤其是缓存和嵌套结果映射(即联接映射)的性能。

每个属性都有一些属性:

Id and Result 属性

Attribute(属性) Description(描述)
property 将列结果映射到的字段或属性。如果存在与给定名称匹配的JavaBeans属性,则将使用该属性。否则,MyBatis将查找给定名称的字段。在这两种情况下,你可以使用常规的点号符号进行复杂属性导航。例如,你可以将其映射为简单的东西,比如:username,或者更复杂的东西,比如:address.street.number。
column 数据库中的列名或别名。这与通常传递给resultSet.getString(columnName)的字符串相同。
javaType 一个完全限定的Java类名,或者一个类型别名(请查看上面的表格以获取内置类型别名的列表)。如果映射到一个JavaBean,MyBatis通常可以自动推断出类型。然而,如果映射到一个HashMap,那么你应该显式地指定javaType以确保期望的行为。
jdbcType 来自支持类型列表的JDBC类型。在插入、更新或删除时,只有可空列才需要JDBC类型。这是一个JDBC的要求,而不是MyBatis的要求。因此,即使你直接编写JDBC代码,也需要指定该类型 - 但仅适用于可空值。
typeHandler 在本文档中,我们之前讨论过默认类型处理程序。使用该属性,你可以基于映射来覆盖默认的类型处理程序。该值可以是一个完全限定的TypeHandler实现类名,或者一个类型别名。
支持 JDBC 类型

供将来参考,MyBatis通过包含的JdbcType枚举支持以下JDBC类型。

BIT FLOAT CHAR TIMESTAMP OTHER UNDEFINED
TINYINT REAL VARCHAR BINARY BLOB NVARCHAR
SMALLINT DOUBLE LONGVARCHAR VARBINARY CLOB NCHAR
INTEGER NUMERIC DATE LONGVARBINARY BOOLEAN NCLOB
BIGINT DECIMAL TIME NULL CURSOR ARRAY
 constructor-构造方法

尽管大多数数据传输对象(DTO)类型的类和大部分领域模型都适用于使用属性,但在某些情况下,你可能希望使用不可变类。通常包含引用或查找数据的表适合使用不可变类,这些类很少或从不修改。构造函数注入允许在实例化时设置类的值,而不需要公开的方法。MyBatis还支持私有属性和私有JavaBeans属性来实现此目的,但有些人更喜欢使用构造函数注入。constructor元素实现了这一功能。

考虑以下构造函数: 

public class User {
   //...
   public User(Integer id, String username, int age) {
     //...
  }
//...
}

 为了将结果注入到构造函数中,MyBatis需要确定构造函数的位置。在下面的示例中,MyBatis会按照以下顺序搜索声明了三个参数(java.lang.Integer、java.lang.String和int)的构造函数。

<constructor>
   <idArg column="id" javaType="int"/>
   <arg column="username" javaType="String"/>
   <arg column="age" javaType="_int"/>
</constructor>

在处理具有许多参数的构造函数时,保持arg元素的顺序容易出错。从3.4.3版本开始,可以通过指定每个参数的名称,在任意顺序下编写arg元素。要通过它们的名称引用构造函数参数,可以为它们添加@Param注解,也可以使用‘-parameters’编译器选项编译项目并启用useActualParamName(此选项默认启用)。即使第二个和第三个参数的顺序与声明的顺序不匹配,以下示例对于同一个构造函数也是有效的。

<constructor>
   <idArg column="id" javaType="int" name="id" />
   <arg column="age" javaType="_int" name="age" />
   <arg column="username" javaType="String" name="username" />
</constructor>

可以省略javaType,如果有一个具有相同名称和类型的可写属性。

其余的属性和规则与常规的id和result元素相同。

Attribute(属性) Description(描述)
column 列名来自数据库或者别名标签的名称。这个名称与通常会传递给resultSet.getString(columnName)的字符串相同。
javaType 一个完全限定的Java类名,或者一个类型别名(请参考上面的表格了解内置类型别名列表)。如果你将映射到JavaBean,MyBatis通常可以自动推断出类型。但是,如果你将映射到HashMap,则应明确指定javaType以确保所需的行为。
jdbcType JDBC类型是从以下支持类型列表中选择的。JDBC类型仅在插入、更新或删除可空列时才需要。这是JDBC的要求,而不是MyBatis的要求。所以,即使你直接使用JDBC编码,你也需要指定这个类型 - 但只针对可为空的值。
typeHandler 在之前的文档中我们讨论过默认类型处理程序。使用此属性,您可以按映射方式覆盖默认的类型处理程序。该值可以是一个完全限定的TypeHandler实现类的类名,或者是一个类型别名。
select 另一个已映射的语句的ID,该语句将加载此属性映射所需的复杂类型。从column属性中指定的列中检索到的值将作为参数传递给目标select语句。有关更多信息,请参见Association元素。
resultMap 这是一个ResultMap的ID,可以将该参数的嵌套结果映射到适当的对象图中。这是使用另一个select语句调用的替代方法。它允许您将多个表连接到一个ResultSet中。这样的ResultSet将包含重复的数据组,需要分解和正确映射到嵌套对象图中。为了方便处理嵌套结果,MyBatis允许您将结果映射“链接”在一起。有关更多信息,请参见下面的Association元素。
name 构造函数参数的名称。指定名称允许您以任何顺序编写arg元素。请参见上面的解释。从3.4.3版本开始可用。
association-关联
<association property="author" javaType="Author">
  <id property="id" column="author_id"/>
  <result property="username" column="author_username"/>
</association>

association元素处理“一对一”类型的关系。例如,在我们的例子中,一个博客只有一个作者。关联映射基本上就像任何其他结果映射一样。您需要指定目标属性、属性的javaType(大多数情况下MyBatis可以自动识别)、如果需要则指定jdbcType,以及如果想要覆盖结果值的检索,则可以指定typeHandler。

关联的不同之处在于您需要告诉MyBatis如何加载关联数据。MyBatis有两种不同的加载方式:

  • 嵌套查询:通过执行另一个已映射的SQL语句,返回所需的复杂类型。
  • 嵌套结果:通过使用嵌套结果映射来处理重复的连接子集结果。

首先,让我们来看一下association元素的属性。如您所见,它与普通的结果映射的区别仅在于select和resultMap属性。

Attribute(属性) Description(描述)
property 将列结果映射到的字段或属性。如果存在与给定名称匹配的JavaBeans属性,则将使用该属性。否则,MyBatis将查找具有给定名称的字段。在这两种情况下,您都可以使用常规的点符号表示法进行复杂属性导航。例如,您可以将其映射到简单的名称,如“username”,也可以将其映射到更复杂的名称,如“address.street.number”。
javaType 一个完全限定的Java类名,或者是一个类型别名(请参见上面的表格获取内置类型别名的列表)。如果您将映射目标设置为JavaBean,MyBatis通常可以自动确定属性类型。然而,如果您将映射目标设置为HashMap,则应该显式指定javaType以确保所需的行为。
jdbcType 从接下来的支持类型列表中选择一个JDBC类型。对于可为空的列在插入、更新或删除时,需要指定JDBC类型。这是一个JDBC的要求,而不是MyBatis的要求。所以即使直接编写JDBC代码,您也需要指定这个类型,但只针对可为空的值。
typeHandler 在之前的文档中,我们讨论了默认类型处理程序。使用此属性,您可以按映射覆盖默认的类型处理程序。该值可以是一个完全限定的TypeHandler实现类的类名,或者是一个类型别名。
 Nested Select for Association-在关联中使用嵌套查询
Attribute(属性) Description(描述)
column 列名在数据库中的名称,或者用作给嵌套查询传递输入参数的列别名。它对应于通常会传递给 ​resultSet.getString(columnName)​方法的字符串。请注意:为了处理复合键,您可以通过使用 ​column="{prop1=col1,prop2=col2}"​的语法指定多个列名来向嵌套查询语句传递。这将导致 ​prop1​和 ​prop2​被设置为目标嵌套查询语句的参数对象。
select 另一个映射语句的ID,该语句将加载此属性映射所需的复杂类型。从列中检索到的值将作为参数传递给目标select语句。一个详细的示例如下表所述。注意:为了处理复合键,您可以使用`column="{prop1=col1,prop2=col2}"`的语法指定多个列名来将其传递给嵌套的select语句。这将导致`prop1`和`prop2`被设置为目标嵌套select语句的参数对象。
fetchType 可选项。有效值为`lazy`和`eager`。如果存在,则会覆盖该映射的全局配置参数`lazyLoadingEnabled`。

 例如:

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

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

<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</select>

我们有两个select语句:一个用于加载Blog,另一个用于加载Author,且Blog的结果映射描述了应使用selectAuthor语句来加载其author属性。

除了使用selectAuthor语句加载author属性外,其他属性将自动加载,前提是它们的列名和属性名匹配。

虽然这种方法简单,但在处理大型数据集或列表时性能可能不佳。这个问题被称为“N+1 Selects问题”。简而言之,N+1 Selects问题的原因如下:

  1. 执行单个SQL语句来检索记录列表(“+1”)。
  2. 对于返回的每条记录,执行一个select语句来加载每个记录的详细信息(“N”)。

 这个问题可能导致需要执行数百或数千条SQL语句。这种情况通常是不可取的。

好的一面是,MyBatis可以延迟加载这些查询,因此您可能不需要一次性承受这些语句的开销。但是,如果您加载了这样的列表,然后立即通过迭代访问嵌套数据,将触发所有的延迟加载操作,从而导致性能非常差。

还有另一种方法。

Nested Results for Association-关联的嵌套结果
Attribute(属性) Description(描述)
resultMap 这是一个ResultMap的ID,它可以将该关联的嵌套结果映射到适当的对象图中。这是使用另一个select语句的替代方法。它允许您将多个表连接在一起形成一个单独的ResultSet。这样的ResultSet将包含重复的数据组,需要将其分解并正确映射到嵌套的对象图中。为了方便处理此问题,MyBatis允许您“链接”多个ResultMap来处理嵌套的结果。
columnPrefix 当连接多个表时,为了避免ResultSet中出现重复的列名,您需要使用列别名进行区分。通过指定columnPrefix,您可以将这些列映射到一个外部的resultMap。
notNullColumn 默认情况下,只有在至少一个映射到子对象属性的列非空时,才会创建子对象。通过这个属性,您可以改变这种行为,通过指定哪些列必须具有值,这样MyBatis只会在这些列中任意一个不为空时创建一个子对象。可以使用逗号作为分隔符指定多个列名。默认值:未设置。
autoMapping 如果存在,MyBatis将在将结果映射到该属性时启用或禁用自动映射。该属性会覆盖全局autoMappingBehavior设置。请注意,它对于外部的resultMap没有影响,因此在select或resultMap属性中使用它是没有意义的。默认值:unset(未设置)。

 您已经在上面看到了一个非常复杂的嵌套关联的示例。以下是一个更简单的示例,用于演示它的工作原理。我们将Blog和Author表连接在一起,而不是执行一个单独的语句,就像这样:

<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" 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>

在上面的示例中,可以看到在Blog的“author”关联中,通过委托“authorResult”resultMap来加载Author实例。

非常重要的是,id元素在嵌套结果映射中起着非常重要的作用。您应该始终指定一个或多个可用于唯一标识结果的属性。事实上,如果您不指定id元素,MyBatis仍然可以工作,但会导致严重的性能损失。请选择尽可能少的属性来唯一标识结果。主键是一个明显的选择(即使是复合主键)。

现在,上面的示例使用了一个外部的resultMap元素来映射关联。这使得Author resultMap可以被重复使用。然而,如果您不需要重复使用它,或者您只是喜欢将结果映射合并到一个描述性的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>

如果博客有一个合作者,那么select语句将如下所示:

<select id="selectBlog" resultMap="blogResult">
  select
    B.id            as blog_id,
    B.title         as blog_title,
    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,
    CA.id           as co_author_id,
    CA.username     as co_author_username,
    CA.password     as co_author_password,
    CA.email        as co_author_email,
    CA.bio          as co_author_bio
  from Blog B
  left outer join Author A on B.author_id = A.id
  left outer join Author CA on B.co_author_id = CA.id
  where B.id = #{id}
</select>

回想一下,Author的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中定义的列名不同,您需要指定columnPrefix来重用resultMap以映射合作者的结果。

<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author"
    resultMap="authorResult" />
  <association property="coAuthor"
    resultMap="authorResult"
    columnPrefix="co_" />
</resultMap>
Multiple ResultSets for Association-关联的多个结果集
Attribute(属性) Description(描述)
column 在使用多个结果集时,此属性指定用逗号分隔的列,这些列将与foreignColumn关联,以识别关系的父项和子项。
foreignColumn 此属性用于识别包含外键的列的名称,其值将与父类型的column属性中指定的列的值进行匹配。
resultSet 此属性用于标识此复杂类型将要从哪个结果集加载。

 从3.2.3版本开始,MyBatis提供了另一种解决N+1问题的方法。

一些数据库允许存储过程返回多个结果集或同时执行多个语句并针对每个语句返回一个结果集。这可以用于只访问一次数据库并返回相关数据,而无需使用连接操作。

在示例中,存储过程执行以下查询并返回两个结果集。第一个结果集将包含博客数据,第二个结果集将包含作者数据。

SELECT * FROM BLOG WHERE ID = #{id}

SELECT * FROM AUTHOR WHERE ID = #{id}

每个结果集都必须通过向映射的语句中添加resultSets属性并以逗号分隔的名称列表来指定名称。

<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
  {call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>

现在,我们可以指定填充“author”关联的数据来自“authors”结果集:

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

collection元素与association几乎类似。实际上,如果记录相似之处将会重复。因此,我们应该专注于它们之间的区别。

继续以上面的示例为例,一个博客只有一个作者。但是一个博客可以有多个帖子。在博客类上,可以表示为类似于以下方式:

private List<Post> posts;

为了将一组嵌套结果映射到这样一个List中,我们使用collection元素。与association元素类似,我们可以使用嵌套的select语句或者来自联接的嵌套结果来实现映射。

Nested 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>

有几点你马上就会注意到,但大部分看起来与我们之前学过的关联元素非常相似。首先,你会注意到我们使用了collection元素。然后你会注意到有一个新的“ofType”属性。该属性是必需的,用于区分JavaBean(或字段)属性类型和集合所包含的类型。因此,你可以这样阅读下面的映射:

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

阅读为:“一个ArrayList类型的帖子集合。”

javaType属性实际上是不必要的,因为在大多数情况下,MyBatis会自动为您解析。因此,您通常可以简化为:

<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>
Nested Results for Collection-嵌套结果集用于集合

到目前为止,您可能已经猜到了如何处理集合的嵌套结果,因为它与关联元素完全相同,只是需要添加ofType属性。

首先,让我们来看一下SQL语句:

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

 同样地,我们使用了Blog和Post表的连接,并确保了简单映射的高质量结果列标签。现在,将一个包含其Post映射集合的Blog映射到对象中就非常简单了:

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

同样地,请记住这里的id元素的重要性,如果还没有阅读过关联部分,请先阅读上面的关联部分。

另外,如果您更喜欢允许更多结果映射的可重用的长格式,可以使用以下备选映射:

<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>
Multiple ResultSets for Collection-集合的多结果集

与我们为关联所做的一样,我们可以调用一个执行两个查询并返回两个结果集的存储过程,一个是包含博客的结果集,另一个是包含帖子的结果集:

SELECT * FROM BLOG WHERE ID = #{id}

SELECT * FROM POST WHERE BLOG_ID = #{id}

 每个结果集必须通过向映射的语句中添加resultSets属性并以逗号分隔的名称列表来指定名称。

<select id="selectBlog" resultSets="blogs,posts" resultMap="blogResult">
  {call getBlogsAndPosts(#{id,jdbcType=INTEGER,mode=IN})}
</select>

我们指定“posts”集合将填充来自名为“posts”的结果集中的数据:

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

注意:您在映射关联和集合时没有深度、广度或组合的限制。在映射它们时,应考虑性能问题。对应用程序进行单元测试和性能测试有助于找到最佳的映射方法。好处是,MyBatis允许您稍后更改决策,对您的代码几乎没有(如果有的话)影响。

高级关联和集合映射是一个深奥的话题。文档只能帮助你到一定程度。通过一点练习,一切都会很快变得清晰起来。

discriminator-鉴别器
<discriminator javaType="int" column="draft">
  <case value="1" resultType="DraftPost"/>
</discriminator>

有时,单个数据库查询可能返回许多不同的(但希望有些相关的)数据类型的结果集。鉴别器元素被设计用于处理这种情况,以及包括类继承层次结构在内的其他情况。鉴别器非常容易理解,它的行为类似于Java中的switch语句。

鉴别器定义指定了column和javaType属性。column属性告诉MyBatis要查找的列名,javaType属性用于确保执行正确类型的相等性测试(尽管对于几乎任何情况,String可能都适用)。例如:

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

在此示例中,MyBatis将从结果集中检索每条记录并比较其vehicle类型值。如果匹配鉴别器的任何一个case,则将使用该case指定的resultMap。这是互斥的,换句话说,鉴别器块之外的其他部分将被忽略(除非它被扩展,我们稍后会讨论)。如果没有匹配的case,则MyBatis将简单地使用在鉴别器块之外定义的resultMap。因此,如果carResult的声明如下:

<resultMap id="carResult" type="Car">
  <result property="doorCount" column="door_count" />
</resultMap>

那么,只有doorCount属性将被加载。这样做是为了允许完全独立的鉴别器case组,即使它们与父resultMap没有关系。在这种情况下,当然我们知道汽车和车辆之间存在关系,因为汽车是一种车辆。因此,我们还希望加载其他属性。仅修改resultMap,就可以继续进行。

<resultMap id="carResult" type="Car" extends="vehicleResult">
  <result property="doorCount" column="door_count" />
</resultMap>

现在,将加载vehicleResult和carResult中的所有属性。

然而,有些人可能会觉得这种外部定义的映射有些繁琐。因此,对于那些更喜欢简洁映射风格的人来说,还有一种替代语法。例如:

<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" resultType="carResult">
      <result property="doorCount" column="door_count" />
    </case>
    <case value="2" resultType="truckResult">
      <result property="boxSize" column="box_size" />
      <result property="extendedCab" column="extended_cab" />
    </case>
    <case value="3" resultType="vanResult">
      <result property="powerSlidingDoor" column="power_sliding_door" />
    </case>
    <case value="4" resultType="suvResult">
      <result property="allWheelDrive" column="all_wheel_drive" />
    </case>
  </discriminator>
</resultMap>

请记住,这些都是结果映射(Result Maps),如果您根本没有指定任何结果,那么MyBatis将自动为您匹配列和属性。因此,大多数示例比实际上需要的要冗长。话虽如此,大多数数据库都比较复杂,我们很难依赖它们适用于所有情况。

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐