mybatis原理分析(八)---子查询循环依赖

前言

对mybatis的相关原理不清楚的读者可以先阅读mybatis原理分析系列的前面几篇博客。再来看这一篇博客,然后跟着步骤debug调试,一边动手调试一边看博客会更好理解。

1.概述

上一篇博客中介绍了结果集处理的过程。当结果集映射中出现复合对象时,会触发子查询。

此时就会有个问题,子查询多次嵌套可以出现循环子查询的问题。也就是子查询循环依赖。查询A,填充属性B,需要去查询B。查询B的时候需要填充属性A,又会去查询A。

例如下面的代码

在这里插入图片描述

这里根据id查找博客,博客中有评论属性,会触发子查询去查找评论。评论的结果集映射中又出现了博客属性。又会去触发子查询查找博客。这样就构成了子查询循环依赖。

这篇博客的重点就在于分析mybatis是如何解决这种情况的。

2.代码分析

根据上面的xml,编写如下的测试代码:

在这里插入图片描述
代码乍一眼看上去很简单,不就是查询嘛。接下来跟进源码看看mybatis做了哪些事情。

在这里插入图片描述
代码会执行到BaseExecutor中的query方法。

首先查询栈+1 结果为1 表示这是第一层查询。

然后从一级缓存中获取,发现此时没有,就会走queryFromDatabase 查询数据库。

在这里插入图片描述

第一行代码很重要,也是解决子查询循环依赖的关键。此时并没有查找到结果。仅仅将缓存key和一个占位符保存在了一级缓存当中。然后执行doQuery方法。

在这里插入图片描述

获取配置信息,构建StatementHandler 和预编译sql在之前的博客中都介绍过了 这里就不再赘述。代码继续执行handler.query

在这里插入图片描述

来到了上一篇博客中讲解的处理结果集的方法handleResultSets

在这里插入图片描述

具体过程参考上一篇博客,此处关注handleResultSet方法
在这里插入图片描述

代码最终会执行到handleRowValues 来处理每一行结果 触发普通结果集映射handleRowValuesForSimpleResultMap

在这里插入图片描述
getRowValue将结果行转换成一个java对象。

在这里插入图片描述

先执行自动映射的属性填充,然后执行手动映射的属性填充,因为此时我们要看的就是评论这个属性是如何填充的。代码定位到applyPropertyMappings
在这里插入图片描述
此处会遍历手动配置的结果集映射,遍历到评论的映射的时候。会触发getPropertyMappingValue来获取评论的属性值。
在这里插入图片描述
评论映射是个复合对象,会触发子查询getNestedQueryMappingValue
在这里插入图片描述
下面的代码子查询的关键

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);//获取子查询的mappedStatement
  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);//准备动态sql
    final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);//创建缓存key
    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;
}

做了一些子查询的准备工作,例如获取子查询的mappedStatement 准备参数,创建缓存key。

然后判断是否命中一级缓存。如果命中了则会触发延迟加载。否则判断是否进行懒加载,如果是要懒加载的话,则为其添加懒加载。如何做的下一篇博客再介绍。

否则就是走实时加载的逻辑,去查询数据库,获取子查询的结果。

此例子,此时会走实时加载的逻辑 resultLoader.loadResult

在这里插入图片描述
在这里插入图片描述

代码又会执行到BaseExecutor中的query方法在这里插入图片描述

此时查询栈计数器为2 因为是子查询,然后会从一级缓存中获取结果。如果没有则会触发queryFromDatabase

在这里插入图片描述

同样 和主查询一样,会先往一级缓存中放入缓存key和对应一个占位符,来解决循环依赖。我们来看看此时的一级缓存中有哪些东西

在这里插入图片描述

主查询(查询博客)和子查询(查询评论)的缓存key 和对应的占位符。

添加完之后执行doQuery方法

之后的逻辑和上面差不多 在处理评论的结果集的时候。会触发获取博客的属性值

在这里插入图片描述
同样因为此时映射中博客属性是个子查询。

在这里插入图片描述
代码来到getNestedQueryMappingValue
在这里插入图片描述
和前一次子查询结果不同的是,在一级缓存中找到了相同的缓存key。因为在主查询的时候,就将查询博客相关的缓存key放入了一级缓存当中。所以此时子查询去查询博客的时候,就不会像刚才那样去走实时加载的逻辑了。而是走延迟加载的逻辑。ecexutor.deferLoad
在这里插入图片描述
加入到延迟加载队列中 DeferredLoad包含了查询结果对象,属性名,缓存key等
在这里插入图片描述

之后就是这样处理完子查询,都放入到延迟加载的队列当中。

代码回到queryFromDatabase 从一级缓存中删去缓存key和占位符,替换成缓存key和结果集

在这里插入图片描述
返回结果集 到query方法

在这里插入图片描述

子查询结束,查询栈减1

当代码回到主查询的query方法时 此时的查询栈等于0了。执行延迟加载defferedLoad.load方法
在这里插入图片描述
从一级缓存中取出结果,然后填充属性

执行完延迟加载后,将延迟加载的队列清空。最后返回结果。

3.总结

子查询的循环依赖解决主要思路如下:

  • 一级缓存记录下已执行过的查询
  • 延迟加载队列记录下每次查询的结果
  • 最后执行延迟加载
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值