ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

MyBatis循环依赖问题

2022-06-06 16:34:48  阅读:186  来源: 互联网

标签:targetType 依赖 list 查询 循环 key MyBatis final 加载


问题分析

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);
    }

结果

 

流程图

A表的引用必须预先完成也就是(A->B),所以必须查询返回后才能从延迟队列加载元素。

标签:targetType,依赖,list,查询,循环,key,MyBatis,final,加载
来源: https://www.cnblogs.com/JoJo1021/p/16348640.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有