目录

MyBatis 中文文档

Mapper XML Files 中文文档

MyBatis的真正强大之处在于映射语句。这就是奇迹发生的地方。尽管具有强大的功能,但 Mapper XML文件相对简单。当然,如果您将它们与等价的JDBC代码进行比较,您将立即看到节省了95%的代码。构建MyBatis的目的是专注于SQL,并尽可能不妨碍您。

Mapper XML文件只有几个第一类元素(按照定义它们的顺序):

  • cache – 为给定名称空间配置缓存。
  • cache-ref – 从另一个名称空间引用缓存配置。
  • resultMap – 最复杂和功能最强大的元素,它描述如何从数据库结果集中加载对象。
  • parameterMap弃用!老式的参数映射方法。内联参数是首选的,这个元素将来可能会被删除。
  • sql – SQL的可重用块,可以由其他语句引用。
  • insert – 映射的 INSERT 语句。
  • update – 映射的 UPDATE 语句。
  • delete – 映射的 DELETE 语句。
  • select – 映射的 SELECT 语句。

下面的一些章节将从语句本身开始,详细描述每个元素。

insert, update and delete

数据修改语句的插入,更新和删除在实现上非常相似:

<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">

<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">

插入、更新和删除属性(以下的 key 指的是键值对中的 键,数据库中指的是 某一列的列名,这在数据库内部自动生成值时有用):

Attribute Description
id 此命名空间中的唯一标识符,可用于引用此语句。
parameterType 将会传递到此语句的参数的完全限定类名或别名。这个属性是可选的,因为MyBatis可以在传递给语句的实际参数之外计算要使用的TypeHandler。默认是 unset
parameterMap 这是一种不推荐(已丢弃)的引用外部参数映射的方法。使用内联参数映射和parameterType属性。
flushCache 将此设置为true会在调用此语句时刷新第二级和本地缓存。 对于插入,更新和删除语句的默认值为true
timeout 这将设置 driver 在抛出异常之前等待数据库从请求返回的最大秒数。默认是 unset (依赖于 driver)。
statementType STATEMENT, PREPARED , CALLABLE 中的任何一个,这将导致 MyBatis 使用 Statement, PreparedStatementCallableStatement 默认值: PREPARED
useGeneratedKeys (仅插入和更新)这告诉MyBatis使用JDBC getGeneratedKeys 方法来检索数据库内部生成的key(例如,RDBMS中的自动增量字段,例如MySQL或SQL Server)。 默认值:false
keyProperty (仅插入和更新)标识一个属性, MyBatis 将设置它为 getGeneratedKeys 返回的值或 insert 语句的 selectKey 子元素返回的键值。 默认值:unset。 如果需要多个生成的列,则可以是用逗号分隔的属性名称列表。
keyColumn (仅插入和更新)设置使用生成的 key 作为值的表中列的名称。只有当key的列不是表中的第一列时,某些数据库(如PostgreSQL)才需要这样做。如果期望生成多个列,则可以使用逗号分隔列名列表。
databaseId In case there is a configured databaseIdProvider, MyBatis will load all statements with no databaseId attribute or with a databaseId that matches the current one. If case the same statement if found with and without the databaseId the latter will be discarded.

下面是一些简单的例子:

<insert id="insertAuthor">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
  update Author set
    username = #{username},
    password = #{password},
    email = #{email},
    bio = #{bio}
  where id = #{id}
</update>

<delete id="deleteAuthor">
  delete from Author where id = #{id}
</delete>

如前所述,insert更丰富一些,因为它有一些额外的属性和子元素,这些属性和子元素允许它以多种方式处理键生成。

