Mybatis映射详解
在最近的工作中,碰到一个比较复杂的返回结果,发现简单映射已经解决不了这个问题了,只好去求助百度,学习mybatis复杂映射应该怎么写,将学习笔记结合工作碰到的问题写下本文,供自身查漏补缺,同时已被不时之需。
ResultMap的属性列表
属性 | 描述 |
---|---|
id | 当前命名空间中的一个唯一标识,用于标识一个结果映射。 |
type | 类的完全限定名, 或者一个类型别名。 |
autoMapping | 如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。 |
1. 简单映射
select查询的返回结果可以通过映射将表字段名与类属性名一一对应:
package com.someapp.model;
@Data
public class User {
private int id;
private String username;
private String hashedPassword;
}
<!--type 标签对应Java实体-->
<resultMap id="userResultMap" type="User">
<id property="id" column="id" />
<result property="username" column="user_name"/>
<result property="hashedPassword" column="hashed_password"/>
</resultMap>
<select id="selectUsers" resultMap="userResultMap">
select id, user_name, hashed_password
from some_table
where id = #{id}
</select>
2. 复杂映射
- constructor - 用于在实例化类时,注入结果到构造方法中
idArg
- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能arg
- 将被注入到构造方法的一个普通结果
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能result
– 注入到字段或 JavaBean 属性的普通结果- association – 一个复杂类型的关联;许多结果将包装成这种类型
- 嵌套结果映射 – 关联可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 关联可以是
- collection – 一个复杂类型的集合
- 嵌套结果映射 – 集合可以是
resultMap
元素,或是对其它结果映射的引用
- 嵌套结果映射 – 集合可以是
- discriminator – 使用结果值来决定使用哪个 resultMap
- case – 基于某些值的结果映射
- 嵌套结果映射 –
case
也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
- 嵌套结果映射 –
- case – 基于某些值的结果映射
2.1 id和result
id
和result
元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。这两者之间的唯一不同是,id元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。
id和result属性
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性。如果 JavaBean有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 |
column | 数据库中的列名,或者是列的别名 |
javaType | 一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名)。 |
jdbcType | JDBC 类型,只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。 |
typeHandler | 使用这个属性,你可以覆盖默认的类型处理器。这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。 |
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
2.2 constructor(构造方法)
构造方法注入允许你在初始化时为类设置属性的值,而不用暴露出公有方法。MyBatis 也支持私有属性和私有 JavaBean 属性来完成注入,但有一些人更青睐于通过构造方法进行注入。constructor
元素就是为此而生的。
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性。如果 JavaBean有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 |
column | 数据库中的列名,或者是列的别名 |
javaType | 一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名)。 |
jdbcType | JDBC 类型,只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。 |
typeHandler | 使用这个属性,你可以覆盖默认的类型处理器。这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。 |
select | 用于加载复杂类型属性的映射语句的 ID,它会从 column 属性中指定的列检索数据,作为参数传递给此 select 语句。 |
resultMap | 结果映射的 ID,可以将嵌套的结果集映射到一个合适的对象树中。 |
name | 构造方法形参的名字。 |
<constructor>
<idArg column="id" javaType="int" name="id" />
<arg column="age" javaType="_int" name="age" />
<arg column="username" javaType="String" name="username" />
</constructor>
2.3 association (关联)
关联(association)元素处理“有一个”类型的关系。关联结果映射和其它类型的映射工作方式差不多。你需要指定目标属性名以及属性的javaType
,在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。
association (关联)属性
属性 | 描述 |
---|---|
property | 映射到列结果的字段或属性。如果 JavaBean有这个名字的属性(property),会先使用该属性。否则 MyBatis 将会寻找给定名称的字段(field)。 |
javaType | 一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名)。 |
jdbcType | JDBC 类型,只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。 |
typeHandler | 使用这个属性,你可以覆盖默认的类型处理器。这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。 |
<association property="author" column="blog_author_id" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
</association>
关联的嵌套 Select 查询
我们有两个 select 查询语句:一个用来加载博客(Blog),另外一个用来加载作者(Author),而且博客的结果映射描述了应该使用 selectAuthor
语句加载它的 author 属性。(不推荐使用)
<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>
关联的嵌套结果映射
<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 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>
id 元素在嵌套结果映射中扮演着非常重要的角色。你应该总是指定一个或多个可以唯一标识结果的属性。虽然,即使不指定这个属性,MyBatis 仍然可以工作,但是会产生严重的性能问题。只需要指定可以唯一标识结果的最少属性。显然,你可以选择主键(复合主键也可以)。
2.4 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>
集合的嵌套 Select 查询
<resultMap id="blogResult" type="Blog">
<!-- “posts 是一个存储 Post 的 ArrayList 集合” -->
<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>
“ofType” 属性:用来将JavaBean(或字段)属性的类型和集合存储的类型区分开来。
集合的嵌套结果映射
<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>
<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>
2.5 discriminator(鉴别器)
有时候,一个数据库查询可能会返回多个不同的结果集(但总体上还是有一定的联系的)。鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。
一个鉴别器的定义需要指定 column 和 javaType 属性。column 指定了 MyBatis 查询被比较值的地方。而 javaType 用来确保使用正确的相等测试。例如:
<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>
<resultMap id="carResult" type="Car" extends="vehicleResult">
<result property="doorCount" column="door_count" />
</resultMap>
在这个示例中,MyBatis 会从结果集中得到每条记录,然后比较它的 vehicle type 值。如果它匹配任意一个鉴别器的 case,就会使用这个 case 指定的结果映射。这个过程是互斥的,也就是说,剩余的结果映射将被忽略。如果不能匹配任何一个 case,MyBatis 就只会使用鉴别器块外定义的结果映射。
2.5 应用实例
<!-- 非常复杂的结果映射 -->
<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>
<!-- 非常复杂的语句 -->
<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>
3 自动映射
当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java类中查找相同名字的属性(忽略大小写)。这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性。
通常数据库列使用大写字母组成的单词命名,单词间用下划线分隔;而 Java属性一般遵循驼峰命名法约定。为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase
设置为 true。
甚至在提供了结果映射后,自动映射也能工作。在这种情况下,对于每一个结果映射,在ResultSet 出现的列,如果没有设置手动映射,将被自动映射。在自动映射处理完毕后,再处理手动映射。在下面的例子中,id 和 userName 列将被自动映射,hashed_password 列将根据配置进行映射。
<resultMap id="userResultMap" type="User">
<result property="password" column="hashed_password"/>
</resultMap>
<select id="selectUsers" resultMap="userResultMap">
select
user_id as "id",
user_name as "userName",
hashed_password
from some_table
where id = #{id}
</select>
有三种自动映射等级:
NONE
- 禁用自动映射。仅对手动映射的属性进行映射。PARTIAL
- 对除在内部定义了嵌套结果映射(也就是连接的属性)以外的属性进行映射(默认映射等级)。FULL
- 自动映射所有属性。
无论设置的自动映射等级是哪种,你都可以通过在结果映射上设置 autoMapping
属性来为指定的结果映射设置启用/禁用自动映射。
<resultMap id="userResultMap" type="User" autoMapping="false">
<result property="password" column="hashed_password"/>
</resultMap>