A依赖B B又依赖A所构成的一种循环,也可以称为循环依赖,试想下这个场景在MyBatis中会怎样?如果不管的话那就是死循环了。

循环依赖流程

循环依赖流程

Mybatis是通过延迟加载来解决循环依赖问题的。

Demo实例

在下面例子中,BlogMap中有评论信息,而评论信息中又反过来包含博客信息,两者互相依赖。

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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mybatis_reader.mybatis.blog.BlogMapper">

<cache/>
<resultMap id="BlogMap" type="org.example.mybatis_reader.mybatis.blog.BlogPO" autoMapping="true">
<id column="ID" property="id" jdbcType="INTEGER"/>
<result column="TITLE" property="title" jdbcType="VARCHAR"/>
<collection property="comments" column="id" select = "selectCommentByBlogId" fetchType="eager"/>
</resultMap>

<resultMap id="commentMap" type="org.example.mybatis_reader.mybatis.blog.CommentPO" autoMapping="true" >
<id column="ID" property="id" jdbcType="INTEGER"/>
<result column="BLOG_ID" property="blogId" jdbcType="INTEGER"/>
<result column="CONTENT" property="content" jdbcType="VARCHAR"/>
<association column="BLOG_ID" property="blog" select="selectBlogById" fetchType="eager"/>
</resultMap>

<select id="selectBlogById" resultMap="BlogMap">
SELECT * FROM T_BLOG WHERE ID = #{id}
</select>

<select id="selectCommentByBlogId" resultMap="commentMap">
SELECT * FROM T_COMMENT WHERE BLOG_ID = #{id}
</select>
</mapper>
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
44
45
46
47
48
49
public class BlogPO implements Serializable {

/**
*
*/
private Long id;
/**
* 标题
*/

private String title;
/**
* 内容
*/
private String content;
/**
* 用户ID
*/
private Long userId;
/**
* 评论集合
*/
private List<CommentPO> comments;

}

public class CommentPO implements Serializable {

private Long id;
/**
* 评论id
*/

private Long blogId;
/**
* 用户id
*/

private Long userId;
/**
* 评论内容
*/
private String content;

/**
* 博客信息
*/
private BlogPO blog;
}

循环依赖问题解决

先通过一张泳道图来了解下Mybatis是如何通过延迟加载机制来处理循环依赖问题的。

简单来说,Mybatis是基于一个queryStack数字、一级缓存、延迟加载器实现的解决循环依赖问题的方案。

延迟加载解决循环依赖图-3

上面一张图已经将流程描述的很清晰了,我们再简单看下每一个步骤的源码。

BaseExecutor

这段方法我们之前已经贴过很多次了,对于解决循环依赖这个场景来说,可以总结为四个步骤

  1. 累计queryStack
  2. 查询数据库或缓存
  3. queryStack减一
  4. 遍历迭代器,完成数据加载
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
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
// 累计queryStack
queryStack++;
// 从缓存中获取值
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) { // 处理缓存中有值的情况
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else { // 缓存中没值,查询数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 查完之后,对queryStack做减一操作
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) { // 遍历延迟加载器
deferredLoad.load(); // 调用load方法完成赋值操作
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}

DefaultResultSetHandler

我们知道这个类是做结果集处理的,而且这个类的实现逻辑十分复杂,所以我们截取并分析关键流程的代码。

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
44
45
46
47
48
49
50
51
52
53
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
final Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
// 获取所有的ResultMapping,resultMap中配置的每个属性都会对应一个ResultMapping
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) { // 依次遍历ResultMapping
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))
|| propertyMapping.getResultSet() != null) {
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader,
columnPrefix);
// issue #541 make property optional
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
}
if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null
|| configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(property, value);
}
}
}
return foundValues;
}

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
if (propertyMapping.getNestedQueryId() != null) { // 如果属性下有子查询
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
}
if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}

关键代码都维护在getNestedQueryMappingValue()中

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
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
// 获取嵌套查询的ID
final String nestedQueryId = propertyMapping.getNestedQueryId();
// 获取属性名
final String property = propertyMapping.getProperty();
// 获取嵌套查询的MappedStatement
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
// 获取嵌套查询参数的类型
final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
// 准备嵌套查询的参数
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping,
nestedQueryParameterType, columnPrefix);
Object value = null;
if (nestedQueryParameterObject != null) {
// 获取嵌套查询的BoundSql
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
// 创建缓存键
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
nestedBoundSql);
final Class<?> targetType = propertyMapping.getJavaType();
if (executor.isCached(nestedQuery, key)) { // 缓存中是否有值
// 如果在缓存中,延迟加载结果
executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
value = DEFERRED;
} else {
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
nestedQueryParameterObject, targetType, key, nestedBoundSql);
if (propertyMapping.isLazy()) { // 是否是懒加载
// 如果需要懒加载,添加结果加载器到懒加载器
lazyLoader.addLoader(property, metaResultObject, resultLoader);
value = DEFERRED;
} else {
// 如果不需要懒加载,立即加载结果
value = resultLoader.loadResult();
}
}
}
return value;
}

最后看下延迟加载的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;

@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key,
Class<?> targetType) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 构造延迟加载器
DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
if (deferredLoad.canLoad()) { //是否能加载
// 实时加载
deferredLoad.load();
} else {
// 将延迟加载器放入队列中
deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
}
}

总结

至此,Mybatis已经解决了循环依赖问题,为了加深印象,我们再以上面的Demo描述一下整个流程。

我们假定第一个SQL为A,第二个SQL为B,整个流程为A–>B–>A

1
2
3
SELECT * FROM T_BLOG WHERE ID = #{id}

SELECT * FROM T_COMMENT WHERE BLOG_ID = #{id}

A

queryStack加1,此时结果为1;

一级缓存中值为NULL,执行查库操作;

设置一级缓存,value为一个占位符;

处理结果集,发现有子查询B,拼装B需要的条件;

生成B的缓存KEY,看KEY是否在缓存中有值;此时肯定没值,也未开启懒加载,通过执行器执行B

B

queryStack加1,此时结果为2;

一级缓存中值为NULL,执行查库操作;

设置一级缓存,value为一个占位符;

处理结果集,发现有子查询A,拼装A需要的条件;

生成A的缓存KEY,看KEY是否在缓存中有值;有值,但值是一个占位符:“EXECUTION_PLACEHOLDER”,生成延迟加载对象,并放入队列(deferredLoads)中;

设置一级缓存;

queryStack减一,此时结果为1

返回处理结果;

A

再接着执行A的逻辑。设置A的一级缓存,然后queryStack减一,此时结果为0,触发延迟加载器的执行逻辑;

遍历延迟加载器并调用load()方法完成赋值;

返回处理结果;