9.4.5 ResultMaps
resultMap
元素是MyBatis
中最重要最强大的元素。它的作用是告诉MyBatis
将从结果集中取出的数据转换成开发者所需要的对象。
查询结果的每条记录转换成一个Map
下面是最简单的映射语句示例:
1 2 3
| <select id="selectUser" resultType="map"> select * from tb_user </select>
|
selectUser
的select
元素执行一条查询语句,查询tb_user
表的所有数据。
resultType="map"
表示返回的每条数据是都是一个Map
集合(使用这条记录的列名作为key
,列值作为value
)。
示例: 测试ResultMaps
项目结构
展开/折叠
D:\Desktop\随书源码\Spring+Mybatis企业应用实战(第2版)\codes\09\ResultMapTest
├─src\
│ ├─db.properties
│ ├─log4j.xml
│ ├─mybatis-config.xml
│ └─org\
│ └─fkit\
│ ├─domain\
│ │ ├─Clazz.java
│ │ ├─Student.java
│ │ └─User.java
│ ├─factory\
│ │ └─FKSqlSessionFactory.java
│ ├─mapper\
│ │ └─UserMapper.xml
│ └─test\
│ ├─ResultMapTest.java
│ ├─SelectClazzTest.java
│ ├─SelectMapTest.java
│ └─SelectStudentTest.java
├─tb_user2.sql
└─WebContent\
├─META-INF\
│ └─MANIFEST.MF
└─WEB-INF\
├─lib\
│ ├─ant-1.9.6.jar
│ ├─ant-launcher-1.9.6.jar
│ ├─asm-5.2.jar
│ ├─cglib-3.2.5.jar
│ ├─commons-logging-1.2.jar
│ ├─javassist-3.22.0-CR2.jar
│ ├─log4j-1.2.17.jar
│ ├─log4j-api-2.3.jar
│ ├─log4j-core-2.3.jar
│ ├─mybatis-3.4.5.jar
│ ├─mysql-connector-java-8.0.17.jar
│ ├─ognl-3.1.15.jar
│ ├─slf4j-api-1.7.25.jar
│ └─slf4j-log4j12-1.7.25.jar
└─web.xml
resultType为map
tb_user.sql
D:\Desktop\随书源码\Spring+Mybatis企业应用实战(第2版)\codes\09\ResultMapTest\src\org\fkit\domain\tb_user.sql1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `tb_user`; CREATE TABLE `tb_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, `sex` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, `age` int(11) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
INSERT INTO `tb_user` VALUES (1, 'admin', '男', 26); INSERT INTO `tb_user` VALUES (2, '张三', '男', 23); INSERT INTO `tb_user` VALUES (3, '李四', '男', 23); INSERT INTO `tb_user` VALUES (4, '王五', '男', 23);
SET FOREIGN_KEY_CHECKS = 1;
|
SelectMapTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package org.fkit.test;
import java.util.List; import java.util.Map; import org.apache.ibatis.session.SqlSession; import org.fkit.factory.FKSqlSessionFactory;
public class SelectMapTest {
public static void main(String[] args) { SqlSession sqlSession = null; try { sqlSession = FKSqlSessionFactory.getSqlSession(); List<Map<String, Object>> list = sqlSession.selectList("org.fkit.mapper.UserMapper.selectUser"); list.forEach(row -> System.out.println(row)); sqlSession.commit(); } catch (Exception e) { sqlSession.rollback(); e.printStackTrace(); } finally { if (sqlSession != null) sqlSession.close(); } } }
|
对应的select标签
D:\Desktop\随书源码\Spring+Mybatis企业应用实战(第2版)\codes\09\ResultMapTest\src\org\fkit\mapper\UserMapper.xml1 2 3 4 5 6 7
|
<select id="selectUser" resultType="map"> SELECT * FROM TB_USER </select>
|
数据库表
此时tb_user
表中的数据如下:
1 2 3 4 5 6 7 8 9 10
| mysql> select * from tb_user; +----+-------+-----+-----+ | id | name | sex | age | +----+-------+-----+-----+ | 1 | admin | 男 | 26 | | 2 | 张三 | 男 | 23 | | 3 | 李四 | 男 | 23 | | 4 | 王五 | 男 | 23 | +----+-------+-----+-----+ 4 rows in set (0.07 sec)
|
运行结果
运行SelectMapTest
类的main
方法,控制台显示如下:
1 2 3 4 5 6 7
| DEBUG [main] ==> Preparing: select * from tb_user DEBUG [main] ==> Parameters: DEBUG [main] <== Total: 4 {sex=男, name=admin, id=1, age=26} {sex=男, name=张三, id=2, age=23} {sex=男, name=李四, id=3, age=23} {sex=男, name=王五, id=4, age=23}
|
可以看到,査询语句返回的每一条记录都被封装成一个Map
集合,这条记录的列名作为Map
集合的key
,而列的值作为Map
的value
。
resultType为JavaBean
虽然数据被封装成Map
集合返回,但是Map
集合并不能很好地描述一个领域模型。在实际项目开发中更加建议使用JavaBean
或POJO(Plain Old Java Object
,普通Java
对象)作为领域模型描述数据。例如:
1 2 3
| <select id="selectUser" resultType="org.fkit.domain.User"> select * from tb_user </select>
|
数据表列名和持久化对象属性名不一致的情况 使用resultMap标签
默认情况下,MyBatis
会将査询到的记录的列和需要返回的对象(User
)的属性逐一进行匹配赋值,但是,如果查询到的记录的列名和需要返回的对象(User
)的属性名不一致,则MyBatis
就不会自动赋值了,这时,可以使用ResultMap
标签进行处理.
创建数据库表tb_user2
进入mybatis
数据库,创建一个表tb_user2
,并插入几条测试数据:
在cmd
命令行中输入下面的sql
语句进行创建,注意是在cmd
命令行,如果使用Navicat
的话先保存成sql
文件,然后通过导入SQL
文件的方式创建,不要使用Navicat
的命令行界面,因为直接复制粘贴下面代码插入数据的时候,会出现乱码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| use mybatis; DROP TABLE IF EXISTS `tb_user2`; CREATE TABLE `tb_user2` ( `user_id` int(11) NOT NULL AUTO_INCREMENT, `user_name` varchar(18) NOT NULL, `user_sex` varchar(18) NOT NULL, `user_age` int(11) NOT NULL, PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO tb_user2 VALUES (null, '小明', '男', 24); INSERT INTO tb_user2 VALUES (null, '小王', '男', 25); INSERT INTO tb_user2 VALUES (null, '小丽', '女', 22); INSERT INTO tb_user2 VALUES (null, '小花', '女', 25);
|
创建持久化对象User.java
接下来创建一个User
对象映射tb_user2
表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class User implements Serializable { private static final long serialVersionUID = 1L; private Integer id; private String name; private String sex; private Integer age; public User() { super(); } public User(String name, String sex, Integer age) { super(); this.name = name; this.sex = sex; this.age = age; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + "]"; } }
|
创建resultMap标签
D:\Desktop\随书源码\Spring+Mybatis企业应用实战(第2版)\codes\09\ResultMapTest\src\org\fkit\mapper\UserMapper.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
<resultMap id="userResultMap" type="org.fkit.domain.User"> <result column="user_name" property="name" /> <result column="user_sex" property="sex" /> <result column="user_age" property="age" /> </resultMap>
<select id="selectUser2" resultMap="userResultMap"> SELECT * FROM TB_USER2 </select>
|
resultMap标签的属性
上面使用了一个新的元素resultMap
,该元素常用属性如下
resultMap
元素的id
属性,是resultMap
的唯一标识符。
resultMap
元素的type
属性,表示resultMap
实际返回的类型。
上面还使用了resultMap
的两个子元素id
和result
:
resultMap标签的id子标签
id
,表示数据库表的主键列,其中,
column
属性表示数据库表的列名,
property
表示数据库列映射到返回类型的属性名。
resultMap标签的result子标签
result
,表示数据库表的普通列,其中,
column
属性表示数据库表的列名,
property
表示这个数据库列名要映射到的持久化对象的属性名。
ResultMapTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class ResultMapTest { public static void main(String[] args) { SqlSession sqlSession = null; try { sqlSession = FKSqlSessionFactory.getSqlSession(); List<User> user_list = sqlSession.selectList("org.fkit.mapper.UserMapper.selectUser2"); user_list.forEach(user -> System.out.println(user)); sqlSession.commit(); } catch (Exception e) { sqlSession.rollback(); e.printStackTrace(); } finally { if (sqlSession != null) sqlSession.close(); } } }
|
运行ResultMapTest
类的main
方法,控制台显示如下:
1 2 3 4 5 6 7
| DEBUG [main] ==> Preparing: SELECT * FROM TB_USER2 DEBUG [main] ==> Parameters: DEBUG [main] <== Total: 4 User [id=1, name=小明, sex=男, age=24] User [id=2, name=小王, sex=男, age=25] User [id=3, name=小丽, sex=女, age=22] User [id=4, name=小花, sex=女, age=25]
|
可以看到,虽然表TB_USER2
的列名和User
对象的属性名不一致,但是经过resultMap
映射后,数据依然被正确封装到User
对象当中。
关联映射
在实际项目开发中,还有更加复杂的情况,例如执行的是一个多表查询语句,而返回的对象关联到另一个对象,此时简单地映射已经无法解决问题,必须使用resultMap
元素来完成关联映射
创建两个表格 tb_cazz和tb_student
进入mybatis
数据库,创建两个表tb_cazz
和tb_student
,并分别插入几条测试数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| use mybatis;
SET FOREIGN_KEY_CHECKS = 0;
drop table if exists tb_clazz;
create table tb_clazz( id int primary key auto_increment, code varchar(18) );
insert into tb_clazz(code) values ('B220203'); insert into tb_clazz(code) values ('B220102');
SET FOREIGN_KEY_CHECKS = 1;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| use mybatis;
SET FOREIGN_KEY_CHECKS = 0;
drop table if exists tb_student;
create table tb_student( id int primary key auto_increment, name varchar(18), sex char(3), age int, clazz_id int, foreign key(clazz_id) references tb_clazz(id) );
insert into tb_student(name, sex, age, clazz_id) values ('大美','男', 22, 1); insert into tb_student(name, sex, age, clazz_id) values ('小美','女', 18, 1); insert into tb_student(name, sex, age, clazz_id) values ('微美','男', 25, 2); insert into tb_student(name, sex, age, clazz_id) values ('巨美','女', 20, 2);
SET FOREIGN_KEY_CHECKS = 1;
|
以上SQL
语句插入了两个班级记录和4个学生记录,两个学生分配在1班,两个学生分配在2班。需要指出的是,tb_student
表中的clazz_id
列作为外键引用tb_clazz
表的id
列,表示学生对应的班级。
接下来我们要做的是:
查询出所有的学生信息,同时关联查询出学生对应的班级信息。
创建持久化对象
创建一个Clazz
对象和Student
对象并分别映射tb_clazz
表和tb_student
表。
Clazz.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class Clazz implements Serializable { private static final long serialVersionUID = 1L; private Integer id; private String code; private List<Student> students; public Clazz() { super(); } public List<Student> getStudents() { return students; } public void setStudents(List<Student> students) { this.students = students; } @Override public String toString() { return "Clazz [id=" + id + ", code=" + code + "]"; } }
|
Student.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Student implements Serializable { private static final long serialVersionUID = 1L; private Integer id; private String name; private String sex; private Integer age; private Clazz clazz; public Student() { super(); } @Override public String toString() { return "Student [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + ", clazz=" + clazz + "]"; } }
|
需要注意的是,Student
中的属性clazz
是Clazz
类的一个对象,clazz
对象的的属性有id
和code
。这是现代开发中最常用的对象关联方式。
映射配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
<select id="selectStudent" resultMap="studentResultMap"> SELECT * FROM TB_STUDENT </select>
<resultMap id="studentResultMap" type="org.fkit.domain.Student"> <id column="id" property="id" /> <result column="name" property="name" /> <result column="sex" property="sex" /> <result column="age" property="age" /> <association column="clazz_id" select="selectClazzWithId" property="clazz" javaType="org.fkit.domain.Clazz" /> </resultMap>
<select id="selectClazzWithId" resultType="org.fkit.domain.Clazz"> SELECT * FROM TB_CLAZZ where id = #{id} </select>
|
上面的映射相对之前复杂了一些,具体解释如下:
①首先执行id
为selectStudent
的select
元素,查询所有的学生数据,此时返回的不是简单的Student
对象,因为Student
对象中还包含了Clazz
对象,所以使用resultMap
去映射返回类型。
②id
为studentResultMap
的resultMap
元素返回类型为org.fkit.domain.Student
,其中,id
、name
、sex
和age
都是简单的属性映射,而查询的班级id
列clazz
则使用了关联映射association
association元素属性解释
association
元素的解释如下
column
。(子查询的参数)表示执行select语句返回的结果集中表的列名
select
。(子查询的位置)表示执行一条査询语句,将查询到的记录封装到property
所代表的类型对象当中。上面的selectClazzWithId
执行一条SQL
语句,将学生的clazz_id
作为参数查询对应的班级信息
property
。(接收子查询结果的成员变量)表示返回类型Student
的属性名clazz
。
javaType
。(接收子查询结果的成员变量的类型)表示该clazz
属性对应的类型名称,本示例是一个Clazz
类型。
SelectStudentTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package org.fkit.test;
import java.util.List; import org.apache.ibatis.session.SqlSession; import org.fkit.domain.Student; import org.fkit.factory.FKSqlSessionFactory;
public class SelectStudentTest {
public static void main(String[] args) { SqlSession sqlSession = null; try { sqlSession = FKSqlSessionFactory.getSqlSession(); List<Student> student_list = sqlSession.selectList("org.fkit.mapper.UserMapper.selectStudent"); student_list.forEach(stu -> System.out.println(stu)); sqlSession.commit(); } catch (Exception e) { sqlSession.rollback(); e.printStackTrace(); } finally { if (sqlSession != null) sqlSession.close(); } } }
|
运行结果
运行SelectStudentTest
类的main
方法,控制台显示如下:
控制台输出1 2 3 4 5 6 7 8 9 10 11 12 13
| DEBUG [main] ==> Preparing: SELECT * FROM TB_STUDENT DEBUG [main] ==> Parameters: DEBUG [main] ====> Preparing: SELECT * FROM TB_CLAZZ where id = ? DEBUG [main] ====> Parameters: 1(Integer) DEBUG [main] <==== Total: 1 DEBUG [main] ====> Preparing: SELECT * FROM TB_CLAZZ where id = ? DEBUG [main] ====> Parameters: 2(Integer) DEBUG [main] <==== Total: 1 DEBUG [main] <== Total: 4 Student [id=1, name=大美, sex=男, age=22, clazz=Clazz [id=1, code=B220203]] Student [id=2, name=小美, sex=女, age=18, clazz=Clazz [id=1, code=B220203]] Student [id=3, name=微美, sex=男, age=25, clazz=Clazz [id=2, code=B220102]] Student [id=4, name=巨美, sex=女, age=20, clazz=Clazz [id=2, code=B220102]]
|
可以看到,因为使用了关联映射,查询学生信息时学生对应的班级对象也被查询出来了。
集合映射
现在査询所有学生时可以关联查询出班级信息了,那如果反过来,查询所有班级时需要查询出班级中的所有学生对象,应该如何映射呢?
学生通常只对应一个班级,但是班级中会有多个学生存在,所以首先在Clazz.java
类中增加一个字段students
,该字段是一个List
集合,表示班级的多个学生。
1 2 3 4 5 6 7
| public class Clazz implements Serializable { private List<Student> students; }
|
映射配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
<select id="selectClazz" resultMap="clazzResultMap"> SELECT * FROM TB_CLAZZ </select>
<resultMap id="clazzResultMap" type="org.fkit.domain.Clazz"> <id column="id" property="id" /> <result column="code" property="code" /> <collection column="id" select="selectStudentWithId" property="students" javaType="ArrayList" ofType="org.fkit.domain.Student" /> </resultMap>
<select id="selectStudentWithId" resultType="org.fkit.domain.Student"> SELECT * FROM TB_STUDENT where clazz_id = #{id} </select>
|
上面的映射和查询学生关联班级类似,具体解释如下:
①首先执行id
为selectClazz
的select
元素,查询所有的班级数据,此时返回的不是简单的Clazz
对象,因为Clazz
对象中还包含了学生的集合对象,所以使用resultMap
去映射返回类型。
id
为clazzResultMap
素返回类型为org.fkit.domain.Clazz
其中,id
和code
都是简单的属性映射,而査询班级所有学生时则使用了集合映射collection
collection标签详解
collection
元素的解释如下:
column
。(子查询输入参数)表示使用select语句结果集中的id
字段作为参数进行之后的select
语句查询。
select
。(子查询语句位置)表示执行一条查询语句,将查询到的数据封装到property
所代表的类型对象当中。上面的selectStudentWithId
执行一条SQL
语句,将班级的id
作为参数查询班级对应的所有学生信息。
property
。(接收子查询结果的成员变量)表示返回类型Clazz
的属性名students
javaType
。(接收子查询结果的成员变量的类型)表示该属性对应的类型名称,本示例中是一个Arraylist
集合。
ofType
。(接收子查询结果的集合中的元素的类型)表示集合当中的类型,本示例中是Student
类型。
SelectClazzTest.java
D:\Desktop\随书源码\Spring+Mybatis企业应用实战(第2版)\codes\09\ResultMapTest\src\org\fkit\test\SelectClazzTest.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package org.fkit.test;
import java.util.List; import org.apache.ibatis.session.SqlSession; import org.fkit.domain.Clazz; import org.fkit.domain.Student; import org.fkit.factory.FKSqlSessionFactory;
public class SelectClazzTest{
public static void main(String[] args){ SqlSession sqlSession = null; try{ sqlSession = FKSqlSessionFactory.getSqlSession(); List<Clazz> clazz_list = sqlSession.selectList("org.fkit.mapper.UserMapper.selectClazz"); for(Clazz clazz:clazz_list){ System.out.println(clazz + ":"); List<Student> student_list = clazz.getStudents(); for(Student stu:student_list){ System.out.println(" " + stu.getId() + " " + stu.getName() + " " + stu.getSex() + " " + stu.getAge()); } System.out.println("-----------------"); System.out.println(); } sqlSession.commit(); }catch(Exception e){ sqlSession.rollback(); e.printStackTrace(); }finally{ if(sqlSession != null) sqlSession.close(); } } }
|
运行结果
运行SelectClazzTest
类的main
方法,控制台显示如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| DEBUG [main] ==> Preparing: SELECT * FROM TB_CLAZZ DEBUG [main] ==> Parameters: DEBUG [main] ====> Preparing: SELECT * FROM TB_STUDENT where clazz_id = ? DEBUG [main] ====> Parameters: 1(Integer) DEBUG [main] <==== Total: 2 DEBUG [main] ====> Preparing: SELECT * FROM TB_STUDENT where clazz_id = ? DEBUG [main] ====> Parameters: 2(Integer) DEBUG [main] <==== Total: 2 DEBUG [main] <== Total: 2 Clazz [id=1, code=B220203]: 1 大美 男 22 2 小美 女 18 -----------------
Clazz [id=2, code=B220102]: 3 微美 男 25 4 巨美 女 20 -----------------
|
可以看到,因为使用了集合映射,所以査询班级信息时班级对应的所有学生对象也被查询出来了。