MyBatis

XML 映射器

MyBatis 的真正强大在于它的语句映射, 这是它的魔力所在.由于它的异常强大, 映射器的 XML 文件就显得相对简单.

xml 元素说明

SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):

  • cache – 该命名空间的缓存配置.
  • cache-ref – 引用其它命名空间的缓存配置.
  • resultMap – 描述如何从数据库结果集中加载对象, 是最复杂也是最强大的元素.
    • parameterMap – 老式风格的参数映射, 已废弃
  • sql – 可被其它语句引用的可重用语句块.
  • insert – 映射插入语句.
  • update – 映射更新语句.
  • delete – 映射删除语句.
  • select – 映射查询语句.

select 元素

查询语句是 MyBatis 中最常用的元素之一——光能把数据存到数据库中价值并不大, 还要能重新取出来才有用, 多数应用也都是查询比修改要频繁. MyBatis 的基本原则之一是: 在每个插入、更新或删除操作之间, 通常会执行多个查询操作.因此, MyBatis 在查询和结果映射做了相当多的改进.一个简单查询的 select 元素是非常简单的.比如:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

这个语句名为 selectPerson, 接受一个 int(或 Integer)类型的参数, 并返回一个 HashMap 类型的对象, 其中的键是列名, 值便是结果行中的对应值.

注意参数符号: #{id} 参数占位符

属性描述
id在命名空间中唯一的标识符, 可以被用来引用这条语句.
parameterType将会传入这条语句的参数的类全限定名或别名.这个属性是可选的, 因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数, 默认值为未设置(unset).
parameterMap用于引用外部 parameterMap 的属性, 目前已被废弃.请使用行内参数映射和 parameterType 属性.
resultType期望从这条语句中返回结果的类全限定名或别名. 注意, 如果返回的是集合, 那应该设置为集合包含的类型, 而不是集合本身的类型. resultType 和 resultMap 之间只能同时使用一个.
resultMap对外部 resultMap 的命名引用.结果映射是 MyBatis 最强大的特性, 如果你对其理解透彻, 许多复杂的映射问题都能迎刃而解. resultType 和 resultMap 之间只能同时使用一个.
flushCache将其设置为 true 后, 只要语句被调用, 都会导致本地缓存和二级缓存被清空, 默认值: false.
useCache将其设置为 true 后, 将会导致本条语句的结果被二级缓存缓存起来, 默认值: 对 select 元素为 true.
timeout这个设置是在抛出异常之前, 驱动程序等待数据库返回请求结果的秒数.默认值为未设置(unset)(依赖数据库驱动).
fetchSize这是一个给驱动的建议值, 尝试让驱动程序每次批量返回的结果行数等于这个设置值. 默认值为未设置(unset)(依赖驱动).
statementType可选 STATEMENT, PREPARED 或 CALLABLE.这会让 MyBatis 分别使用 Statement, PreparedStatement 或 CallableStatement, 默认值: PREPARED.
resultSetTypeFORWARD_ONLY, SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个, 默认值为 unset (依赖数据库驱动).
databaseId如果配置了数据库厂商标识(databaseIdProvider), MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有, 则不带的会被忽略.
resultOrdered这个设置仅针对嵌套结果 select 语句: 如果为 true, 将会假设包含了嵌套结果集或是分组, 当返回一个主结果行时, 就不会产生对前面结果集的引用. 这就使得在获取嵌套结果集的时候不至于内存不够用.默认值: false.
resultSets这个设置仅适用于多结果集的情况.它将列出语句执行后返回的结果集并赋予每个结果集一个名称, 多个名称之间以逗号分隔.

insert, update 和 delete 元素

数据变更语句 insert, update 和 delete 的实现非常接近:

