4 结果映射
resultMap 元素是 MyBatis 中最重要最强大的元素。 它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来。
之前的简单映射语句的示例,没有显式指定 resultMap。比如:
select id, username, password
from users
where id = #{id}
看看下面这个 JavaBean:
package org.mybatis.example;
public class User {
private int id;
private String username;
private String password;
// 省略get set方法
}
这样的一个 JavaBean 可以被映射到 ResultSet,就像映射到 HashMap 一样简单。
select id, username, password
from users
where id = #{id}
使用类型别名就可以不用输入类的全限定名了。比如:
select id, username, password
from users
where id = #{id}
如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名来完成匹配。
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword"
from users
where id = #{id}
高级结果映射
CREATE TABLE `post` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`blog_id` int(11) DEFAULT NULL,
`author_id` int(11) DEFAULT NULL,
`created_on` date DEFAULT NULL,
`section` varchar(100) DEFAULT NULL,
`subject` varchar(100) DEFAULT NULL,
`draft` varchar(100) DEFAULT NULL,
`body` varchar(1000) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=511 DEFAULT CHARSET=utf8;
insert into `post`(`id`,`blog_id`,`author_id`,`created_on`,`section`,`subject`,`draft`,`body`) values (501,101,301,'2020-02-01','1','MyBatis 3.5.1','草案1','以下是用户可见更改的列表。用参数名称指定的keyProperty可能导致ExecutorException。#1485'),(502,102,302,'2020-02-02','2','MyBatis 3.5.2','草案2','以下是用户可见更改的列表。SQL Builder现在支持LIMIT,OFFSET #1521和FETCH FIRST #1582。'),(503,103,303,'2020-02-03','3','MyBatis 3.5.3','草案3','以下是用户可见更改的列表。在包含的
CREATE TABLE `comment` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`post_id` int(11) DEFAULT NULL,
`name` varchar(100) DEFAULT NULL,
`comment` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=611 DEFAULT CHARSET=utf8;
insert into `comment`(`id`,`post_id`,`name`,`comment`) values (601,501,'好评1','看起来不错!谢谢@ kazuki43zoo!'),(602,502,'好评2','非常感谢您的帮助@ huan0huan!'),(603,503,'差评3','为什么不保留Java中SQL的灵活性和语法感觉?');
CREATE TABLE `tag` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=711 DEFAULT CHARSET=utf8;
insert into `tag`(`id`,`name`) values (701,'Alpha'),(702,'Beta'),(703,'RC'),(704,'GA');
CREATE TABLE `post_tag` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`post_id` int(11) DEFAULT NULL,
`tag_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=811 DEFAULT CHARSET=utf8;
insert into `post_tag`(`id`,`post_id`,`tag_id`) values (801,501,701),(802,502,702),(803,503,703);
结果映射(resultMap)
constructor - 用于在实例化类时,注入结果到构造方法中
idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
arg - 将被注入到构造方法的一个普通结果
id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
result – 注入到字段或 JavaBean 属性的普通结果
association – 一个复杂类型的关联;许多结果将包装成这种类型
嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
collection – 一个复杂类型的集合
嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
discriminator – 使用结果值来决定使用哪个 resultMap
case – 基于某些值的结果映射
嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
ResultMap 的属性列表
属性
描述
id
当前命名空间中的一个唯一标识,用于标识一个结果映射。
type
类的完全限定名, 或者一个类型别名。
autoMapping
如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。
id 和 result 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。
Id 和 Result 的属性
属性
描述
property
映射到列结果的字段或属性。
column
数据库中的列名,或者是列的别名。
javaType
一个 Java 类的全限定名,或一个类型别名。
jdbcType
JDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。
typeHandler
使用这个属性,你可以覆盖默认的类型处理器。
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
看看下面这个构造方法:
public class User {
//...
public User(Integer id, String username, String password) {
//...
}
//...
}
为了将结果注入构造方法,MyBatis 需要通过某种方式定位相应的构造方法。 在下面的例子中,MyBatis 搜索一个声明了三个形参的的构造方法,参数类型以 java.lang.Integer, java.lang.String 和 a.lang.String的顺序给出。
属性
描述
column
数据库中的列名,或者是列的别名。
javaType
一个 Java 类的完全限定名,或一个类型别名。
jdbcType
JDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。
typeHandler
使用这个属性,你可以覆盖默认的类型处理器。
select
用于加载复杂类型属性的映射语句的 ID,它会从 column 属性中指定的列检索数据,作为参数传递给此 select 语句。
resultMap
结果映射的 ID,可以将嵌套的结果集映射到一个合适的对象树中。
name
构造方法形参的名字。
关联(association)元素处理“有一个”类型的关系。 比如,在我们的示例中,一个博客有一个用户。
首先,先让我们来看看这个元素的属性。你将会发现,和普通的结果映射相比,它只在 select 和 resultMap 属性上有所不同。
属性
描述
property
映射到列结果的字段或属性。
javaType
一个 Java 类的完全限定名,或一个类型别名。
jdbcType
JDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。
typeHandler
使用这个属性,你可以覆盖默认的类型处理器。
属性
描述
property
映射到列结果的字段或属性。
javaType
一个 Java 类的完全限定名,或一个类型别名。
jdbcType
JDBC 类型,所支持的 JDBC 类型参见这个表格之前的“支持的 JDBC 类型”。
typeHandler
使用这个属性,你可以覆盖默认的类型处理器。
SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM AUTHOR WHERE ID = #{id}
属性
描述
resultMap
结果映射的 ID,可以将此关联的嵌套结果集映射到一个合适的对象树中。
columnPrefix
当连接多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。
notNullColumn
默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。
autoMapping
如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。
现在我们将博客表和作者表连接在一起,就像这样:
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}
注意查询中的连接,以及为确保结果能够拥有唯一且清晰的名字,我们设置的别名。 现在我们可以映射这个结果:
属性
描述
column
当使用多个结果集时,该属性指定结果集中用于与 foreignColumn 匹配的列,以识别关系中的父类型与子类型。
foreignColumn
指定外键对应的列名,指定的列将与父类型中 column 的给出的列进行匹配。
resultSet
指定用于加载复杂类型的结果集名字。
在例子中,存储过程执行下面的查询并返回两个结果集。
DELIMITER $$
CREATE PROCEDURE getBlogsAndAuthors(IN blog_id INT, IN author_id INT)
BEGIN
SELECT * FROM BLOG WHERE ID = blog_id;
SELECT * FROM AUTHOR WHERE ID = author_id;
END $$
在映射语句中,必须通过 resultSets 属性为每个结果集指定一个名字,多个名字使用逗号隔开。
{call getBlogsAndAuthors(#{blogId,jdbcType=INTEGER,mode=IN},#{authorId,jdbcType=INTEGER,mode=IN})}
现在我们可以指定使用 “authors” 结果集的数据来填充 “author” 关联:
集合元素和关联元素几乎是一样的,我们来关注它们的不同之处。
一个博客(Blog)只有一个作者(Author)。但一个博客有很多文章(Post)。 在博客类中,这可以用下面的写法来表示:
private List
要像上面这样,映射嵌套结果集合到一个 List 中,可以使用集合元素。
首先,让我们看看如何使用嵌套 Select 查询来为博客加载文章。
SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM POST WHERE BLOG_ID = #{id}
“ofType” 属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。
首先, 让我们看看对应的 SQL 语句:
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}
像关联元素那样,我们可以通过执行存储过程实现,它会执行两个查询并返回两个结果集,一个是博客的结果集,另一个是文章的结果集:
DELIMITER $$
CREATE PROCEDURE getBlogsAndPosts(IN in_id INT)
BEGIN
SELECT * FROM BLOG WHERE ID = in_id;
SELECT * FROM POST WHERE BLOG_ID = in_id;
END $$
在映射语句中,必须通过 resultSets 属性为每个结果集指定一个名字,多个名字使用逗号隔开。
{call getBlogsAndPosts(#{id,jdbcType=INTEGER,mode=IN})}
我们指定 “posts” 集合将会使用存储在 “posts” 结果集中的数据进行填充:
有时候,一个数据库查询可能会返回多个不同的结果集。 鉴别器(discriminator)元素就是被设计来应对这种情况的。 鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。 一个鉴别器的定义需要指定 column 和 javaType 属性。column 指定了 MyBatis 查询被比较值的地方。 而 javaType 用来确保使用正确的相等测试。例如:
CREATE TABLE `vehicle` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`vin` varchar(100) DEFAULT NULL,
`year` varchar(100) DEFAULT NULL,
`make` varchar(100) DEFAULT NULL,
`model` varchar(100) DEFAULT NULL,
`color` varchar(100) DEFAULT NULL,
`vehicle_type` int(11) DEFAULT NULL,
`door_count` int(11) DEFAULT NULL,
`box_size` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=911 DEFAULT CHARSET=utf8;
insert into `vehicle`(`id`,`vin`,`year`,`make`,`model`,`color`,`vehicle_type`,`door_count`,`box_size`) values (901,'奔驰汽车','1883','德国奔驰汽车公司','AMG GT4','绿色',1,2,NULL),(902,'麦克卡车','1940','麦克制造公司','Mack NR','白色',2,NULL,4),(903,'雷诺货车','1941','美国雷诺货车公司','LCV','红色',3,NULL,NULL);
如果 carResult 的声明如下:
那么只有 doorCount 属性会被加载。
SELECT * FROM vehicle WHERE id = #{id}