MyBatis循环依赖问题是怎么解决的?

问题分析

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

demo

<resultMap id="authorMap" type="org.apache.ibatis.demo.Author">
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <collection property="book" column="book_id" select="selectBookByid" fetchType="eager"/>
</resultMap>

<resultMap id="bookMap" type="org.apache.ibatis.demo.Book">
    <result column="id" property="id"></result>
    <result column="name" property="name"></result>
    <collection property="author" column="author_id" select="selectAuthorByid" fetchType="eager"></collection>
</resultMap>

<select id="selectAuthorByid" resultMap="authorMap">
    select * from author where id = #{id}
</select>

<select id="selectBookByid" resultMap="bookMap">
    select * from  book where id = #{id}
</select>

结果可以看到每个SQL只查询一次,看到这个结果应该就可以猜到用什么方式解决了吧?没错用的就是缓存预先占位处理,如果重复查询就不去数据库查询了,而是直接返回缓存的引用!!!

MyBatis采用空占位符的方式

//从数据库查
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  // 缓存中放入占位符
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  // 查询完加入缓存
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

如果是嵌套查询中间有一段逻辑处理,可以理解为映射到需要嵌套的属性时,创建Executor.query方法重新调用一遍查询嵌套的属性

//加载结果
  public Object loadResult() throws SQLException {
	//1.selectList
    List<Object> list = selectList();
    //2.ResultExtractor.extractObjectFromList
    resultObject = resultExtractor.extractObjectFromList(list, targetType);
    return resultObject;
  }

  private <E> List<E> selectList() throws SQLException {
    Executor localExecutor = executor;
    // 如果executor已经被关闭了,则创建一个新的
    if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
      localExecutor = newExecutor();
    }
    try {
      // 又调回Executor.query去了,相当于B的查询
      return localExecutor.<E> query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
    } finally {
      if (localExecutor != executor) {
        localExecutor.close(false);
      }
    }
  }

如果之前的查询有被缓存过那么会走executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType)这个逻辑

  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 = NO_VALUE;
    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);
      } else {
        final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
        if (propertyMapping.isLazy()) {
          // 懒加载
          lazyLoader.addLoader(property, metaResultObject, resultLoader);
        } else {
          // 立即加载
          value = resultLoader.loadResult();
        }
      }
    }
    return value;
  }

如果重复查询进入延迟加载逻辑,可以看到延迟加载之后没有再递归调用query方法了,说明已经到头了,之后一路返回。

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 {
    // 队列加入,整体查询完在加载(延迟加载)这里new一个新的,直接拿deferredLoad不行吗??
    deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
  }
}

查询返回完以后,将队列的元素通过反射映射嵌套查询的字段就可以了

//加载
    public void load() {
      List<Object> list = (List<Object>) localCache.getObject(key);
      // 取出缓存的值
      Object value = resultExtractor.extractObjectFromList(list, targetType);
      resultObject.setValue(property, value);
    }

结果

 

流程图

核心思路:先完成一条不重复的链子后将他们头尾连接

posted @ 2022-06-06 16:21  猫长寿  阅读(798)  评论(0编辑  收藏  举报