<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">
  insert into Author (id, username, password, email, bio)
  values (#{id}, #{username}, #{password}, #{email}, #{bio})
  </insert>
 
<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">
   update Author set
    username = #{username}, 
    password = #{password}, 
    email = #{email}, 
    bio = #{bio}
  where id = #{id}
  </update>
 
<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">
  delete from Author where id = #{id}
  </delete>
 
  
属性描述
id在命名空间中唯一的标识符, 可以被用来引用这条语句.
parameterType将会传入这条语句的参数的类全限定名或别名.这个属性是可选的, 因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数, 默认值为未设置(unset).
parameterMap用于引用外部 parameterMap 的属性, 目前已被废弃.请使用行内参数映射和 parameterType 属性.
flushCache将其设置为 true 后, 只要语句被调用, 都会导致本地缓存和二级缓存被清空, 默认值: (对 insert、update 和 delete 语句)true.
timeout这个设置是在抛出异常之前, 驱动程序等待数据库返回请求结果的秒数.默认值为未设置(unset)(依赖数据库驱动).
statementType可选 STATEMENT, PREPARED 或 CALLABLE.这会让 MyBatis 分别使用 Statement, PreparedStatement 或 CallableStatement, 默认值: PREPARED.
useGeneratedKeys(仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如: 像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段), 默认值: false.
keyProperty(仅适用于 insert 和 update)指定能够唯一识别对象的属性, MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值, 默认值: 未设置(unset).如果生成列不止一个, 可以用逗号分隔多个属性名称.(回填主键的key)
keyColumn(仅适用于 insert 和 update)设置生成键值在表中的列名, 在某些数据库(像 PostgreSQL)中, 当主键列不是表中的第一列的时候, 是必须设置的.如果生成列不止一个, 可以用逗号分隔多个属性名称.
databaseId如果配置了数据库厂商标识(databaseIdProvider), MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有, 则不带的会被忽略.

insert 自增主键+回填

设置 useGeneratedKeys=”true”, 然后再把 keyProperty 设置为目标属性就 OK 了

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

insert 多行插入

如果你的数据库还支持多行插入, 你也可以传入一个 Author 数组或集合, 并返回自动生成的主键.

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

insert 下的 selectKey 元素

有时业务逻辑需要自定义实体主键, 该selectKey标签就是用来获取这个生成的主键(id)

<insert id="add" parameterType="com.demo.pojo.User">
	<!--通过mybatis框架提供的selectKey标签获得自增产生的ID值-->
	<selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
		select LAST_INSERT_ID()
	</selectKey>
	insert into user(code, name, remark, sex)
				  values
	(#{code}, #{name}, #{remark}, #{sex})
</insert>

selectKey 会将 SELECT LAST_INSERT_ID() 的结果放入 keyProperty 定义的属性中

selectKey 可定义的属性:

属性描述
keyPropertyselectKey 语句结果应该被设置到的目标属性.如果生成列不止一个, 可以用逗号分隔多个属性名称.
keyColumn返回结果集中生成列属性的列名.如果生成列不止一个, 可以用逗号分隔多个属性名称.
resultType结果的类型.通常 MyBatis 可以推断出来, 但是为了更加准确, 写上也不会有什么问题.MyBatis 允许将任何简单类型用作主键的类型, 包括字符串.如果生成列不止一个, 则可以使用包含期望属性的 Object 或 Map.
order可以设置为 BEFORE 或 AFTER.如果设置为 BEFORE, 那么它首先会生成主键, 设置 keyProperty 再执行插入语句.如果设置为 AFTER, 那么先执行插入语句, 然后是 selectKey 中的语句 - 这和 Oracle 数据库的行为相似, 在插入语句内部可能有嵌入索引调用.
statementType和前面一样, MyBatis 支持 STATEMENT, PREPARED 和 CALLABLE 类型的映射语句, 分别代表 Statement,  PreparedStatement 和 CallableStatement 类型.

sql 元素(片段)

这个元素可以用来定义可重用的 SQL 代码片段, 以便在其它语句中使用. 参数可以静态地(在加载的时候)确定下来, 并且可以在不同的 include 元素中定义不同的参数值.

in short 一段可被重用的sql 代码/模版

比如: <sql id="userColumns"> ${alias}.id, ${alias}.username, ${alias}.password </sql>

这个 SQL 片段可以在其它语句中使用, 例如:

<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>, 
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

结果类似

select
    -- <include refid="userColumns"><property name="alias" value="t1"/></include>,
    t1.id, t1.username, t1.password,
    -- <include refid="userColumns"><property name="alias" value="t2"/></include>
    t2.id, t2.username, t2.password
from some_table t1
   cross join some_table t2

使用 include 复用SQL模版; 使用 property 指定参数给模版;

动态SQL

https: //mybatis.net.cn/dynamic-sql.html

choose 元素

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

in short 从多个条件中选择一个使用, 有点像 Java 中的 switch 语句

条件拼接问题

将 “state = ‘ACTIVE’” 设置成动态条件, 看看会发生什么

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE
  <if test="state != null">
    state = #{state}
  </if>
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE

这会导致查询失败.如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

这个查询也会失败.这个问题不能简单地用条件元素来解决.这个问题是如此的难以解决, 以至于解决过的人不会再想碰到这种问题.

Where 元素

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句. 若子句的开头为 “AND” 或 “OR”, where 元素也会将它们去除.

trim 元素

如果 where 元素与你期望的不太一样, 你也可以通过自定义 trim 元素来定制 where 元素的功能.比如, 和 where 元素等价的自定义 trim 元素为:

<select  >  
SELECT * FROM BLOG  
<where>  
 ....
</where>  
<trim prefix="WHERE" prefixOverrides="AND |OR ">  
</trim>  
</select>

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的).上述例子会移除所有 prefixOverrides 属性中指定的内容, 并且插入 prefix 属性中指定的内容.

foreach 集合遍历

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator=", " close=")">
        #{item}
  </foreach>
</select>

它允许你指定一个集合, 声明可以在元素体内使用的集合项(item)和索引(index)变量. 它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符.

可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach.当使用可迭代对象或者数组时, index 是当前迭代的序号, item 的值是本次迭代获取到的元素.当使用 Map 对象(或者 Map.Entry 对象的集合)时, index 是键, item 是值.

bind 创建变量

bind 元素允许你在 OGNL 表达式以外创建一个变量, 并将其绑定到当前的上下文.比如:

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

更新语句处理

动态更新语句的类似解决方案叫做 set.set 元素可以用于动态包含需要更新的列, 忽略其它不更新的列.比如:

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username}, </if>
      <if test="password != null">password=#{password}, </if>
      <if test="email != null">email=#{email}, </if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

