1.映射工作原理
<select id="selectUsers" resultType="map">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
上述语句只是简单地将所有的列映射到 HashMap
的键上,但是 HashMap
并不是一个很好的领域模型。
而我们的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为领域模型。MyBatis 对两者都提供了支持。无论是自动映射还是手动映射,都可以将数据库的列映射到JavaBean 或 POJO 的属性。
1.1自动映射
在简单的场景下,MyBatis 可以为你自动映射,不需要手动映射(显式resultMap
元素)。
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;
}
}
<!-- mybatis-config.xml 中 -->
<typeAlias type="com.someapp.model.User" alias="User"/>
<!-- SQL 映射 XML 中 -->
<select id="selectUsers" resultType="User">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。 这意味着如果发现了 ID
列和 id
属性,MyBatis 会将列 ID
的值赋给 id
属性。
通常数据库列使用大写字母组成的单词命名,单词间用下划线分隔;而 Java 属性一般遵循camelCase
命名约定。为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase
设置为 true。
如果数据库列名与JavaBean属性名不相同,可以配置数据库列名的别名为JavaBean属性名达到匹配。
<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>
有三种自动映射等级:
自动映射 | 描述 |
NONE | 禁用自动映射。只对手动映射的属性进行映射。 |
PARTIAL (默认) | 除被定义在内部(join)的嵌套结果映射以外的属性进行自动映射。 |
FULL | 自动映射所有属性。要谨慎使用 FULL 。 |
1.2手动映射
对于复杂一点的语句,需要手动映射(显式resultMap
元素)。
虽然上面的例子不用显式配置 resultMap
。 但为了讲解,我们来看看如果在刚刚的示例中,显式使用外部的 resultMap
会怎样,这也是解决列名不匹配的另外一种方式。
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
1.3混合映射
甚至在提供了手动映射(显式resultMap
元素)后,自动映射也能工作。在这种情况下,对于每一个结果映射,在 ResultSet 出现的列,如果没有设置手动映射,将被自动映射。在自动映射处理完毕后,再处理手动映射。 在下面的例子中,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>
2.resultMap元素
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>
<!-- 非常复杂的结果映射 -->
<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 column="draft" javaType="int">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
resultMap元素的属性 | 描述 |
id | 此命名空间中的唯一标识符,可用于引用此结果映射。 |
type | Java类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考MyBatis配置中的typeAliases部分)。 |
autoMapping | 如果已经配置了这个属性,MyBatis 将开启或关闭此 ResultMap 的自动映射。此属性覆盖全局 autoMappingBehavior。默认值为未设置(unset)。 |
3.id和result元素
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能。
result
– 注入到字段或 JavaBean 属性的普通结果。
id
和result
元素都将一个列的值映射到一个简单数据类型(String
, int
, double
, Date
等)的属性或字段。
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
id和result元素的属性 | 描述 |
property | 映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性(property),会先使用该属性,否则 MyBatis 将会寻找给定名称的字段(field)。无论是哪一种情形,你都可以使用常见的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username ”,或者映射到一些复杂的东西上:“address.street.number ”。 |
column | 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 |
javaType | 一个 Java 类的全限定名,或一个类型别名(关于内置的类型别名,可以参考MyBatis配置中的typeAliases部分)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
jdbcType | 所支持的 JDBC 类型参见下面的“支持的 JDBC 类型”表格。 只需要在可能执行insert、update和delete的且nullable(可为空)的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值(nullable)的列指定这个类型。 |
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 |
4.constructor元素
constructor
– 用于在实例化类时,注入结果到类的构造方法中。
idArg
– ID 参数;标记出作为 ID 的结果可以帮助提高整体性能。
arg
– 将被注入到构造方法的一个普通结果。
constructor
元素:MyBatis 支持私有属性和私有 JavaBean 属性来完成注入,但有一些人更青睐于通过构造方法进行注入,constructor
元素就是为这一些人而生的。
<constructor>
<idArg column="id" javaType="int" name="id" />
<arg column="age" javaType="_int" name="age" />
<arg column="username" javaType="String" name="username" />
</constructor>
constructor元素的属性 | 描述 |
column | 数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 |
javaType | 一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考MyBatis配置中的typeAliases部分)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
jdbcType | JDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行insert、update和delete的nullable(可为空)的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值(nullable)的列指定这个类型。 |
typeHandler | 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。 |
select | 另一个映射语句的 ID,它将加载此属性映射所需的复杂类型。从 column 属性中指定的列中检索的值将作为参数传递给目标 select 语句。有关更多信息,请参见association元素。 |
resultMap | 这是 ResultMap 的 ID,可以将此参数的嵌套结果映射到适当的对象图中。 这是使用对另一个 select 语句的调用的替代方法。它允许您将多个表join在一起成为一个 ResultSet 。这样的ResultSet 将包含重复的重复数据组,这些数据需要被分解并正确映射到嵌套对象图。为了促进这一点,MyBatis 允许您将结果映射“链接”在一起,以处理嵌套的结果。有关更多信息,请参阅下面的association元素。 |
name | 构造方法形参的名字。从 3.4.3 版本开始,通过指定具体的参数名,你可以以任意顺序写入 arg 元素。 |
5.association元素
association
元素:处理“has-one
”类型的关系。
<association property="author" column="blog_author_id" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
</association>
association元素的属性 | 描述 |
property | 映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username ”,或者映射到一些复杂的东西上:“address.street.number ”。 |
javaType | 一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考MyBatis配置中的typeAliases部分)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 |
jdbcType | JDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。 只需要在可能执行insert、update和delete的nullable(可为空)的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可以为空值(nullable)的列指定这个类型。 |
typeHandler | 我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。 |
关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:Nested Select(嵌套查询) 和 Nested Results(嵌套结果)。
5.1Nested Select for Association
Nested Select:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
性能缺陷一:由于分多次查询数据,而不是一次查询,在大型数据查询时,这会引发“N+1 查询问题”。
性能缺陷二:为了解决性能缺陷一,开启延迟加载嵌套数据,但是如果在返回了第一次查询后立即获取嵌套数据,此时会立即触发延迟加载,这时性能也会变得很糟糕。
因此,可以使用下面介绍的Nested Results和Multiple ResultSets解决“N+1 查询问题”。
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<resultMap id="blogResult" type="Blog">
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>
<select id="selectAuthor" resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
Nested 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 全局配置参数。 |
5.2Nested Results for Association
Nested Results:使用嵌套的结果映射来处理连接结果的重复子集。
<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>
//分离写法
<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>
//嵌套写法
<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>
Nested Results的属性 | 描述 |
resultMap | 这是 ResultMap 的 ID,可以将此参数的嵌套结果映射到适当的对象图中。 这是使用对另一个 select 语句的调用的替代方法。它允许您将多个表join在一起成为一个 ResultSet 。这样的ResultSet 将包含重复的重复数据组,这些数据需要被分解并正确映射到嵌套对象图。为了促进这一点,MyBatis 允许您将结果映射“链接”在一起,以处理嵌套的结果。 |
columnPrefix | 当join多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。指定 columnPrefix 列名前缀允许你将带有这些前缀的列映射到一个外部的结果映射中。 |
notNullColumn | 默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为,指定后,Mybatis 将只在这些列中任意一列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。默认值:未设置(unset)。 |
autoMapping | 如果已经配置了这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。注意,本属性对外部的结果映射无效,所以不能搭配 select 或 resultMap 元素使用。默认值:未设置(unset)。 |
5.3Multiple ResultSets for Association
某些数据库允许存储过程(stored procedures)返回多个结果集,或一次性执行多个语句,每个语句返回一个结果集。 我们可以利用这个特性,在不使用连接(join)的情况下,只访问数据库一次就能获得相关数据。
<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
{call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
</select>
<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>
Multiple ResultSets的属性 | 描述 |
column | 当使用多个结果集时,此属性指定将与foreignColumn 相关的列(用逗号分隔),以标识关系的父节点和子节点。 |
foreignColumn | 标识包含外键的列的名称,其值将与父类型的column 属性中指定的列的值匹配。 |
resultSet | 标识将从中加载此复杂类型的结果集的名称。 |
6.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>
6.1Nested Select for Collection
<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>
6.2Nested Results for Collection
<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>
//分离写法
<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>
//嵌套写法
<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>
6.3Multiple ResultSets for Collection
<select id="selectBlog" resultSets="blogs,posts" resultMap="blogResult">
{call getBlogsAndPosts(#{id,jdbcType=INTEGER,mode=IN})}
</select>
<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>
7.discriminator元素
discriminator
– 使用结果值来决定使用哪个 resultMap
。
鉴别器(discriminator)的概念很好理解——它很像 Java 语言中的 switch
语句。
一个鉴别器的定义需要指定 column
和 javaType
属性。column
指定了 MyBatis 查询被比较值的地方。 而 javaType
用来确保使用正确的相等测试(虽然很多情况下字符串的相等测试都可以工作)。
如果它匹配任意一个鉴别器的 case
,就会使用这个 case
指定的结果映射,剩余的鉴别器块外的结果映射将被忽略(除非它是扩展的)。
如果不能匹配任何一个 case
,MyBatis 就只会使用鉴别器块外定义的结果映射。
<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>
//只使用匹配的这个case指定的结果映射,剩余的鉴别器块外的id、year、make、model、color结果映射将被忽略
<resultMap id="carResult" type="Car">
<result property="doorCount" column="door_count" />
</resultMap>
//既使用匹配的这个case指定的结果映射,剩余的鉴别器块外的id、year、make、model、color结果映射也会被使用
<resultMap id="carResult" type="Car" extends="vehicleResult">
<result property="doorCount" column="door_count" />
</resultMap>
原创文章,作者:huoxiaoqiang,如若转载,请注明出处:https://www.huoxiaoqiang.com/java/mybatis/17690.html