首先,如果您的数据库支持自动生成的键字段(例如MySQL和SQL Server),那么您可以简单地设置useGeneratedKeys=“true"并将keyProperty设置为目标属性,这样就完成了。例如,如果上面的Author表为id使用了自动生成的列类型,那么语句将被修改如下:

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username,password,email,bio)
  values (#{username},#{password},#{email},#{bio})
</insert>

如果您的数据库还支持多行插入,您可以传递一个列表或一个作者数组,并检索自动生成的键。

<insert id="insertAuthor" useGeneratedKeys="true"
    keyProperty="id">
  insert into Author (username, password, email, bio) values
  <foreach item="item" collection="list" separator=",">
    (#{item.username}, #{item.password}, #{item.email}, #{item.bio})
  </foreach>
</insert>

MyBatis有另一种方法来处理不支持自动生成列类型的数据库的键生成,或者为可能还不支持自动生成键的JDBC驱动程序提供支持。

这里有一个简单的(愚蠢的)示例,它将生成一个随机的ID(您可能永远不会这样做,但是这演示了它的灵活性,以及MyBatis是如何真正不介意的)

<insert id="insertAuthor">
  <selectKey keyProperty="id" resultType="int" order="BEFORE">
    select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
  </selectKey>
  insert into Author
    (id, username, password, email,bio, favourite_section)
  values
    (#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection,jdbcType=VARCHAR})
</insert>

在上面的示例中,将首先运行selectKey语句,设置Author id属性,然后调用insert语句。这使您的行为类似于数据库中自动生成的键,而不会使Java代码复杂化。

selectKey元素描述如下:

<selectKey
  keyProperty="id"
  resultType="int"
  order="BEFORE"
  statementType="PREPARED">
属性 Description
keyProperty 应该在其中设置selectKey语句结果的目标属性。如果希望生成多个列,可以使用逗号分隔属性名列表。
keyColumn 返回的结果集中与属性匹配的列名。如果期望生成多个列,则可以使用逗号分隔列名列表。
resultType 结果的类型。MyBatis通常可以解决这个问题,但是添加它也无妨。MyBatis允许使用任何简单类型作为键,包括字符串。如果希望生成多个列,则可以使用包含预期属性的对象或映射。
order 可以将其设置为BEFORE或AFTER。如果设置为BEFORE,则首先选择密钥,设置keyProperty,然后执行insert语句。如果将其设置为AFTER,它将运行insert语句,然后运行selectKey语句,这在Oracle等数据库中很常见,因为它们可能在insert语句中嵌入了序列调用。
statementType 与上面一样,MyBatis支持分别映射到STATEMENT、PreparedStatement和CallableStatement的语句类型:STATEMENT、PREPARED和CALLABLE。

Result Maps

resultMap元素是MyBatis中最重要和功能最强大的元素。 它使您可以消除JDBC从ResultSets检索数据所需的90%的代码,并且在某些情况下还可以执行JDBC甚至不支持的操作。 实际上,为诸如复杂映射的联接映射之类的代码编写等效代码可能会跨越数千行代码。 ResultMaps的设计使得简单的语句根本不需要显式的结果映射,而更复杂的语句所需要的仅是描述关系。

您已经看到了没有显式resultMap的简单映射语句的示例。 例如:

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

这样的语句仅导致所有列自动映射到由resultType属性指定的HashMap的键。 尽管在许多情况下很有用,但HashMap并不是一个很好的域模型。 您的应用程序更有可能将 JavaBeans 或 POJO (普通的旧Java对象)用于域模型。 MyBatis 都支持。 考虑以下 JavaBean :

package com.someapp.model;

@Data
public class User {
  private int id;
  private String username;
  private String hashedPassword;
}

根据JavaBeans规范,以上类具有3个属性:id,username 和 hashedPassword。 它们与select语句中的列名完全匹配。

这样的JavaBean可以像HashMap一样轻松地映射到ResultSet。

<select id="selectUsers" resultType="com.someapp.model.User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

请记住,TypeAliases 是您的朋友。 使用它们,这样您就不必继续键入类的完全限定路径。 例如:

<!-- In Config XML file -->
<typeAlias type="com.someapp.model.User" alias="User"/>

<!-- In SQL Mapping XML file -->
<select id="selectUsers" resultType="User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

在这些情况下,MyBatis 将在幕后自动创建一个 ResultMap,以根据名称将列自动映射到 JavaBean 属性。 如果列名不完全匹配,则可以在列名上使用 as 别名(标准SQL功能)以使标签匹配。 例如:

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

ResultMaps 的妙处在于您已经了解了很多有关它们的知识,但是您甚至还没有看过! 这些简单的案例只需要您在这里看到的内容即可。 例如,让我们来看最后一个示例作为外部resultMap的样子,这是解决列名不匹配的另一种方法。

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

引用它的语句使用resultMap属性来执行此操作(注意,我们删除了resultType属性)。 例如:

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>

要是世界总是那么简单就好了。

Advanced Result Maps

创建 MyBatis 的初衷是一个想法:数据库并不总是您想要或需要的。 虽然我们希望每个数据库都可以成为完美的第三范式或BCNF,但事实并非如此。 如果可以将一个数据库完美地映射到使用它的所有应用程序,那将是很好的,事实并非如此。 Result Maps 是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>

您可能希望将其映射到一个智能对象模型,该模型包含一个由作者编写的 Blog ,并且具有许多帖子,每个帖子可能包含零个或多个注释和标签。 以下是复杂的 ResultMap 的完整示例(假设作者,博客,帖子,评论和标签都是类型别名)。 看看吧,但是不用担心,我们将经历每个步骤。 虽然乍一看可能令人生畏,但实际上非常简单。

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

resultMap 元素具有许多子元素和一个值得讨论的结构。 以下是 resultMap 元素的概念图。

resultMap

  • constructor

    - 用于在实例化时将结果注入到类的构造函数中

    • idArg - ID参数; 将结果标记为ID将有助于提高整体效果
    • arg - 将一般结果注入构造函数
  • id – ID结果; 将结果标记为ID将有助于提高整体效果

  • result – 一般结果注入到字段或JavaBean属性中

  • association

    – 复杂类型关联; 许多结果将汇总为这种类型

    • 嵌套结果映射——关联本身就是结果映射,也可以引用结果映射集合
  • collection

    – 复杂类型的集合

    • 嵌套结果映射——集合本身就是结果映射,也可以引用结果映射集合
  • discriminator

    – 使用结果值来确定要使用哪个 resultMap

    • case

      – 一个 case 是基于某个值的 resultMap

      • 嵌套结果映射——case 本身也是一个结果映射,因此它可以包含许多相同的元素,或者它可以引用一个外部的resultMap。

ResultMap 属性:

Attribute Description
id 此名称空间中的唯一标识符,可用于引用此结果映射。
type 完全限定的Java类名或类型别名(有关内置类型别名的列表,请参见上表)。
autoMapping 如果存在,MyBatis将启用或禁用此ResultMap的自动操作。此属性覆盖全局自动应用行为。默认值: unset。

最佳实践:始终以增量方式构建ResultMap。 单元测试在这里确实有帮助。 如果您试图一次构建一个巨大的resultMap,例如上面的一个,可能会出错,并且很难使用。 从简单开始,然后一步一步地发展。 和单元测试! 使用框架的不利之处在于它们有时有点黑匣子(无论是否开放源代码)。 最好确保编写单元测试,以确保达到预期的行为。 提交错误时帮助他们。

下一节将更详细地介绍每个元素。

id & result

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

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

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

每个都有许多属性:

Attribute Description
property 要将列结果映射到的字段或属性。 如果给定名称存在匹配的JavaBeans属性,则将使用该属性。 否则,MyBatis将查找给定名称的字段。 在这两种情况下,都可以使用通常的点符号来使用复杂的属性导航。 例如,您可以映射到简单的名称:用户名,或映射到更复杂的名称: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

未完待续