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;
private Long userId;
private List<CommentPO> comments;
}
public class CommentPO implements Serializable {
private Long id;
private Long blogId;
private Long userId;
private String content;
private BlogPO blog; }
|
循环依赖问题解决
先通过一张泳道图来了解下Mybatis是如何通过延迟加载机制来处理循环依赖问题的。
简单来说,Mybatis是基于一个queryStack数字、一级缓存、延迟加载器实现的解决循环依赖问题的方案。

上面一张图已经将流程描述的很清晰了,我们再简单看下每一个步骤的源码。
BaseExecutor
这段方法我们之前已经贴过很多次了,对于解决循环依赖这个场景来说,可以总结为四个步骤
- 累计queryStack
- 查询数据库或缓存
- queryStack减一
- 遍历迭代器,完成数据加载
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++; 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--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { 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; final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); if (propertyMapping.getNestedResultMapId() != null) { 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); 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()) { 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); 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 { final String nestedQueryId = propertyMapping.getNestedQueryId(); final String property = propertyMapping.getProperty(); 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) { 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()方法完成赋值;
返回处理结果;