0%

9.4.5 ResultMaps

9.4.5 ResultMaps

resultMap元素是MyBatis中最重要最强大的元素。它的作用是告诉MyBatis将从结果集中取出的数据转换成开发者所需要的对象。

每条记录转换成一个Map

下面是最简单的映射语句示例:

1
2
3
<select id="selectUser" resultType="map">
select * from tb_user
</select>
  • selectUserselect元素执行一条查询语句,查询tb_user表的所有数据。
  • resultType="map"表示返回的每条数据是都是一个Map集合(使用这条记录的列名作为key,列值作为value)。

示例: 测试ResultMaps

简单映射 一条记录封装成map

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
public class SelectMapTest {
public static void main(String[] args)
{
// 1.定义SqlSession变量
SqlSession sqlSession = null;
try
{
// 2.创建SqlSession实例
sqlSession = FKSqlSessionFactory.getSqlSession();
// 3.查询TB_USER表所有记录,每一条记录都封装Map,
// 该记录的属性作为key,属性值作为value
List<Map<String, Object>> list = sqlSession
.selectList("org.fkit.mapper.UserMapper.selectUser");
// 遍历List集合,打印每一个Map对象
list.forEach(row -> System.out.println(row));
// 4.提交事务
sqlSession.commit();
} catch (Exception e)
{
// 5.回滚事务
sqlSession.rollback();
e.printStackTrace();
} finally
{
// 关闭SqlSession
if (sqlSession != null)
sqlSession.close();
}
}
}

对应的select标签

1
2
3
4
5
6
7
<!-- select操作
resultType="map"返回的每条记录都封装成一个Map
对于每条记录,使用这条记录的列名做key,值做value -->
<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 |
+----+------+-----+-----+
| 2 | 小丽 | 男 | 22 |
| 3 | 小李 | 男 | 22 |
| 4 | 小王 | 女 | 20 |
| 5 | 小明 | 女 | 20 |
+----+------+-----+-----+
4 rows in set

运行结果

运行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=小丽, id=2, age=22}
{sex=男, name=小李, id=3, age=22}
{sex=女, name=小王, id=4, age=20}
{sex=女, name=小明, id=5, age=20}

可以看到,査询语句返回的每一条记录都被封装成一个Map集合,这条记录的列名作为Map集合的key,而列的值作为Mapvalue.

简单映射 每条记录封装成一个持久化对象

虽然数据被封装成Map集合返回,但是Map集合并不能很好地描述一个领域模型。在实际项目开发中更加建议使用JavaBeanPOJO(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标签进行处理.

创建数据库表

进入mybatis数据库,创建一个表tb_user2,并插入几条测试数据:
cmd命令行中输入下面的sql语句进行创建,注意是在cmd命令行,如果使用Navicat的话先保存成sql文件,然后通过导入SQL文件的方式创建,不要使用Navicat的命令行界面,因为直接复制粘贴下面代码插入数据的时候,会出现乱码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set names "gbk";
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对象映射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();
// TODO Auto-generated constructor stub
}
public User(String name, String sex, Integer age)
{
super();
this.name = name;
this.sex = sex;
this.age = age;
}
// 此处省略getter和setter方法,请自己补上
@Override
public String toString()
{
return "User [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + "]";
}
}

创建resultMap标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 映射数据库表的列名和持久化对象的属性名 -->
<!-- 用在两者不同名的情况 -->
<resultMap
id="userResultMap"
type="org.fkit.domain.User">
<id
property="id"
column="user_id"/>
<result
property="name"
column="user_name"/>
<result
property="sex"
column="user_sex"/>
<result
property="age"
column="user_age"/>
</resultMap>
<!-- 使用自定义的映射 -->
<select
id="selectUser2"
resultMap="userResultMap"> SELECT * FROM TB_USER2
</select>
resultMap标签详解

上面使用了一个新的元素resultMap,该元素常用属性如下

  • resultMap元素的id属性,是resultMap的唯一标识符。
  • resultMap元素的type属性,表示resultMap实际返回的类型。

上面使用了resultMap的两个子元素idresult

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 sqlSession = null;
try
{
// 1.创建SqlSession实例
sqlSession = FKSqlSessionFactory.getSqlSession();
//
List<User> user_list = sqlSession.selectList("org.fkit.mapper.UserMapper.selectUser2");
// 遍历List集合,打印每一个Map对象
user_list.forEach(user -> System.out.println(user));
// 提交事务
sqlSession.commit();
} catch (Exception e)
{
// 回滚事务
sqlSession.rollback();
e.printStackTrace();
} finally
{
// 关闭SqlSession
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_USER的列名虽然和User对象的属性名不一致,数据依然被正确封装到User对象当中。

关联映射

在实际项目开发中,还有更加复杂的情况,例如执行的是一个多表查询语句,而返回的对象关联到另一个对象,此时简单地映射已经无法解决问题,必须使用resultMap元素来完成关联映射
进入mybatis数据库,创建两个表tb_cazztb_student,并分别插入几条测试数据:
此处代码后面补上…
以上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();
// TODO Auto-generated constructor stub
}
// 此处省略getter和setter方法,请自己补上
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;
// 关联的Clazz对象
private Clazz clazz;
public Student()
{
super();
// TODO Auto-generated constructor stub
}
// 此处省略getter和setter方法,请自己补上
@Override
public String toString()
{
return "Student [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + ", clazz="
+ clazz + "]";
}
}

需要注意的是,Student中的属性Clazz是一个对象,该对象包括Clazzidcode这是现代开发中最常用的对象关联方式。

