likes
comments
collection
share

水煮MyBatis(十七)- 延迟加载(下)

作者站长头像
站长
· 阅读数 15

前言

前面介绍了一些延迟加载功能里的基本组件和相应的使用方式,这一章主要从源码角度来分析其是如何实现此功能的。

懒加载执行序列图

注意:在执行invoke之前,有一些前置动作:主查询完成之后,给嵌入式查询生成对应的ResultLoader,这个逻辑与主查询是绑定在一起的,所以没有在序列图中展现。 水煮MyBatis(十七)- 延迟加载(下)

关键源码

这里分三个关键源码来说明

  • 判定是否需要懒加载,也是前提条件;
  • 主查询执行完成以后,Mybatis对懒加载属性做了什么?
  • 查询懒加载属性的时候,如何执行的;

判定是否延迟加载

先来看看源码,具体是在Mapper接口进行初始化的时候,MapperAnnotationBuilder类中根据注解解析,生成MappedStatement时。 方法中有两个分支:

  • 读取配置文件中的lazy-loading-enabled设置;
  • 一对一的懒加载条件:有对应的查询方法,并且获取类型是LAZY;
  • 一对多的懒加载条件:有对应的查询方法,并且获取类型是LAZY;
  private boolean isLazy(Result result) {
    // 读取配置文件中的lazy.load.enabled设置
    boolean isLazy = configuration.isLazyLoadingEnabled();
    // 一对一的懒加载条件:有对应的查询方法,并且获取类型是LAZY
    if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) {
      isLazy = result.one().fetchType() == FetchType.LAZY;
    } else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) {
      // 一对多的懒加载条件:有对应的查询方法,并且获取类型是LAZY
      isLazy = result.many().fetchType() == FetchType.LAZY;
    }
    return isLazy;
  }

值得注意的是,就算下面的if/else语句里的条件都不匹配,也会根据配置文件中的lazy-loading-enabled设置来指定是否进行懒加载。也就是说,就算我们设定了FetchType=DEFAULT,只要配置了lazy-loading-enabled=true,就会默认进行懒加载。

主查询处理懒查询属性

主查询完成之后,会给嵌入式查询生成对应的ResultLoader,方便执行后续的查询。这段代码在【DefaultResultSetHandler】类中。 水煮MyBatis(十七)- 延迟加载(下)

这里主要介绍一下图中标注绿色的方法:【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);
    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);
      // 获取缓存key
      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;
  }

方法很直白,分了三大块:

  • 读取懒加载的配置,也就是上章提到过的@Result注解;
  • 如果有本地缓存【一级缓存】,则无论是否懒加载,都直接返回。为什么不判断二级缓存呢?据我所知,二级缓存只有在开启缓存以后,在CachingExecutor里查询的时候使用到,也就是说,就算在这个方法里用不到二级缓存,后续查询的时候,也有用到的机会。
  • 生成ResultLoader,判断嵌入式属性对象是否需要懒加载,如果配置的是[FetchType = EAGER],则直接加载返回,否则将加载参数缓存,等待需要加载的时候再执行resultLoader.loadResult()方法。

执行懒加载

下面是【JavassistProxyFactory】类里的简化以后的invoke()方法,只展示了核心逻辑

    @Override
	public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
		String methodName = method.getName();
		// 如果存在懒加载属性,且不为finally方法
		if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
		  if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
		  	// 如果aggressive.enable为true,或者当前方法为懒加载触发方法,则加载全部属性,无论是否配置了懒加载
		    lazyLoader.loadAll();
		  } else if (PropertyNamer.isSetter(methodName)) {
		  	// 如果是set方法,则懒加载列表中移除此属性
		    final String property = PropertyNamer.methodToProperty(methodName);
		    lazyLoader.remove(property);
		  } else if (PropertyNamer.isGetter(methodName)) {
		    final String property = PropertyNamer.methodToProperty(methodName);
		    if (lazyLoader.hasLoader(property)) {
		      // 执行懒加载
		      lazyLoader.load(property);
		    }
		  }
		}
		return methodProxy.invoke(enhanced, args);
	}

真正执行懒加载的三个前提条件:

  • 如果存在懒加载属性,且不为finally方法;
  • 如果aggressive-lazy-loading为true,或者当前方法为懒加载触发方法,lazy-load-trigger-methods配置,则加载全部属性,无论是否配置了懒加载;
  • 如果是set方法,则懒加载列表中移除此属性;

执行加载

最终执行懒加载的类【ResultLoaderMap.LoadPair】,也是简化了逻辑,就不展开介绍了。

   public void load(final Object userObject) throws SQLException {
   	  // 用resultLoader对象执行加载
      this.metaResultObject.setValue(property, this.resultLoader.loadResult());
    }
    
    // 加载
  public Object loadResult() throws SQLException {
    // 查询列表,无论是二级缓存或者数据库查询
    List<Object> list = selectList();
    // 组合数据,列表或者单个元素
    resultObject = resultExtractor.extractObjectFromList(list, targetType);
    return resultObject;
  }