resultMap映射学习
博客作为个人学习笔记,随着学习深入更新。
接上一篇resultType映射学习
(1)Question:resultMap是什么
resultMap与resultType类似,也是MyBatis框架中的一种映射方式。但是与resultMap不同的是,其映射规则由用户自己定义,比resultType更加灵活。不过还有很重要的一个方面就是,在返回数据的形式上,resultMap可以去除不必要的冗余。
(2)resultMap实际使用
数据库中有这样一张表school
class_name | teacher | mobile | name | id |
---|---|---|---|---|
六年一班 | 阿福 | 13131313131 | 小夫 | 1 |
六年一班 | 阿福 | 13131313131 | 大熊 | 2 |
六年一班 | 阿福 | 13131313131 | 静香 | 3 |
需求:客户端需要读取到六年一班所有学生的姓名与学号
xml文件关键语句如下
<mapper namespace="com.test.MybatisDao">
<resultMap id="getStudentMap" type="com.test.Class">
<id property="id" column="id"/>
<result property="name" column="name"/>
</resultMap>
<select id="get" resultMap="getStudentMap">
select id, name from school
</select>
这里明确一点,property为映射结果中的属性,而column是查询回结果集中的列名
映射结果集为
class Class {
private Integer id;
private String name;
}
执行sql语句后,可以得到这样一个查询结果集
id | name |
---|---|
1 | 小夫 |
2 | 大熊 |
3 | 静香 |
该结果集经过resultMap映射后,返回前端的形式如下
[
{
"id": 1,
"name": "小夫"
},
{
"id": 2,
"name": "大熊"
},
{
"id": 3,
"name": "静香"
}
]
这是一个简单的结果集映射,如果要深入探究resultMap映射,那么可以做一个较为复杂的映射。
高级映射
需求:客户需要知道查询班级的老师、老师的号码以及所有学生的信息
修改school表如下
class_name | teacher | mobile | name | id |
---|---|---|---|---|
六年一班 | 阿福 | 13131313131 | 小夫 | 1 |
六年一班 | 阿福 | 13131313131 | 大熊 | 2 |
六年一班 | 阿福 | 13131313131 | 静香 | 3 |
六年二班 | 特鲁 | 13131313132 | 胖虎 | 1 |
六年二班 | 特鲁 | 13131313132 | 小新 | 2 |
六年三班 | 老爹 | 13131313133 | 小玉 | 1 |
xml关键语句如下
<resultMap id="studentMap" type="com.thingcom.mybatis.Class">
<id property="className" column="class_name"/>
<result property="teacher" column="teacher"/>
<result property="mobile" column="mobile"/>
<collection property="student" ofType="com.thingcom.mybatis.Student">
<id property="id" column="id"/>
<result property="name" column="name"/>
</collection>
</resultMap>
<select id="get" resultMap="getStudentMap">
select class_name, teacher, mobile, id, name from school
where class_name = #{className}
</select>
映射结果集如下
@Data
class Class {
private String className;
private String teacher;
private String mobile;
private Student student;
}
@Data
class Student {
private Integer id;
private String name;
}
结果查询返回这样一张表
class_name | teacher | mobile | id | name |
---|---|---|---|---|
六年一班 | 阿福 | 13131313131 | 1 | 小夫 |
六年一班 | 阿福 | 13131313131 | 2 | 大熊 |
六年一班 | 阿福 | 13131313131 | 3 | 静香 |
返回前端的结果集如下
[
{
"className": "六年一班",
"teacher": "阿福",
"mobile": "13131313131",
"student": {
"id": 3,
"name": "静香"
}
}
]
可以看到,结果并不是我们期望的,从数据库查询的内容没有问题,问题一定是出现在resultMap映射上。
(3)查看result映射机制
首先在说明result是如何映射时,我们要先了解一个规范----JDBC
JDBC(Java Database Connectivity)是用于连接数据库与java的一道桥梁,由一些java编写的类和接口组成,使用spring框架的可以在yml文件中看到这一句
driver-class-name: com.mysql.cj.jdbc.Driver
这是使用jdbc驱动与数据库连接
值得关注的是在jdbc中有一个resultSet接口,其中有一个next方法可以用来遍历resultSet结果集,即resultSet.next()每一次执行都会取结果集中的下一行,该方法返回类型为boolean。此时可以通过getString()方法获取指定列的值,这样就可以将结果集中的值取出,在处理完结果集后,jdbc会释放资源,此时ResultSet结果集就被清空。
DBUtil dbUtil = new DBUtil();
Connection con = dbUtil.getconnection();
String sql = "select id from table"
PreparedStatement psta = con.prepareStatement(sql);
ResultSet rs = new psta.executeQuery();
while(rs.next()) {
int id = rs.getString("id");
int id = rs.getString(1);
Class test = new Class();
test.setId(id);
}
getString方法中的参数可以是列的位置,也可以是列的键,在ResultSet中结果集的列是从1开始计算的。在这里可以看出,如果存储结果集的类不是一个list的话,就会导致数据被覆盖,显示的数据就会是最新的那一条。
在jdbc中只需要加上这一句即可
testList.add(test);
mybatis将jdbc封装后这种思想也是一样,映射规则也是按行取出,因此将映射结果集代码修改如下
@Data
class Class {
private String className;
private String teacher;
private String mobile;
private List<Student> student;
}
从映射结果如下
[
{
"className": "六年一班",
"teacher": "阿福",
"mobile": "13131313131",
"student": [
{
"id": 1,
"name": "小夫"
},
{
"id": 2,
"name": "大熊"
},
{
"id": 3,
"name": "静香"
}
]
}
]
此时回到resultType上,按照这个想法,假设resultType要处理这样的映射,需不需要将第二层封装设置为List呢?
答案是这样处理回导致无法映射进结果集
xml配置
<select id="get" resultType="com.thingcom.mybatis.Class">
select class_name "className", teacher, mobile, id "student.id", name "student.name" from school
where class_name = #{className}
</select>
返回结果集
[
{
"className": "六年一班",
"teacher": "阿福",
"mobile": "13131313131",
"student": null
},
{
"className": "六年一班",
"teacher": "阿福",
"mobile": "13131313131",
"student": null
},
{
"className": "六年一班",
"teacher": "阿福",
"mobile": "13131313131",
"student": null
}
]
可以看到,第二层的信息没有映射进结果集,根据jdbc的思想,Class类会创建一个对象去取className、teacher、mobile的值,最后通过list.add方法处理;Student类也会创建一个对象取id、name,但是问题在于这个对象是取不到id和name的值的,因为在处理sql语句中,将id、name重命名为student.id、student.name了。因此resultType无法做到像resultMap那样灵活。
(4)合并
对比resultMap和resultType返回结果集,可以发现resultMap会把相同的字段合并。既然在jdbc那里了解到result结果集映射是按行返回的。那么可以想到,在从resultSet中取回信息时,resultMap会根据键值来判断可不可以合并,没有设置主键则所有键值相同则判断为可以合并,设置主键,会根据主键值来判断。所以在设置映射时设置主键可以减少映射时间。
(5)再加一层
现在将表修改如下
class_name | teacher | mobile | name | id | home_name |
---|---|---|---|---|---|
六年一班 | 阿福 | 13131313131 | 小夫 | 1 | a |
六年一班 | 阿福 | 13131313131 | 大熊 | 2 | b |
六年一班 | 阿福 | 13131313131 | 静香 | 3 | c |
六年二班 | 特鲁 | 13131313132 | 胖虎 | 1 | d |
六年二班 | 特鲁 | 13131313132 | 小新 | 2 | e |
六年三班 | 老爹 | 13131313133 | 小玉 | 1 | f |
需求与(3)相同
xml修改如下
<resultMap id="studentMap" type="com.thingcom.mybatis.Class">
<id property="className" column="class_name"/>
<result property="teacher" column="teacher"/>
<result property="mobile" column="mobile"/>
<collection property="student" ofType="com.thingcom.mybatis.Student">
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="home" ofType="com.thingcom.mybatis.Home">
<id property="homeName" column="home_name"/>
</collection>
</collection>
</resultMap>
<select id="get" resultMap="studentMap">
select class_name , teacher, mobile, id, name, home_name from school
where class_name = #{className}
</select>
添加映射结果集
@Data
class Student {
private Integer id;
private String name;
private Home home;
}
@Data
class Home{
private String homeName;
}
映射结果如下
[
{
"className": "六年二班",
"teacher": "特鲁",
"mobile": "13131313132",
"student": [
{
"id": 1,
"name": "胖虎",
"home": {
"homeName": "d"
}
},
{
"id": 2,
"name": "小新",
"home": {
"homeName": "e"
}
}
]
}
]
了解了映射原理之后,再写也就简单多了