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.sql 1 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.xml 1 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.xml 1 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.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 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 -----------------
可以看到,因为使用了集合映射,所以査询班级信息时班级对应的所有学生对象也被查询出来了。