这个例子中, set 元素会动态地在行首插入 SET 关键字, 并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的).

来看看与 set 元素等价的自定义 trim 元素吧:

<trim prefix="SET" suffixOverrides=", ">
  ...
</trim>

注意, 我们覆盖了后缀值设置, 并且自定义了前缀值.

参数映射

https://mybatis.net.cn/sqlmap-xml.html#Parameters

简单映射

对象参数

<insert id="insertUser" parameterType="User">
  insert into users (id,  username,  password)
  values (#{id},  #{username},  #{password})
</insert>

如果 User 类型的参数对象传递到了语句中, 会查找 id、username 和 password 属性, 然后将它们的值传入预处理语句的参数中.

Map参数

对象是一个 HashMap.这个时候, 你需要显式指定 javaType 来确保正确的类型处理器(TypeHandler)被使用.

 JDBC 要求, 如果一个列允许使用 null 值, 并且会使用值为 null 的参数, 就必须要指定 JDBC 类型(jdbcType).阅读 PreparedStatement.setNull()的 JavaDoc 来获取更多信息.

高级映射

定义参数处理器

#{property, javaType=int, jdbcType=NUMERIC}

更进一步地自定义类型处理方式, 可以指定一个特殊的类型处理器类(或别名), 比如:

#{age, javaType=int, jdbcType=NUMERIC, typeHandler=MyTypeHandler}

对于数值类型, 还可以设置 numericScale 指定小数点后保留的位数.

#{height, javaType=double, jdbcType=NUMERIC, numericScale=2}

MyBatis 也支持很多高级的数据类型, 比如结构体(structs), 但是当使用 out 参数时, 你必须显式设置类型的名称.比如(在实际中要像这样不能换行):

#{middleInitial,  mode=OUT,  jdbcType=STRUCT,  jdbcTypeName=MY_TYPE,  resultMap=departmentResultMap}

字符串替换 $ 还是 # ?

默认情况下, 使用 #{} 参数语法时, MyBatis 会创建 PreparedStatement 参数占位符, 并通过占位符安全地设置参数(就像使用 ? 一样). 这样做更安全, 更迅速, 通常也是首选做法, 不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串. 比如 ORDER BY 子句, 这时候你可以:

ORDER BY ${columnName} //使用 $ 符号

这样, MyBatis 就不会修改或转义该字符串了

结果映射

resultMap 元素是 MyBatis 中最重要最强大的元素.它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来, 并在一些情形下允许你进行一些 JDBC 不支持的操作.实际上, 在为一些比如连接的复杂语句编写映射代码的时候, 一份 resultMap 能够代替实现同等功能的数千行代码. ResultMap 的设计思想是, 对简单的语句做到零配置, 对于复杂一点的语句, 只需要描述语句之间的关系就行了.

简单映射

https://mybatis.net.cn/sqlmap-xml.html#Result_Maps

别名映射 as

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

别名映射 resultMap

或者通过 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>

映射为HashMap

之前你已经见过简单映射语句的示例, 它们没有显式指定 resultMap.

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

上述语句只是简单地将所有的列映射到 HashMap 的键上, 这由 resultType 属性指定

映射为DTO

基于 JavaBean 的规范, 上面这个类有 3 个属性: id, username 和 hashedPassword.这些属性会对应到 select 语句中的列名.

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

自动映射

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

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

在下面的例子中,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>

有三种自动映射等级:

  • NONE - 禁用自动映射。仅对手动映射的属性进行映射。
  • PARTIAL - 对除在内部定义了嵌套结果映射(也就是连接的属性)以外的属性进行映射
  • FULL - 自动映射所有属性。

默认值是 PARTIAL,这是有原因的。当对连接查询的结果使用 FULL 时,连接查询会在同一行中获取多个不同实体的数据,因此可能导致非预期的映射。 下面的例子将展示这种风险:

无论设置的自动映射等级是哪种,你都可以通过在结果映射上设置 autoMapping 属性来为指定的结果映射设置启用/禁用自动映射。

<resultMap id="userResultMap" type="User" autoMapping="false">
  <result property="password" column="hashed_password"/>
</resultMap>
 

高级映射

MyBatis 创建时的一个思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序,那就太好了,但可惜也没有。

一个非常复杂的语句

<!-- 非常复杂的语句, 注意 detailedBlogResultMap 是指向 resultMap 配置的ID -->
<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">
  <!-- Blog 的构造方法参数  -->
  <constructor>
    <idArg column="blog_id" javaType="int"/>
    <arg column="title" javaType="String"/>
  </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和普通属性-->
    <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">
     <!--  鉴别器 discriminator: 如果 draft=1 映射为 DraftPost-->
	 
      <case value="1" resultType="DraftPost"/>
    </discriminator>
  </collection>
</resultMap>

伪结构

{
    "blog_id": null,
    "title": null,
    "author": {
        "id": null,
        "username": null
    },
    "posts": [
        {
            "id": null,
            "subject": null,
            "author": {},
            "comments": [
                {
                    "id": null
                }
            ],
            "tags": [
                {
                    "id": null
                }
            ],
            // draft 列的结果值 来决定使用哪个 resultMap
            //  <case value="1" resultType="DraftPost"/>
            "xxx": null
        }
    ]
}
 

resultMap 配置

<resultMap id="detailedBlogResultMap" type="Blog">
属性描述
id当前命名空间中的一个唯一标识,用于标识一个结果映射。
type类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。
autoMapping如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。

id & result 配置

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

id 和 result 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。 这两者之间的唯一不同是,id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。

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

关联配置

关联(association)元素处理“有一个”类型的关系。 比如,在我们的示例中,一个博客有一个作者 关联的不同之处是,你需要告诉 MyBatis 如何加载关联。

MyBatis 有两种不同的方式加载关联:

  1. 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集
  2. 嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载的复杂类型
嵌套结果映射

即本例的配置

<resultMap id="detailedBlogResultMap" type="Blog">
	...
	<association property="author" column="blog_author_id" javaType="Author">
	  <id property="id" column="author_id"/>
	  <result property="username" column="author_username"/>
	</association>
	...
 
嵌套 Select 结果映射

你需要指定目标属性名以及属性的javaType(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。

<!-- 注意 blogResult 是 resultMap 配置的ID -->
<select id="selectBlog" resultMap="blogResult">
  SELECT * FROM BLOG WHERE ID = #{id}
</select>
 
<resultMap id="blogResult" type="Blog">
<!-- 嵌套select查询, selectAuthor  selectAuthor 配置的ID-->
  <association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>
 
<select id="selectAuthor" resultType="Author">
  SELECT * FROM AUTHOR WHERE ID = #{id}
</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,使用属性的值。
复用 resultMap 映射配置

例如: 一个博客(blog)有一个共同作者(co-author), 仅仅只是join的列名不同, 可以复用’作者’的结果映射配置

<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>
 
<resultMap id="blogResult" type="Blog">
  <id property="id" column="blog_id" />
  <result property="title" column="blog_title"/>
  <association property="author"
    resultMap="authorResult" />
 
 <!-- 复用同一个 authorResult 配置, columnPrefix 加一个列别名 以区分-->
  <association property="coAuthor"
    resultMap="authorResult"
    columnPrefix="co_" />
</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>

鉴别器 discriminator

鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。 鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。

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

<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_type 值。 如果它匹配任意一个 case,就会使用这个 case 指定的结果映射。

配置标签说明

  • constructor - 用于在实例化类时,注入结果到构造方法中
    • idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg - 将被注入到构造方法的一个普通结果
  • id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
  • result – 注入到字段或 JavaBean 属性的普通结果
  • association – 一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
  • collection – 一个复杂类型的集合
    • 嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
  • discriminator – 使用结果值来决定使用哪个 resultMap
    • case – 基于某些值的结果映射
      • 嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射

对应的Java 结构

public class Blog {
    private int blogId;
    private String title;
    private Author author;
    private List<Post> posts;
    //构造方法
	public Blog(int blogId, String title){...}
    // Getter 和 Setter 方法
}
 
public class Author {
    private int id;
    private String username;
    private String password;
    private String email;
    private String bio;
    private String favouriteSection;
    // Getter 和 Setter 方法
}
 
public class Post {
    private int id;
    private String subject;
    private Author author; // 这个是一个关联关系,对应于<association property="author" javaType="Author"/>
    private List<Comment> comments;
    private List<Tag> tags;
    // 在此可以根据需要添加其他属性
    // Getter 和 Setter 方法
}
 
public class Comment {
    private int id;
    // 在此可以添加其他评论相关的属性
}
 
public class Tag {
    private int id;
    // 在此可以添加其他标签相关的属性
}
 

JDBC 类枚举列表

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

BITFLOATCHARTIMESTAMPOTHERUNDEFINED
TINYINTREALVARCHARBINARYBLOBNVARCHAR
SMALLINTDOUBLELONGVARCHARVARBINARYCLOBNCHAR
INTEGERNUMERICDATELONGVARBINARYBOOLEANNCLOB
BIGINTDECIMALTIMENULLCURSORARRAY

定义类型别名 typeAlias

<!-- mybatis-config.xml 中 -->
<typeAlias type="com.someapp.model.User" alias="User"/>