mybatis学习教程-4-resultMap 元素
resultMap 元素
resultMap元素是MyBatis中最重要最强大的元素。与使用JDBC从结果集获取数据相比,它可
以省掉90%的代码,也可以允许您做一些JDBC不支持的事。事实上,要写一个类似于连结映射
(join mapping)这样复杂的交互代码,可能需要上千行的代码。设计ResultMaps 的目的,就是
只使用简单的配置语句而不需要详细地处理结果集映射,对更复杂的语句除了使用一些必须的语
句描述以外,就不需要其它的处理了。
您可能已经看到过这样简单映射语句,它并没有使用 resultMap,例如:
<select id=”selectUsers” parameterType=”int” resultType=”hashmap”>
select id, username, hashedPassword
from some_table
where id = #{id}
</sql>
像上面的语句,所有结果集将会自动地映射到以列表为key 的HasMap(由resultType指定)
中。虽然这对许多场合下有用,但是HashMap 却不是非常好的域模型。更多的情况是使用
JavaBeans或者POJOs作为域模型。MyBatis支持这两种域模型。考虑下面的JavaBean:
package com.someapp.model;
public class User {
private int id;
private String username;
private String hashedPassword;
public int getId() {
MyBatis 3 - User Guide
31
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;
}
}
基于 JavaBeans 规范,上面的类有 3 个属性: id、username,和 hashedPassword 。这 3 个
属性对应 select 语句的列名。
这样的JavaBean 可以像HashMap 一样简单地映射到 ResultSet 结果集。
<select id=”selectUsers” parameterType=”int”
resultType=”com.someapp.model.User”>
select id, username, hashedPassword
from some_table
where id = #{id}
</sql>
别忘了别名是您的朋友,使用别名您不用输入那长长的类路径。例如:
<!-- In Config XML file -->
<typeAlias type=”com.someapp.model.User” alias=”User”/>
<!-- In SQL Mapping XML file -->
<select id=”selectUsers” parameterType=”int”
resultType=”User”>
select id, username, hashedPassword
from some_table
where id = #{id}
</sql>
这种情况下,MyBatis 在后台自动生成 ResultMap,将列名映射到 JavaBean 的相应属性。如
果列名与属性名不匹配,可以使用 select 语法(标准的 SQL 特性)中的将列名取一个别名的方式
来进行匹配。例如
<select id=”selectUsers” parameterType=”int” resultType=”User”>
MyBatis 3 - User Guide
32
select
user_id
as “id”,
user_name
as “userName”,
hashed_password
as “hashedPassword”
from some_table
where id = #{id}
</sql>
ResultMaps 的知识您可能已经学到了许多,但还有一个您从没见到过。为了举例,让我们看
看最后一个例子,作为另一种解决列名不匹配的方法。
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="username"/>
<result property="password" column="password"/>
</resultMap>
这个语句将会被 resultMap 属性引用(注意,我们没有使用 resultType)。如:
<select id=”selectUsers” parameterType=”int” resultMap=”userResultMap”>
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</sql>
一切就是这么简单!
高级结果映射
MyBatis的创建基于这样一个思想:数据库并不是您想怎样就怎样的。虽然我们希望所有的数
据库遵守第三范式或BCNF(修正的第三范式),但它们不是。如果有一个数据库能够完美映射到
所有应用数据模型,也将是非常棒的,但也没有。结果集映射就是MyBatis为解决这些问题而提供
的解决方案。例如,我们如何映射下面这条语句?
<!-- Very Complex Statement -->
<select id="selectBlogDetails" parameterType="int" 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,
MyBatis 3 - User Guide
33
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>
您可能想要把它映射到一个智能的对象模型,包括由一个作者写的一个博客,有许多文章
(Post,帖子),每个文章由0个或者多个评论和标签。下面是一个复杂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" column="blog_author_id" 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" column="post_author_id" javaType="Author"/>
<collection property="comments" column="post_id" ofType=" Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" column="post_id" ofType=" Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
这个resultMap 的元素的子元素比较多,讨论起来比较宽泛。下面我们从概念上概览一下这
MyBatis 3 - User Guide
34
个resultMap的元素。
resultMap
?
constructor – 实例化的时候通过构造器将结果集注入到类中
o
idArg – ID 参数; 将结果集标记为ID,以方便全局调用
o
arg – 注入构造器的结果集
?
id –结果集ID,将结果集标记为 ID,以方便全局调用
?
result – 注入一个字段或者 javabean 属性的结果
?
association – 复杂类型联合; 许多查询结果合成这个类型
o
嵌套结果映射 – associations 能引用自身, 或者从其它地方引用
?
collection – 复杂类型集合
o
嵌套结果映射– collections 能引用自身, 或者从其它地方引用
?
discriminator –使用一个结果值以决定使用哪个 resultMap
o
case – 基于不同值的结果映射
?
嵌套结果映射 –case 也能引用它自身, 所以也能包含这些同样的元
素。它也可以从外部引用 resultMap
? 最佳实践:
逐步地生成resultMap,单元测试对此非常有帮助。如果您尝试一下子就
生成像上面这样巨大的resultMap,可能会出错,并且工作起来非常吃力。从简单地开
始,再一步步地扩展,并且进行单元测试。使用框架开发有一个缺点,它们有时像是一个
黑盒子。为了确保达到您所预想的行为,最好的方式就是进行单元测试。这对提交 bugs
也非常有用。
下一节,我们一步步地查看这些细节。
id, result 元素
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
这是最基本的结果集映射。id 和 result 将列映射到属性或简单的数据类型字段 (String,
int, double, Date 等)。
这两者唯一不同的是,在比较对象实例时 id 作为结果集的标识属性。这有助于提高总体性
能,特别是应用缓存和嵌套结果映射的时候。
id、result 属性如下:
Attribute
Description
property
映射数据库列的字段或属性。如果JavaBean 的属性与给定的名称匹配,就会使用
匹配的名字。否则,MyBatis 将搜索给定名称的字段。两种情况下您都可以使用
逗点的属性形式。比如,您可以映射到“username”,也可以映射到
“address.street.number”。
column
数据库的列名或者列标签别名。与传递给 resultSet.getString(columnName)的
参数名称相同。
javaType
完整 java 类名或别名 (参考上面的内置别名列表)。如果映射到一个 JavaBean,
那 MyBatis 通常会自行检测到。然而,如果映射到一个 HashMap,那您应该明确
MyBatis 3 - User Guide
35
支持的 JDBC 类型
MyBatis 支持如下的 JDBC 类型:
Constructor 元素
<constructor>
<idArg column="id" javaType="int"/>
<arg column=”username” javaType=”String”/>
</constructor>
当属性与DTO,或者与您自己的域模型一起工作的时候,许多场合要用到不变类。通常,包含
引用,或者查找的数据很少或者数据不会改变的的表,适合映射到不变类中。构造器注入允许您
在类实例化后给类设值,这不需要通过public方法。MyBatis 同样也支持private 属性和
JavaBeans 的私有属性达到这一点,但是一些用户可能更喜欢使用构造器注入。构造器元素可以
做到这点。
考虑下面的构造器:
public class User {
//…
public User(int id, String username) {
//…
}
//…
}
为了将结果注入构造器,MyBatis 需要使用它的参数类型来标记构造器。Java 没有办法通过
参数名称来反射获得。因此当创建 constructor 元素,确保参数是按顺序的并且指定了正确的类
型。
<constructor>
<idArg column="id" javaType="int"/>
<arg column=”username” javaType=”String”/>
</constructor>
指定 javaType 来确保所需行为。
jdbcType
这张表下面支持的 JDBC 类型列表列出的JDBC 类型。这个属性只在 insert,
update 或 delete 的时候针对允许空的列有用。JDBC 需要这项,但 MyBatis 不
需要。如果您直接编写JDBC 代码,在允许为空值的情况下需要指定这个类型。
typeHandler
我们已经在文档中讨论过默认类型处理器。使用这个属性可以重写默认类型处理
器。它的值可以是一个TypeHandler 实现的完整类名,也可以是一个类型别名。
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
MyBatis 3 - User Guide
36
其它的属性与规则与 id、result 元素的一样。
Association 元素
<association property="author" column="blog_author_id" javaType=" Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
</association>
Association 元素处理“has-one”(一对一)这种类型关系。比如在我们的例子中,一个
Blog 有一个Author。联合映射与其它的结果集映射工作方式差不多,指定property、column、
javaType(通常 MyBatis 会自动识别)、jdbcType(如果需要)、typeHandler。
不同的地方是您需要告诉 MyBatis 如何加载一个联合查询。MyBatis 使用两种方式来加载:
?
Nested Select:
通过执行另一个返回预期复杂类型的映射SQL 语句(即引用外部定义好
的 SQL 语句块)。
?
Nested Results:
通过嵌套结果映射(nested result mappings)来处理联接结果集
(joined results)的重复子集。
首先,让我们检查一下元素属性。正如您看到的,它不同于普通只有select 和 resultMap 属
性的结果映射。
Attribute
Description
column
数据库的列名或者列标签别名。与传递给 resultSet.getString(columnName)的
参数名称相同。
javaType
完整 java 类名或别名 (参考上面的内置别名列表)。如果映射到一个 JavaBean,
那 MyBatis 通常会自行检测到。然而,如果映射到一个 HashMap,那您应该明确
指定 javaType 来确保所需行为。
jdbcType
支持的JDBC 类型列表中列出的 JDBC 类型。这个属性只在insert,update 或
delete 的时候针对允许空的列有用。JDBC 需要这项,但 MyBatis 不需要。如果
您直接编写JDBC 代码,在允许为空值的情况下需要指定这个类型。
typeHandler
我们已经在文档中讨论过默认类型处理器。使用这个属性可以重写默认类型处理
器。它的值可以是一个TypeHandler 实现的完整类名,也可以是一个类型别名。
Attribute
Description
property
映射数据库列的字段或属性。如果 JavaBean 的属性与给定的名称匹配,就会使
用匹配的名字。否则,MyBatis 将搜索给定名称的字段。两种情况下您都可以使
用逗点的属性形式。比如,您可以映射到”username”,也可以映射到更复杂点
的”address.street.number”。
column
数据库的列名或者列标签别名。与传递给 resultSet.getString(columnName)的
参数名称相同。
注意: 在处理组合键时, 您可以使用column= “{prop1=col1,prop2=col2}”
这样的语法,设置多个列名传入到嵌套查询语句。这就会把prop1 和 prop2 设置
MyBatis 3 - User Guide
37
联合嵌套选择(Nested Select for Association)
例如:
<resultMap id=”blogResult” type=”Blog”>
<association property="author" column="blog_author_id" javaType="Author"
select=”selectAuthor”/>
</resultMap>
<select id=”selectBlog” parameterType=”int” resultMap=”blogResult”>
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id=”selectAuthor” parameterType=”int” resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
我们使用两个 select 语句:一个用来加载 Blog,另一个用来加载 Author。Blog 的
resultMap 描述了使用“selectAuthor”语句来加载author 的属性。
如果列名和属性名称相匹配的话,所有匹配的属性都会自动加载。
到目标嵌套选择语句的参数对象中。
javaType
完整 java 类名或别名 (参考上面的内置别名列表)。如果映射到一个 JavaBean,
那 MyBatis 通常会自行检测到。然而,如果映射到一个 HashMap,那您应该明确
指定 javaType 来确保所需行为。
jdbcType
支持的JDBC 类型列表中列出的 JDBC 类型。这个属性只在insert,update 或
delete 的时候针对允许空的列有用。JDBC 需要这项,但 MyBatis 不需要。如果
您直接编写JDBC 代码,在允许为空值的情况下需要指定这个类型。
typeHandler
我们已经在文档中讨论过默认类型处理器。使用这个属性可以重写默认类型处理
器。它的值可以是一个TypeHandler 实现的完整类名,也可以是一个类型别名。
select
通过这个属性,通过 ID 引用另一个加载复杂类型的映射语句。从指定列属性中返
回的值,将作为参数设置给目标select 语句。表格下方将有一个例子。注意:
在处理组合键时,您可以使用 column=”{prop1=col1,prop2=col2}”这样的语
法,设置多个列名传入到嵌套语句。这就会把 prop1 和 prop2 设置到目标嵌套语
句的参数对象中。
译者注:
上面的例子,首先执行<select id=“selectBlog”>,执行结果存放到
<resultMap id=“blogResult”>结果映射中。“blogResult”是一个Blog 类型,
从<select id=“selectBlog”>查出的数据都会自动赋值给”blogResult”的与列
名匹配的属性,这时 blog_id,title 等就被赋值了。同时“blogResult”还有一
个关联属性"Author",执行嵌套查询select=”selectAuthor”后,Author 对象的
属性 id,username,password,email,bio 也被赋于与数据库匹配的值。
Blog
{
MyBatis 3 - User Guide
38
虽然这个方法简单,但是对于大数据集或列表查询,就不尽如人意了。这个问题被称为“N+1
选择问题”(N+1 Selects Problem)。概括地说,N+1选择问题是这样产生的:
?
您执行单条SQL 语句去获取一个列表的记录 ( “+1”)。
?
对列表中的每一条记录,再执行一个联合 select 语句来加载每条记录更加详细的信息
(“N”)。
这个问题会导致成千上万的SQL 语句的执行,因此并非总是可取的。
上面的例子,MyBatis 可以使用延迟加载这些查询,因此这些查询立马可节省开销。然而,
如果您加载一个列表后立即迭代访问嵌套的数据,这将会调用所有的延迟加载,因此性能会变得
非常糟糕。
鉴于此,这有另外一种方式。
联合嵌套结果集(Nested Results for Association)
您已经在上面看到了一个非常复杂的嵌套联合的例子,接下的演示的例子会更简单一些。我
们把 Blog 和 Author 表联接起来查询,而不是执行分开的查询语句:
blog_id;
title;
Author author
{
id;
username;
password;
email;
bio;
}
}
建议不要使用 Batatis 的自动赋值,这样不能够清晰地知道要映射哪些属性,
并且有时候还不能保证正确地映射数据库检索结果。
译者注:
如:执行一条 SQL 语句获得了10 条记录,这 10 条记录的每一条再执行一条SQL 语句去加载
更详细的信息,这就执行了10+1 次查询。
resultMap
一个可以映射联合嵌套结果集到一个适合的对象视图上的ResultMap 。这是一个
替代的方式去调用另一个 select 语句。它允许您去联合多个表到一个结果集
里。这样的结果集可能包括冗余的、重复的需要分解和正确映射到一个嵌套对象
视图的数据组。简言之,MyBatis 让您把结果映射‘链接’到一起,用来处理嵌
套结果。举个例子会更好理解,例子在表格下方。
MyBatis 3 - User Guide
39
<select id="selectBlog" parameterType="int" 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>
注意到这个连接(join),要确保所有的别名都是唯一且无歧义的。这使映射容易多了,现
在我们来映射结果集:
<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>
在上面的例子中,您会看到Blog 的作者(“author”)联合一个“authorResult”结果映射
来加载Author 实例。
重点提示:
id 元素在嵌套结果映射中扮演了非常重要的角色,您应该总是指定一个或多个属性
来唯一标识这个结果集。事实上,如果您没有那样做,MyBatis 也会工作,但是会导致严重性能
开销。选择尽量少的属性来唯一标识结果,而使用主键是最明显的选择(即使是复合主键)。
上面的例子使用一个扩展的resultMap 元素来联合映射。这可使Author结果映射可重复使
用。然后,如果您不需要重用它,您可以直接嵌套这个联合结果映射。下面例子就是使用这样的
方式:
<resultMap id="blogResult" type="Blog">
<id property=”blog_id” column="id" />
<result property="title" column="blog_title"/>
<association property="author" column="blog_author_id" 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"/>
MyBatis 3 - User Guide
40
<result property="bio" column="author_bio"/>
</association>
</resultMap>
在上面的例子中您已经看到如果处理“一对一”(“ has one”)类型的联合查询。但是对
于“一对多”(“has many”)的情况如果处理呢?这个问题在下一节讨论。