resultMap 元素是 MyBatis 中最重要最强大的元素。 它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来。
之前的简单映射语句的示例,没有显式指定 resultMap。比如:
看看下面这个 JavaBean:
package org.mybatis.example;
public class User {
private int id;
private String username;
private String password;
// 省略get set方法
}
这样的一个 JavaBean 可以被映射到 ResultSet,就像映射到 HashMap 一样简单。
使用类型别名就可以不用输入类的全限定名了。比如:
如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名来完成匹配。
高级结果映射
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','以下是用户可见更改的列表。在包含的 的CDATA中支持变量替换。#1615');
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)
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 |
使用这个属性,你可以覆盖默认的类型处理器。 |
属性 |
描述 |
resultMap |
结果映射的 ID,可以将此关联的嵌套结果集映射到一个合适的对象树中。 |
columnPrefix |
当连接多个表时,你可能会不得不使用列别名来避免在 ResultSet 中产生重复的列名。 |
notNullColumn |
默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 |
autoMapping |
如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 |
现在我们将博客表和作者表连接在一起,就像这样:
注意查询中的连接,以及为确保结果能够拥有唯一且清晰的名字,我们设置的别名。 现在我们可以映射这个结果:
属性 |
描述 |
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 属性为每个结果集指定一个名字,多个名字使用逗号隔开。
现在我们可以指定使用 “authors” 结果集的数据来填充 “author” 关联:
集合元素和关联元素几乎是一样的,我们来关注它们的不同之处。
一个博客(Blog)只有一个作者(Author)。但一个博客有很多文章(Post)。 在博客类中,这可以用下面的写法来表示:
private List posts;
要像上面这样,映射嵌套结果集合到一个 List 中,可以使用集合元素。
首先,让我们看看如何使用嵌套 Select 查询来为博客加载文章。
“ofType” 属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。
首先, 让我们看看对应的 SQL 语句:
像关联元素那样,我们可以通过执行存储过程实现,它会执行两个查询并返回两个结果集,一个是博客的结果集,另一个是文章的结果集:
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 属性为每个结果集指定一个名字,多个名字使用逗号隔开。
我们指定 “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 属性会被加载。