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()方法完成赋值;
返回处理结果;