1 Mapper XML
- select
- insert, update and delete
- sql
- Parameters
- Result Maps
- Auto-mapping
- cache
2 Result Maps
The resultMap element is the most important and powerful element in MyBatis. It's what allows you to do away with 90% of the code that JDBC requires to retrieve data from ResultSets, and in some cases allows you to do things that JDBC does not even support. In fact, to write the equivalent code for something like a join mapping for a complex statement could probably span thousands of lines of code.
resultMap 元素是 MyBatis 最重要也是最强大的元素。它可以使你从 90% 的获取 JDBC 结果集数据的代码中解放出来,甚至在某些情形下允许你做一些 JDBC 不支持的事。事实上,编写一个复杂语句的关联映射的相同功能可能需要几千行的代码。
The design of the ResultMaps is such that simple statements don't require explicit result mappings at all, and more complex statements require no more than is absolutely necessary to describe the relationships.
ResultMap 的设计思想是,简单的语句根本不需要显式的结果映射,更复杂一点的语句只要求描述它们的关系就可以。
2.1 简单结果映射
2.1.1 结果映射到 HashMap
simple mapped statements that don't have an explicit resultMap. For example:
不需要显式的 resultMap 的简单语句映射,如下:
<select id="selectUsers" resultType="map">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
Such a statement simply results in all columns being automatically mapped to the keys of a HashMap, as specified by the resultType attribute.
这样一个语句,通过 resultType 属性指定,可以简单地将所有的列自动映射到 HashMap 的键上。
2.1.2 结果映射到 JavaBean 或 POJO
While useful in many cases, a HashMap doesn't make a very good domain model. It's more likely that your application will use JavaBeans or POJOs (Plain Old Java Objects) for the domain model.
虽然在大多数情况下有用,但是 HashMap 不是一个很好的域模型。你的应用可能需要使用 JavaBean 或者 POJO 作为域模型。
MyBatis supports both. Consider the following JavaBean:
MyBatis 支持以上两者。请看下面的 JavaBean:
package com.someapp.model;
public class User {
private int id;
private String username;
private String hashedPassword;
public int getId() {
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;
}
}
Based on the JavaBeans specification, the above class has 3 properties: id, username, and hashedPassword. These match up exactly with the column names in the select statement.
基于指定的 JavaBean,上面的类有三个属性:id、username 和 hashedPassword。这和 select 语句中的列名完全匹配。
Such a JavaBean could be mapped to a ResultSet just as easily as the HashMap.
这样的一个 JavaBean 可以映射到 ResultSet 中,和 HashMap 一样简单。
<select id="selectUsers" resultType="com.someapp.model.User">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
And remember that TypeAliases are your friends. Use them so that you don't have to keep typing the fully qualified path of your class out.
回忆我们前面所讲的 TypeAliases,它会是你的好帮手。使用它们你就不必使用类的完全限定路径了。
<!-- 在 Config XML 文件中 -->
<typeAlias type="com.someapp.model.User" alias="User"/>
<!-- 在 SQL Mapping XML 文件中-->
<select id="selectUsers" resultType="User">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
In these cases MyBatis is automatically creating a ResultMap behind the scenes to auto-map the columns to the JavaBean properties based on name.
在这些情况下,MyBatis 会自动创建一个 ResultMap 来基于属性名将列自动映射到 JavaBean 的属性上。
If the column names did not match exactly, you could employ select clause aliases (a standard SQL feature) on the column names to make the labels match.
如果列名不是精确匹配,你可以在 select 语句中在列上使用别名(这是一个标准的 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>
The great thing about ResultMaps is that you've already learned a lot about them, but you haven't even seen one yet!
ResultMap 强大的地方在于你已经对它了解了很多,但是你可能根本不会明显地看到它的使用!
2.1.3 使用外部 resultMap
These simple cases don't require any more than you've seen here.
上面这些简单的情况不需要像下面这样配置。
Just for example sake, let's see what this last example would look like as an external resultMap, as that is another way to solve column name mismatches.
出于举例的原因,让我们看一看最后这个例子,如果使用外部的 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>
And the statement that references it uses the resultMap attribute to do so (notice we removed the resultType attribute). For example:
要引用它的语句可以这样来使用 resultMap 属性(注意我们移除了 resultType 属性):
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
Now if only the world was always that simple.
如果世界总是这样简单就好了。
2.2 高级结果映射
MyBatis was created with one idea in mind: Databases aren't always what you want or need them to be. While we'd love every database to be perfect 3rd normal form or BCNF, they aren't. And it would be great if it was possible to have a single database map perfectly to all of the applications that use it, it's not.
MyBatis 创建的一个想法是:数据库并不总是你所想要或者需要的样子。我们希望每个数据库都符合第三范式或 BCNF 范式,但它们并不总是这样。如果有一个数据库映射模式可以完美地映射到所有使用它的应用中那就再好不过了,可惜并没有。
Result Maps are the answer that MyBatis provides to this problem.
结果映射就是 MyBatis 提供的用来解决这个问题的方案。
For example, how would we map this 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>
You'd probably want to map it to an intelligent object model consisting of a Blog that was written by an Author, and has many Posts, each of which may have zero or many Comments and Tags.
你可能想要将它映射到一个智能的对象模型,这个对象由一个作者写的博客组成,有很多的博文,每一篇博文有一到多个评论和标签。
The following is a complete example of a complex ResultMap (assume Author, Blog, Post, Comments and Tags are all type aliases).
下面是一个复杂 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>
2.2.1 resultMap
resultMap 的子元素和结构:
-
constructor - used for injecting results into the constructor of a class upon instantiation
constructor :实例化类时,注入结果到构造方法中。-
idArg - ID argument; flagging results as ID will help improve overall performance
idArg :标记 ID 参数,有助于提高整体性能。 -
arg - a normal result injected into the constructor
arg:要注入到构造方法的一个普通结果(除 idArg 标记的主键外的属性字段)。
-
idArg - ID argument; flagging results as ID will help improve overall performance
-
id – an ID result; flagging results as ID will help improve overall performance
id:标记一个 ID 结果,有助于提高整体性能。 -
result – a normal result injected into a field or JavaBean property
result:要注入到字段或者 JavaBean 属性的普通结果(除 id 标记的主键外的字段)。 -
association – a complex type association; many results will roll up into this type
association:一个复杂类型关联;多个结果的会包装到这个类型。-
nested result mappings – associations are resultMaps themselves, or can refer to one
嵌套结果映射:关联可以是 resultMap 本身,或者引用一个 resultMap。
-
nested result mappings – associations are resultMaps themselves, or can refer to one
-
collection – a collection of complex types
collection:复杂类型的集合。-
nested result mappings – collections are resultMaps themselves, or can refer to one
嵌套结果映射:集合可以是 resultMap 本身,或者引用一个 resultMap。
-
nested result mappings – collections are resultMaps themselves, or can refer to one
-
discriminator – uses a result value to determine which resultMap to use
discriminator :使用一个结果值来决定使用哪个 resultMap。-
case – a case is a result map based on some value
case:基于某些值的结果映射。-
nested result mappings – a case is also a result map itself, and thus can contain many of these same elements, or it can refer to an external resultMap.
嵌套结果映射:一个 case 也是一个 resultMap,因此可以包含多个这样相同的元素,或者可以指向一个外部的 resultMap。
-
nested result mappings – a case is also a result map itself, and thus can contain many of these same elements, or it can refer to an external resultMap.
-
case – a case is a result map based on some value
ResultMap 的属性:
-
id
A unique identifier in this namespace that can be used to reference this result map.
在该命名空间中可以被用来指向该 ResultMap 的唯一标识。 -
type
A fully qualified Java class name, or a type alias (see the table above for the list of built-in type aliases).
完全限定 Java 类名,或者一个类型别名(内置的别名请参考文章 Mybatis Doc 2.3:Configuration 之 TypeAliases)。 -
autoMapping
If present, MyBatis will enable or disable the automapping for this ResultMap. This attribute overrides the global autoMappingBehavior. Default: unset.
如果设置该属性,MyBatis 将会打开或关闭该 ResultMap 的自动映射。这个属性会覆盖全局自动映射行为。默认未设置。
2.2.2 id & result
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
These are the most basic of result mappings. Both id and result map a single column value to a single property or field of a simple data type (String, int, double, Date, etc.).The only difference between the two is that id will flag the result as an identifier property to be used when comparing object instances. This helps to improve general performance, but especially performance of caching and nested result mapping (i.e. join mapping).
这些是结果映射最基本的内容。id 和 result 都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。两者唯一的区别在于:id 标识的结果是用来在比较对象实例时区分对象的唯一标识属性。这样可以帮助提高整体性能,尤其是缓存和关联结果映射(即 连接查询映射)的性能提升。
Id 和 Result 的属性:
-
property
The field or property to map the column result to. If a matching JavaBeans property exists for the given name, then that will be used. Otherwise, MyBatis will look for a field of the given name. In both cases you can use complex property navigation using the usual dot notation. For example, you can map to something simple like: username, or to something more complicated like: address.street.number.
用来映射列的结果的字段或属性。如果匹配的 JavaBean 存在一个给定名字的属性,那么它将被使用。否则,MyBatis 将会查找一个给定名字的字段。无论哪种情况,你都可以使用点式分隔的复杂属性导航。例如,你可以映射一个简单的属性:username,或者更加复杂的属性:address.street.number。 -
column
The column name from the database, or the aliased column label. This is the same string that would normally be passed to resultSet.getString(columnName).
数据库中的列名,或者列的别名。这跟传递到 resultSet.getString(columnName) 的参数一致。 -
javaType
A fully qualified Java class name, or a type alias (see the table above for the list of built-in type aliases). MyBatis can usually figure out the type if you're mapping to a JavaBean. However, if you are mapping to a HashMap, then you should specify the javaType explicitly to ensure the desired behaviour.
一个完全限定 Java 类名,或类型别名(内置的别名请参考文章 Mybatis Doc 2.3:Configuration 之 TypeAliases)。如果你映射到一个 JavaBean,MyBatis 通常可以推断出类型。但是,如果你映射到一个 HashMap,那么你应该显式地指定 JavaType 以确保期望的映射行为。 -
jdbcType
The JDBC Type from the list of supported types that follows this table. The JDBC type is only required for nullable columns upon insert, update or delete. This is a JDBC requirement, not a MyBatis one. So even if you were coding JDBC directly, you'd need to specify this type – but only for nullable values.
所支持的 JDBC 类型参见之后的表格。仅在执行 insert、update 、delete 操作时的可空列上需要设置。这是 JDBC 要求的,而非 MyBatis。所以即使你直接使用 JDBC 编程,你也需要为可空字段设置这个类型。 -
typeHandler
We discussed default type handlers previously in this documentation. Using this property you can override the default type handler on a mapping-by-mapping basis. The value is either a fully qualified class name of a TypeHandler implementation, or a type alias.
我们之前讨论了默认的类型处理器(Mybatis Doc 2.4:Configuration 之 TypeHandlers)。在基于映射的映射原则上,使用这个属性你可以覆盖默认的类型处理器。值为一个 TypeHandler 实现的完全限定类名或类型别名。
支持的 JDBC 类型
For future reference, MyBatis supports the following JDBC Types via the included JdbcType enumeration.
为了之后的引用,MyBatis 通过内置的 JdbcType 的枚举类型,支持以下 JDBC 类型。
2.2.3 constructor
While properties will work for most Data Transfer Object (DTO) type classes, and likely most of your domain model, there may be some cases where you want to use immutable classes. Often tables that contain reference or lookup data that rarely or never changes is suited to immutable classes.
虽然属性可以满足大多数的数据传输对象(DTO)类型的类和大多数的域模型,但可能还有一些情况你想使用不可变类。通常包含引用或查询数据的表很少变甚至不变,适合不可变类。
Constructor injection allows you to set values on a class upon instantiation, without exposing public methods.
构造器注入允许你在初始化时为类设置值,而不会暴露公有方法。
MyBatis also supports private properties and private JavaBeans properties to achieve this, but some people prefer Constructor injection. The constructor element enables this.
MyBatis 也支持私有属性和私有 JavaBean 属性来达到这个目的,但一些人还是更喜欢构造器注入的方式。constructor 元素就是来做这个的。
我们来看下面这个构造方法:
public class User {
//...
public User(Integer id, String username, int age) {
//...
}
//...
}
In order to inject the results into the constructor, MyBatis needs to identify the constructor for somehow.
为了注入结果到这个构造器,MyBatis 需要用某种方式标识这个构造器。
In the following example, MyBatis searches a constructor declared with three parameters: java.lang.Integer, java.lang.String and int in this order.
在下面这个例子中,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>
When you are dealing with a constructor with many parameters, maintaining the order of arg elements is error-prone.Since 3.4.3, by specifying the name of each parameter, you can write arg elements in any order.
当你正在处理一个包含多个参数的构造器,很容易在 arg 元素顺序上出错。从 3.4.3 开始,通过为每个参数指定名称,你可以以任意顺序编写 arg 元素。
To reference constructor parameters by their names, you can either add @Param annotation to them or compile the project with '-parameters' compiler option and enable useActualParamName (this option is enabled by default).
为了通过 name 来引用构造方法的参数,你可以添加 @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 can be omitted if there is a property with the same name and type.
如果类中存在一个属性有相同的名称和类型,javaType 可以省略。
The rest of the attributes and rules are the same as for the regular id and result elements.
剩余的属性和规则同普通的 id 和 result 元素是一样的。
-
column(同 id & result,此处略)
-
javaType(同 id & result,此处略)
-
jdbcType(同 id & result,此处略)
-
typeHandler(同 id & result,此处略)
-
select
The ID of another mapped statement that will load the complex type required by this property mapping. The values retrieved from columns specified in the column attribute will be passed to the target select statement as parameters. See the Association element for more.
用于加载复杂类型属性的映射语句的 ID。它会从 column 属性指定的列获取值,并将其作为参数传递到此 select 语句中。更多请参考 Association 元素。 -
resultMap
This is the ID of a ResultMap that can map the nested results of this argument into an appropriate object graph. This is an alternative to using a call to another select statement. It allows you to join multiple tables together into a single ResultSet. Such a ResultSet will contain duplicated, repeating groups of data that needs to be decomposed and mapped properly to a nested object graph. To facilitate this, MyBatis lets you "chain" result maps together, to deal with the nested results. See the Association element below for more.
能够映射嵌套结果到一个合适的对象树中的 ResultMap 的 ID。同 select 功能相似。它允许你将连接多张表的结果映射到一个 ResultSet。这样一个 ResultSet 会将包含重复的或部分数据重复的结果集映射到一个嵌套的对象树中。为了实现它,MyBatis 允许你串联多个结果映射,以便解决嵌套结果的问题。更多请参考 Association 元素。 -
name
The name of the constructor parameter. Specifying name allows you to write arg elements in any order. See the above explanation. Since 3.4.3.
构造方法参数的名称。指定名称允许你以任意顺序编写 arg 元素。从 3.4.3 开始。
2.2.4 association
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
</association>
The association element deals with a "has-one" type relationship. For example, in our example, a Blog has one Author.
association 元素处理“有一个”类型的关系。例如,在我们的例子中,一个博客有一个作者。
An association mapping works mostly like any other result. You specify the target property, the javaType of the property (which MyBatis can figure out most of the time), the jdbcType if necessary and a typeHandler if you want to override the retrieval of the result values.
关联映射和其他任意的结果映射工作几乎很像。你要指定一个目标属性、 javaType(大多数时候 MyBatis 可以推断出来)、jdbcType (如果需要的话)和 一个TypeHandler(如果你想要覆盖获取的结果值)。
Where the association differs is that you need to tell MyBatis how to load the association. MyBatis can do so in two different ways:
association 不同的地方在于你需要告诉 MyBatis 怎样加载 association。MyBatis会以以下两种不同的方式来处理:
-
Nested Select: By executing another mapped SQL statement that returns the complex type desired.
嵌套查询:通过执行另外一个映射的 SQL 语句来返回预期的复杂类型。 -
Nested Results: By using nested result mappings to deal with repeating subsets of joined results.
嵌套结果:通过使用嵌套结果映射来处理重复的联合结果的子集。
First, let's examine the properties of the element. As you'll see, it differs from a normal result mapping only by the select and resultMap attributes.
首先让我们来看看这个元素的属性。你可以看到,它和普通的结果映射只有 select 和 resultMap 属性不同。
-
property(同 id & result,此处略)
-
javaType(同 id & result,此处略)
-
jdbcType(同 id & result,此处略)
-
typeHandler(同 id & result,此处略)
2.2.4.1 关联的嵌套 Select 查询
-
column
The column name from the database, or the aliased column label that holds the value that will be passed to the nested statement as an input parameter. This is the same string that would normally be passed to resultSet.getString(columnName). Note: To deal with composite keys, you can specify multiple column names to pass to the nested select statement by using the syntax column="{prop1=col1,prop2=col2}". This will cause prop1 and prop2 to be set against the parameter object for the target nested select statement.
数据库的列名或列的别名,其值会作为一个输入参数传递给嵌套语句。通常,这和传递给 resultSet.getString(columnName) 的参数是同一个字符串。注意:为了处理复合主键,你可以通过使用 column="{prop1=col1,prop2=col2}" 的句法指定多个列名传递到嵌套查询语句中。这会使 prop1 和 prop2 以参数对象形式传给目标嵌套查询语句。 -
select
The ID of another mapped statement that will load the complex type required by this property mapping. The values retrieved from columns specified in the column attribute will be passed to the target select statement as parameters. A detailed example follows this table. Note: To deal with composite keys, you can specify multiple column names to pass to the nested select statement by using the syntax column="{prop1=col1,prop2=col2}". This will cause prop1 and prop2 to be set against the parameter object for the target nested select statement.
用于加载复杂类型属性的映射语句的 ID。它会从 column 属性指定的列获取值,并将其作为参数传递到此 select 语句中。之后有一个详细的示例。注意:为了处理复合主键,你可以通过使用 column="{prop1=col1,prop2=col2}" 的句法指定多个列名传递到嵌套查询语句中。这会使 prop1和 prop2 以参数形式传给目标嵌套查询语句。 -
fetchType
Optional. Valid values are lazy and eager. If present, it supersedes the global configuration parameter lazyLoadingEnabled for this mapping.
可选项。有效的值为 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>
That's it. We have two select statements: one to load the Blog, the other to load the Author, and the Blog's resultMap describes that the selectAuthor statement should be used to load its author property.
我们有两条查询语句,一个用来加载 Blog,另一个用来加载 Author,Blog 的 resultMap 描述了 selectAuthor 语句应该加载 author 属性。
All other properties will be loaded automatically assuming their column and property names match.
其他所有属性将会被自动加载,如果它们的列名和属性名匹配的话。
N+1 问题
While this approach is simple, it will not perform well for large data sets or lists. This problem is known as the "N+1 Selects Problem". In a nutshell, the N+1 selects problem is caused like this:
尽管这种方法很简单,但在大型数据集合和列表上它的表现并不是很好。这个问题就是我们熟知的“N + 1 查询问题”。简言之,N + 1 查询问题是这样引起的:
-
You execute a single SQL statement to retrieve a list of records (the "+1").
你执行了一条 SQL 语句获取结果列表(“+1”). -
For each record returned, you execute a select statement to load details for each (the "N").
对每条返回结果记录,你执行了一条查询语句加载每个结果的详细信息(“N”)。
This problem could result in hundreds or thousands of SQL statements to be executed. This is not always desirable.
这个问题会导致成百上千的 SQL 语句被执行。这通常不是我们想看到的。
The upside is that MyBatis can lazy load such queries, thus you might be spared the cost of these statements all at once. However, if you load such a list and then immediately iterate through it to access the nested data, you will invoke all of the lazy loads, and thus performance could be very bad.
好处是 MyBatis 可以对这样的查询进行懒加载,因此你可能能够分散这样的语句的消耗。然而,如果你加载这样一个列表然后立刻遍历它来访问嵌套的数据,就会触发所有的懒加载,这样的行为可能是很糟糕的。
And so, there is another way.
因此,还有另外一种方式来实现。
2.2.4.2 关联的嵌套结果查询
-
resultMap
This is the ID of a ResultMap that can map the nested results of this association into an appropriate object graph. This is an alternative to using a call to another select statement. It allows you to join multiple tables together into a single ResultSet. Such a ResultSet will contain duplicated, repeating groups of data that needs to be decomposed and mapped properly to a nested object graph. To facilitate this, MyBatis lets you "chain" result maps together, to deal with the nested results. An example will be far easier to follow, and one follows this table.
ResultMap 的 ID,可以映射关联嵌套结果到一个合适的对象树中。这是调用另外一个 select 语句的替代方法。它允许你将连接多个表的结果映射到一个 ResultSet。这样一个 ResultSet 将会将包含重复的或部分数据重复的结果集映射到一个嵌套的对象树中。为了实现它,MyBatis 允许你串联多个结果映射,以便解决嵌套结果的问题。之后会有一个简单的例子。 -
columnPrefix
When joining multiple tables, you would have to use column alias to avoid duplicated column names in the ResultSet. Specifying columnPrefix allows you to map such columns to an external resultMap. Please see the example explained later in this section.
在连接多个表时,你可能不得不使用列的别名来避免 ResultSet 中重复的列名。指定 columnPrefix 允许你将这些列映射到一个外部的 ResultMap。(列名前缀,有助于重复使用外部的 resultMap)。 -
notNullColumn
By default a child object is created only if at least one of the columns mapped to the child's properties is non null. With this attribute you can change this behaviour by specifiying which columns must have a value so MyBatis will create a child object only if any of those columns is not null. Multiple column names can be specified using a comma as a separator. Default value: unset.
默认情况下,子对象仅在至少一个列映射到非空属性时被创建。使用这个属性你可以通过指定哪些列必须有值来改变这个行为,这样 MyBatis 将会仅在任何一个列不为空时创建一个子对象。多个列名可以使用逗号分隔符来指定。默认:未设置。 -
autoMapping
If present, MyBatis will enable or disable automapping when mapping the result to this property. This attribute overrides the global autoMappingBehavior. Note that it has no effect on an external resultMap, so it is pointless to use it with select or resultMap attribute. Default value: unset.
如果设置了该属性,MyBatis 将会在映射结果到当前属性时开启或关闭自动映射。这个属性设置覆盖了全局的自动映射行为。注意它对外部结果映射是没有影响的,所以它在 select 或 resultMap 中是没用的。默认:未设置。
You've already seen a very complicated example of nested associations above. The following is a far simpler example to demonstrate how this works. Instead of executing a separate statement, we'll join the Blog and Author tables together, like so:
你已经在上面看到了一个非常复杂的关于嵌套关联的例子。下面是一个更加简单的例子来展示它是如何工作的。我们来连接 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>
Notice the join, as well as the care taken to ensure that all results are aliased with a unique and clear name. This makes mapping far easier. Now we can map the results:
注意这个联合查询,以及使用唯一且清晰别名的保护措施。这会使映射更加简单。现在我们就可以来映射结果:
<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>
In the example above you can see at the Blog's "author" association delegates to the "authorResult" resultMap to load the Author instance.
在上面的例子中你可以看到 Blog 的 "author" 关联委托到 "authorResult" 的 resultMap 去加载 Author 实例。
Very Important: id elements play a very important role in Nested Result mapping. You should always specify one or more properties that can be used to uniquely identify the results. The truth is that MyBatis will still work if you leave it out, but at a severe performance cost. Choose as few properties as possible that can uniquely identify the result. The primary key is an obvious choice (even if composite).
非常重要的是:id 元素在嵌套结果映射中扮演非常重要的角色。你应该总是指定一个或多个属性,用来唯一标识结果。事实上 MyBatis 依然会正常工作即使你不这么做,但是会导致严重的性能消耗。选择尽可能少的属性可以唯一标识一个结果,主键是一个很好的选择(即使是复合主键)。
Now, the above example used an external resultMap element to map the association. This makes the Author resultMap reusable. However, if you have no need to reuse it, or if you simply prefer to co-locate your result mappings into a single descriptive resultMap, you can nest the association result mappings. Here's the same example using this approach:
现在,上面的例子使用了一个外部的 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>
What if the blog has a co-author? The select statement would look like:
如果一个 blog 有多个 author 怎么办?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>
Recall that the resultMap for Author is defined as follows.
回忆一下 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>
Because the column names in the results differ from the columns defined in the resultMap, you need to specify columnPrefix to reuse the resultMap for mapping co-author results.
因为结果中的列名和 resultMap 中定义的列不同,你需要指定 columnPrefix 重复利用这个 resultMap 来映射 co-author 的结果。
<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>
2.2.4.3 关联的多结果集
-
column
When using multiple resultset this attribute specifies the columns (separated by commas) that will be correlated with the foreignColumn to identify the parent and the child of a relationship.
当使用多个结果集时,该属性指定同 foreignColumn 有关系的列(多个用逗号分隔)来标识父子关系。 -
foreignColumn
Identifies the name of the columns that contains the foreign keys which values will be matched against the values of the columns specified in the column attibute of the parent type.
标识包含外键的列的名称,这些外键的值将与父类型的 column 属性中指定的列的值匹配。 -
resultSet
Identifies the name of the result set where this complex type will be loaded from.
标识该复杂类型将要从哪里加载的结果集的名称。
Starting from version 3.2.3 MyBatis provides yet another way to solve the N+1 problem.
从 3.2.3 版本开始,MyBatis 提供另外一种方式去解决 N+1 问题。
Some databases allow stored procedures to return more than one resultset or execute more than one statement at once and return a resultset per each one. This can be used to hit the database just once and return related data without using a join.
一些数据库允许存储过程返回多个结果集或一次性执行多条语句且每条语句返回一个结果集。这使得与数据库只进行一次访问并且在不使用 join 的情况下返回关联数据。
In the example, the stored procedure executes the following queries and returns two result sets. The first will contain Blogs and the second Authors.
在这个例子中,存储过程执行下面的查询并返回两个结果集。第一个包含 Blog,第二个是包含Author。
SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM AUTHOR WHERE ID = #{id}
A name must be given to each result set by adding a resultSets attribute to the mapped statement with a list of names separated by commas.
通过添加 resultSets 属性为每个结果集指定名称来映射语句,使用逗号分隔的名称列表。
<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
{call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>
Now we can specify that the data to fill the "author" association comes in the "authors" result set:
现在我们就可以指定填充位于 "author" 结果集中的 "author" 关联的数据。
<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>
2.2.5 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>
The collection element works almost identically to the association. In fact, it's so similar, to document the similarities would be redundant. So let's focus on the differences.
collection 元素几乎与 association 相同。列举它们的相似处是多余的。所以我们来看看不同的地方。
To continue with our example above, a Blog only had one Author. But a Blog has many Posts. On the blog class, this would be represented by something like:
继续我们上面的例子,一个 blog 只有一个 Author。但是一个 Blog 有很多的 Post。在 Blog 类中,这会表示为:
private List<Post> posts;
To map a set of nested results to a List like this, we use the collection element. Just like the association element, we can use a nested select, or nested results from a join.
为了映射这样一个嵌套结果,我们使用 collection 元素。就像 association 元素一样,我们可以使用嵌套 select 查询或嵌套结果。
2.2.5.1 集合的嵌套 Select 查询
<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>
First, you'll notice that we're using the collection element. Then you'll notice that there's a new "ofType" attribute. This attribute is necessary to distinguish between the JavaBean (or field) property type and the type that the collection contains. So you could read the following mapping like this:
首先,你会注意到我们正在使用 collection 元素。然后你将注意到一个新的 "ofType" 属性。这个属性在区分 JavaBean(或字段)属性类型和集合包含的类型上是必要的。所以你可以阅读下面这个映射:
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
Read as: "A collection of posts in an ArrayList of type Post."
读作:posts 是一个存储 Post 的 ArrayList 集合。
The javaType attribute is really unnecessary, as MyBatis will figure this out for you in most cases. So you can often shorten this down to simply:
javaType 属性是不必要的,因为 MyBatis 在大多数情况下会为你推算出来。所以你可以缩短为:
<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>
2.2.5.2 集合的嵌套结果查询
By this point, you can probably guess how nested results for a collection will work, because it's exactly the same as an association, but with the same addition of the ofType attribute applied.
至此,你可能已经能猜出在 collection 中嵌套结果是如何工作的,因为它几乎与 association 一样,除了增加了一个 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>
Again, we've joined the Blog and Post tables, and have taken care to ensure quality result column labels for simple mapping. Now mapping a Blog with its collection of Post mappings is as simple as:
我们再次连接 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>
Again, remember the importance of the id elements here, or read the association section above if you haven't already.
同样在这里要记得 id 元素的重要性。如果你忘了,可以阅读 association 部分。
Also, if you prefer the longer form that allows for more reusability of your result maps, you can use the following alternative mapping:
同样,如果你更喜欢允许你的结果映射重用性更好的长形式,你可以使用下面这样的映射来替代:
<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>
2.2.5.3 集合的多结果集
As we did for the association, we can call a stored procedure that executes two queries and returns two result sets, one with Blogs and another with Posts:
就像我们在 association 中用到的那样,我们可以调用执行两条查询并返回两个结果集的存储过程,一个 Blog,另一个是 Posts:
SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM POST WHERE BLOG_ID = #{id}
A name must be given to each result set by adding a resultSets attribute to the mapped statement with a list of names separated by commas.
通过添加 resultSets 属性为每个结果集指定名称来映射语句,使用逗号分隔的名称列表。
<select id="selectBlog" resultSets="blogs,posts" resultMap="blogResult">
{call getBlogsAndPosts(#{id,jdbcType=INTEGER,mode=IN})}
</select>
We specify that the "posts" collection will be filled out of data contained in the result set named "posts":
我们指定“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>
NOTE There's no limit to the depth, breadth or combinations of the associations and collections that you map. You should keep performance in mind when mapping them. Unit testing and performance testing of your application goes a long way toward discovering the best approach for your application. The nice thing is that MyBatis lets you change your mind later, with very little (if any) impact to your code.
注意:映射对于深度、广度或 association 和 collection 的结合使用是没有限制的。当你映射它们的时候应该时刻关注性能。对于为你的应用找到一个最优解决方案,单元测试和性能测试会有很长的路要走。好在 MyBatis 允许你在之后改变想法,而不会对代码产生多大影响。
2.2.6 discriminator(鉴别器)
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
Sometimes a single database query might return result sets of many different (but hopefully somewhat related) data types. The discriminator element was designed to deal with this situation, and others, including class inheritance hierarchies.
有时一个独立的数据库查询可能会返回包含很多不同(但希望有些关联)数据类型的结果集。discriminator 元素就是为这种情况而生的,它还能处理类的继承结构层次。
The discriminator is pretty simple to understand, as it behaves much like a switch statement in Java.A discriminator definition specifies column and javaType attributes. The column is where MyBatis will look for the value to compare. The javaType is required to ensure the proper kind of equality test is performed (although String would probably work for almost any situation). For example:
discriminator 理解起来很简单,它和 Java 里的 switch 语句很像。一个 discriminator 的定义中指定了 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>
In this example, MyBatis would retrieve each record from the result set and compare its vehicle type value. If it matches any of the discriminator cases, then it will use the resultMap specified by the case. This is done exclusively, so in other words, the rest of the resultMap is ignored (unless it is extended, which we talk about in a second). If none of the cases match, then MyBatis simply uses the resultMap as defined outside of the discriminator block. So, if the carResult was declared as follows:
在这个例子中,MyBatis 会从结果集中获取每条记录并比较它的 vehicle_type 的值。如果匹配任意一个 discriminator 的 case,则使用该 case 指定的 resultMap。这个操作是排他的,换句话说,剩余的 resultMap 会被忽略(除非它被 extends 了,我们随后会讨论)。如果没有 case 可以匹配,那么 MyBatis 会使用定义在 discriminator 块外的 resultMap。所以,如果 carResult 是这么声明的:
<resultMap id="carResult" type="Car">
<result property="doorCount" column="door_count" />
</resultMap>
Then ONLY the doorCount property would be loaded. This is done to allow completely independent groups of discriminator cases, even ones that have no relationship to the parent resultMap. In this case we do of course know that there's a relationship between cars and vehicles, as a Car is-a Vehicle. Therefore, we want the rest of the properties loaded too. One simple change to the resultMap and we're set to go.
那么只有 doorCount 属性会被加载。这么做允许了完全独立的 discriminator 的 case 组,甚至是和父 resultMap 没有什么关系的 case。在这个例子中,我们当然是知道 car 和 vehicle 之间是有关系的:一个 Car 是一个 Vehicle。因此我们想要同时加载其余的属性。简单改变一下这个 resultMap 的设置就可以了:
<resultMap id="carResult" type="Car" extends="vehicleResult">
<result property="doorCount" column="door_count" />
</resultMap>
Now all of the properties from both the vehicleResult and carResult will be loaded.
现在 vehicleResult 和 carResult 的所有属性都将被加载。
Once again though, some may find this external definition of maps somewhat tedious. Therefore there's an alternative syntax for those that prefer a more concise mapping style. For example:
不过,也有人可能会发现这个外部映射的定义有一些烦人。因此,为那些更喜欢简洁的映射风格的人我们提供了一个可替代的语法:
<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>
NOTE Remember that these are all Result Maps, and if you don't specify any results at all, then MyBatis will automatically match up columns and properties for you. So most of these examples are more verbose than they really need to be. That said, most databases are kind of complex and it's unlikely that we'll be able to depend on that for all cases.
注意:记住这些都是 ResultMap,如果你不指定任何结果,那么 MyBatis 将会自动为你匹配列和属性。所以大多数这些例子是冗余的。也就是说,大多数的数据库是很复杂的,我们不太可能在所有的情况中都依赖这种机制。
最后
说明:MyBatis 官网提供了简体中文的翻译,但个人觉得较为生硬,甚至有些地方逻辑不通,于是自己一个个重新敲着翻译的(都不知道哪里来的自信...),有些地方同官网翻译有出入,有些倔强地保留了自己的,有的实在别扭则保留了官网的,这些都会在实践中一一更正。鉴于个人英文能力有限,文章中保留了官方文档原英文介绍(个别地方加以调整修剪),希望有缘看到这里的朋友们能够有自己的理解,不会被我可能错误或不合理的翻译带跑偏(〃'▽'〃),欢迎指正!
当前版本:mybatis-3.5.0
官网文档:MyBatis
官网翻译:MyBatis 简体中文
项目实践:MyBatis Learn
网友评论