映射配置

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
<select
id="selectStudent"
resultMap="studentResultMap"> SELECT * FROM TB_STUDENT
</select>
<!-- 映射学生对象的resultMap -->
<resultMap
id="studentResultMap"
type="org.fkit.domain.Student">
<id
property="id"
column="id"/>
<result
property="name"
column="name"/>
<result
property="sex"
column="sex"/>
<result
property="age"
column="age"/>
<!-- 关联映射 -->
<association
property="clazz"
column="clazz_id"
javaType="org.fkit.domain.Clazz"
select="selectClazzWithId"/>
</resultMap>
<!-- 根据班级id查询班级 -->
<select
id="selectClazzWithId"
resultType="org.fkit.domain.Clazz"> SELECT * FROM TB_CLAZZ where id = #{id}
</select>

上面的映射相对之前复杂了一些,具体解释如下:
①首先执行idselectstudentselect元素,查询所有的学生数据,此时返回的不是简单的Student对象,因为Student对象中还包含了Clazz对象,所以使用resultMap去映射返回类型。
idstudentResultMapresultMap元素返回类型为org.fkit.domain.Student,其中,idnamesexage都是简单的属性映射,而查询的班级idclazz则使用了关联映射association
assoclation元素的解释如下

  • column。表示数据库表的列名
  • property。表示返回类型Student的属性名clazz
  • javaType。表示该clazz属性对应的类型名称,本示例是一个Clazz类型。
  • select。表示执行一条査询语句,将查询到的记录封装到property所代表的类型对象当中。上面的selectClazzWithId执行一条SQL语句,将学生的clazz_id作为参数查询对应的班级信息

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
public class SelectStudentTest {
public static void main(String[] args)
{
// 定义SqlSession变量
SqlSession sqlSession = null;
try
{
// 创建SqlSession实例
sqlSession = FKSqlSessionFactory.getSqlSession();
// 查询TB_USER表所有数据返回List集合,集合中的每个元素都是一个Student对象
List<Student> student_list = sqlSession
.selectList("org.fkit.mapper.UserMapper.selectStudent");
// 遍历List集合,打印每一个Student对象,该对象包含关联的Clazz对象
student_list.forEach(stu -> System.out.println(stu));
// 提交事务
sqlSession.commit();
} catch (Exception e)
{
// 回滚事务
sqlSession.rollback();
e.printStackTrace();
} finally
{
// 关闭SqlSession
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=B180102]]
Student [id=2, name=小王, sex=男, age=22, clazz=Clazz [id=2, code=B180203]]
Student [id=3, name=小丽, sex=男, age=21, clazz=Clazz [id=2, code=B180203]]
Student [id=4, name=小芳, sex=男, age=23, clazz=Clazz [id=1, code=B180102]]

可以看到,因为使用了关联映射,查询学生信息时学生对应的班级对象也被查询出来了。

集合映射

现在査询所有学生时可以关联查询出班级信息了,那如果反过来,查询所有班级时需要查询出班级中的所有学生对象,应该如何映射呢?
学生通常只对应一个班级,但是班级中会有多个学生存在,所以首先在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 -->
<resultMap
id="clazzResultMap"
type="org.fkit.domain.Clazz">
<id
property="id"
column="id"/>
<result
property="code"
column="code"/>
<!-- 班级的学生属性,因为一个班级有多个学生,所以该属性是一个集合 -->
<collection
property="students"
javaType="ArrayList"
column="id"
ofType="org.fkit.domain.Student"
select="selectStudentWithId"/>
</resultMap>
<!-- 根据班级id查询学生 -->
<select
id="selectStudentWithId"
resultType="org.fkit.domain.Student"> SELECT * FROM TB_STUDENT where clazz_id = #{id}
</select>

上面的映射和查询学生关联班级类似,具体解释如下:
①首先执行idselectClazzselect元素,查询所有的班级数据,此时返回的不是简单的Clazz对象,因为Clazz对象中还包含了学生的集合对象,所以使用resultMap去映射返回类型。
idclazzResultMap素返回类型为org.fkit.domain.Clazz
其中,idcode都是简单的属性映射,而査询班级所有学生时则使用了集合映射collection

collection标签详解

  • collection元素的解释如下:
  • property。表示返回类型Clazz的属性名students
  • javaType。表示该属性对应的类型名称,本示例中是一个Arraylist集合。
  • ofType。表示集合当中的类型,本示例中是Student类型。
  • column。表示使用id作为参数进行之后的select语句查询。
  • select。表示执行一条查询语句,将查询到的数据封装到property所代表的类型对象当中。上面的selectStudentWithId执行一条SQL语句,将班级的id作为参数查询班级对应的所有学生信息。

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
public class SelectClazzTest {
public static void main(String[] args)
{
// 定义SqlSession变量
SqlSession sqlSession = null;
try
{
// 创建SqlSession实例
sqlSession = FKSqlSessionFactory.getSqlSession();
// 查询TB_CLAZZ表所有数据返回List集合,集合中的每个元素都是一个Clazz对象
List<Clazz> clazz_list = sqlSession
.selectList("org.fkit.mapper.UserMapper.selectClazz");
// 遍历List集合,打印每一个Clazz对象和该Clazz关联的所有Student对象
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());
}
}
// 提交事务
sqlSession.commit();
} catch (Exception e)
{
// 回滚事务
sqlSession.rollback();
e.printStackTrace();
} finally
{
// 关闭SqlSession
if (sqlSession != null)
sqlSession.close();
}
}
}

SelectClazzTest类的main方法,控制台显示如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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=B180102]
1 小明 男 22
4 小芳 男 23
Clazz [id=2, code=B180203]
2 小王 男 22
3 小丽 男 21

以看到,因为使用了集合映射,所以査询班级信息时班级对应的所有学生对象也被查询出来了。

原文链接: 9.4.5 ResultMaps