在学习MyBatis的时候,我查阅了【深入浅出MyBatis系列三】Mapper映射文件配置 - 陶邦仁的个人空间 - OSCHINA这篇文章,里面讲到了<resultMap>中的<id>,但是并没有详细地解释它的作用,于是就有了这篇文章。
准备
我们先创建teacher
和student
表,一个老师可以对应多个学生。
create table teacher
(
id int,
name varchar(100)
);
create table student
(
id int,
name varchar(100),
teacher_id int
);
初始化数据
-- 注意两个老师的名字是一样的
INSERT INTO guiderank_server.teacher (id, name)
VALUES (1, 'Jason'),
(2, 'Jason');
-- id为1的老师关联2个学生,id为2的老师关联4个学生
INSERT INTO guiderank_server.student (id, name, teacher_id)
VALUES (1, 's_1', 1),
(2, 's_2', 1),
(3, 's_3', 2),
(4, 's_4', 2),
(5, 's_5', 2),
(6, 's_6', 2);
Teacher
类
@Data
public class Teacher {
private Integer id;
private String name;
private List<Student> students;
}
Student
类
@Data
public class Student {
private Integer id;
private String name;
}
我们希望将
select t.id as t_id, t.name as t_name, s.id as s_id, s.name as s_name
from teacher t
join student s on t.id = s.teacher_id;
-- 执行结果为:
+------+--------+------+--------+
| t_id | t_name | s_id | s_name |
+------+--------+------+--------+
| 1 | Jason | 1 | s_1 |
| 1 | Jason | 2 | s_2 |
| 2 | Jason | 3 | s_3 |
| 2 | Jason | 4 | s_4 |
| 2 | Jason | 5 | s_5 |
| 2 | Jason | 6 | s_6 |
+------+--------+------+--------+
的执行结果转变为List<Teacher> teachers
,期望的结果是:teachers
有两个元素,分别对应id为1和2的老师,并且id为1的老师的students
的大小为2,id为2的老师的students
的大小为4。
<id>是用来干嘛的?
<id>就是用来解决上面所说的问题的,也就是说<id>可以用来实现聚集。为了将查询结果转变为我们期望的List<Teacher> teachers
,我们需要定义如下的<resultMap>。
<resultMap id="resultMap" type="Teacher">
<id column="t_id" property="id"/>
<result column="t_name" property="name"/>
<collection property="students" ofType="Student">
<result column="s_id" property="id"/>
<result column="s_name" property="name"/>
</collection>
</resultMap>
<id> VS <result>
<id>和<result>有什么区别呢?我们来做一下实验吧。
- 将
<id column="t_id" property="id"/>
改为<result column="t_id" property="id"/>
。
结果还是:teachers
有两个元素,分别对应id
为1和2
的老师,并且id
为1的老师的students
的大小为2,id
为2的老师的students
的大小为4。
- 将
<result column="t_name" property="name"/>
改为<id column="t_name" property="name"/>
并将其与<id column="t_id" property="id"/>
调换位置。
结果变了,现在teachers
只有一个元素,这个唯一的老师的id为1,它拥有六个学生。
为什么会出现这样的情况呢?请看接下来的原理分析。
原理
SQL的查询结果是这样子的。
+------+--------+------+--------+
| t_id | t_name | s_id | s_name |
+------+--------+------+--------+
| 1 | Jason | 1 | s_1 |
| 1 | Jason | 2 | s_2 |
| 2 | Jason | 3 | s_3 |
| 2 | Jason | 4 | s_4 |
| 2 | Jason | 5 | s_5 |
| 2 | Jason | 6 | s_6 |
+------+--------+------+--------+
那么MyBatis是怎么将它转变为List<Teacher> teachers
的呢?而且为什么会有上面所描述的那些种种表现呢?这一切都跟ResultSetHandler.handleResultSets(Statement stmt)
有关,这个方法是用来处理结果集的。
在看代码之前,先讲一下大概的处理过程。其实可以将整个过程理解为rowStream.collect(Collectors.groupingBy(row -> row.getTeacherId, Collectors.toList()))
,也就是说使用teacherId进行分组,每个组的学生形成teacher.students。
现在你应该明白上面所说的那些现象的原因吧。1. 将<id column="t_id" property="id"/>
改为<result column="t_id" property="id"/>
,结果没变化。这是因为在没有配置<id>时,MyBatis默认将所有<result>作为一个分组的依据,也就是t_id和t_name都相同的行为一组。2. 将<result column="t_name" property="name"/>
改为<id column="t_name" property="name"/>
并将其与<id column="t_id" property="id"/>
调换位置,结果只有一个元素,这是因为虽然存在1和2这两个t_id,但是t_name都是一样的,而此时MyBatis是使用t_name作为分组的依据。
现在你应该明白了大概的思路,那我们看看具体的代码吧。
//
// HANDLE RESULT SETS
//
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>(); // 存储转换结果,每个元素为resultSet的转换结果。此例中,multipleResults只有一个元素,就是我们所说的`List<Teacher> teachers`
int resultSetCount = 0; // 语句可能有多个resultSet,每个resultSet都需要被对应的resultMap转换。这里初始化resultSetCount为0
ResultSetWrapper rsw = getFirstResultSet(stmt); // 获取第一个结果集
List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) { // 循环,使用对应的resultMap处理resultSet
ResultMap resultMap = resultMaps.get(resultSetCount); // 获取对应的resultMap
handleResultSet(rsw, resultMap, multipleResults, null); // 处理结果集,并将处理结果加进multipleResults
rsw = getNextResultSet(stmt); // 将rsw设置为下一个结果集
cleanUpAfterHandlingResultSet();
resultSetCount++; // 改变resultSetCount
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults); // 如果multipleResults只有一个元素。直接返回那个元素,否则返回multipleResults
}
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); // 处理结果集,并将转换结果存储在defaultResultHandler
multipleResults.add(defaultResultHandler.getResultList()); // 将defaultResultHandler中的转换结果加进multipleResults
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
//
// HANDLE ROWS FOR SIMPLE RESULTMAP
//
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) { // 含有内嵌resultMap
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); // 处理含有内嵌resultMap的结果集,并将转换结果存储在resultHandler
} else {
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
//
// HANDLE NESTED RESULT MAPS
//
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
skipRows(resultSet, rowBounds);
Object rowValue = previousRowValue;
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null); // 它的值类似于`-1885500083:-515674352:BookMapper.resultMap:t_id:1`,注意后面的`t_id:1`,这就是id为1的老师的标识呀
Object partialObject = nestedResultObjects.get(rowKey); // 获取id为1的teacher对象,为什么叫partialObject呢?因为这个对象可能不是完整的,它可能为null,也就是说第一次碰到这个老师,也可能是关联了一个或多个学生的老师
// issue #577 && #542
if (mappedStatement.isResultOrdered()) {
if (partialObject == null && rowValue != null) {
nestedResultObjects.clear();
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
} else {
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject); // 如果partialObject为null,将一行数据转换为一个teacher,这个teacher的students只有一个元素,并且会修改nestedResultObjects,用来记录teacherId和teacher对象的关联关系;如果partialObject不为null,不再创建teacher,直接将一行数据对应的学生加进已有的students
if (partialObject == null) { // 第一次碰到这个老师
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); // 将这个老师加进在resultHandler
}
}
}
if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
previousRowValue = null;
} else if (rowValue != null) {
previousRowValue = rowValue;
}
}
//
// GET VALUE FROM ROW FOR NESTED RESULT MAP
//
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
final String resultMapId = resultMap.getId();
Object rowValue = partialObject;
if (rowValue != null) { // rowValue不为null,也就是说partialObject不为null。可以理解为已经存在这个老师
final MetaObject metaObject = configuration.newMetaObject(rowValue);
putAncestor(rowValue, resultMapId);
applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false); // 直接处理内嵌结果映射,不再创建老师,而是直接往老师的学生列表加入一个学生(其实加入学生也会判断是否重复,本文不讲解,有兴趣自己去看看😬)
ancestorObjects.remove(resultMapId);
} else { // rowValue为null,也就是说partialObject为null。可以理解为还不存在这个老师
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); // 创建一个结果对象,也就是一个teacher
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, true)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; // 处理属性映射,也就是设置teacher的id和name
putAncestor(rowValue, resultMapId);
foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues; // 处理内嵌结果映射,也就是设置teacher的students
ancestorObjects.remove(resultMapId);
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
if (combinedKey != CacheKey.NULL_CACHE_KEY) {
nestedResultObjects.put(combinedKey, rowValue); // 将combinedKey和rowValue关联起来,可以理解为将老师的id跟老师对象关联起来
}
}
return rowValue;
}
现在你应该清楚之前所说的那些现象背后的原因吧。