Mybatis查询延迟加载

启用延迟加载

       Mybatis的延迟加载是针对嵌套查询而言的,指在进行查询的时候先只查询最外层的SQL,对于内层SQL将在需要使用的时候才查询出来。Mybatis的延迟加载默认是关闭的,即默认是一次就将所有的嵌套SQL一并查了将对象所有的信息查询出来。开启延迟加载有两种方式。

       第一种是在对应的<collection>或<association>标签上指定fetchType属性值为“lazy”。如下示例在查询id为selectByPrimaryKey的查询时,会返回BaseResultMap,在BaseResultMap中,我们指定属性“nodes”是一个集合类型的,而且是需要通过id为selectNodes的查询进行查询的,我们指定了该查询的fetchType为lazy,即延迟加载。

<?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="mybatis.mapper.ProcessMapper">
    <resultMap id="BaseProcess" type="mybatis.domain.Process">
        <id property="id" column="id"/>
        <result property="templateId" jdbcType="INTEGER" column="template_id"/>
        <result property="creator" jdbcType="INTEGER" column="creator"/>
        <result property="createTime" jdbcType="TIMESTAMP" column="create_time"/>
        <collection property="nodes" column="id" ofType="mybatis.domain.Node" select="selectNodes" fetchType="lazy"/>
    </resultMap>
    <!-- 一条查询Sql查出一对多关系-->
    <select id="findProcessByIdLazy" parameterType="Integer" resultMap="BaseProcess">
        select a.id,a.template_id,a.creator,a.create_time from process a
        where a.id=#{id}
    </select>

    <resultMap id="NodeResults" type="mybatis.domain.Node">
        <result property="nodeId" jdbcType="INTEGER" column="node_id"/>
        <result property="processId" jdbcType="INTEGER" column="process_id"/>
        <result property="nodeCode" jdbcType="VARCHAR" column="node_code"/>
        <result property="nodeName" jdbcType="VARCHAR" column="node_name"/>
    </resultMap>

    <select id="selectNodes" parameterType="Integer" resultMap="NodeResults">
        select b.id node_id,b.process_id,b.node_code,b.node_name from node b
        where b.process_id=#{id}
    </select>
</mapper>

  第二种是开启全局的延迟加载。通过在Mybatis的配置文件的<settings>标签下,加上如下配置可开启全局的延迟加载。开启全局的延迟加载后,就无需再在各个嵌套的子查询上配置延迟加载,如果有某一个嵌套的子查询是不需要延迟加载的,可以设置其fetchType=”eager”。设置在嵌套查询上的fetchType可以覆盖全局的延迟加载设置。

  <setting name="lazyLoadingEnabled" value="true"/>

分析

       Mybatis的查询结果是由ResultSetHandler接口的handleResultSets()方法处理的。ResultSetHandler接口只有一个实现,DefaultResultSetHandler。有兴趣的朋友可以去看一下它的源码,看一下它是如何处理结果集的。对于本文的主题,延迟加载相关的一个核心的方法就是如下这个创建返回结果对象的方法

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {

    final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>();
    final List<Object> constructorArgs = new ArrayList<Object>();
    final Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {

      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
      for (ResultMapping propertyMapping : propertyMappings) {

        // issue gcode #109 && issue #149

        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
          return configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory,
constructorArgTypes, constructorArgs); } } }
return resultObject; }

  在上面方法中可以看到Mybatis先是根据正常情况,创建一个返回类型对应的对象。当我们的ResultMap是包含子查询的时候,其会在正常返回类型对象的基础上,创建对应的代理对象。就是我们的直接结果是代理对象,而不是子查询对应的属性是代理对象。默认是基于JavassistProxyFactory类创建的代理对象。可以通过Mybatis的全局配置proxyFactory来更改,可选值是CGLIB和JAVASSIST,默认是后者。需要使用CGLIB代理时,注意加入CGLIB的包。

 <setting name="proxyFactory" value="CGLIB"/>

  一个查询返回的是Process类型的对象,其有Node集合类型的nodes属性,nodes属性是通过一个子查询查出来的,而且是延迟加载。当我们访问该代理对象的方法时,都会触发加载所有的延迟加载的对象信息。这只是Mybatis的一个默认策略,可以通过Mybatis的全局配置aggressiveLazyLoading来改变它,默认是true,表示延迟加载时将在第一次访问代理对象的方法时,就将全部的延迟加载对象加载出来。当设置为false时,则会在我们第一次访问延迟加载的对象的时候,才会从数据库加载对应的数据。注意在延迟对象未从数据库加载出来前,对应延迟对象的属性将是null,因为你没有对它赋值。

<setting name="aggressiveLazyLoading" value="fasle"/>

  设置aggressiveLazyLoading=”false”,但又希望在调用某些方法之前,把所有的延迟对象都从数据库加载出来,怎么办呢?这个时候我们可以通过lazyLoadTriggerMethods参数,来指定需要加载延迟对象的方法调用。默认是equals、clone、hashCode和toString,也就是说我们在调用代理对象的这些方法之前,就会把延迟加载对象从数据库加载出来。

<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" />

  以下是基于Javassist创建的代理对象的代理过程,基于CGLIB的代理也是类似的。从下面的代码我们可以看到Mybatis的代理对象需要从数据库加载延迟对象时,是在目标方法被调用以前发生的,这就可以保证目标方法被调用时,延迟加载的对象已经从数据库中加载出来。

 @Override

    public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
      final String methodName = method.getName();
      try {
        synchronized (lazyLoader) {
          if (WRITE_REPLACE_METHOD.equals(methodName)) {
            Object original = null;
            if (constructorArgTypes.isEmpty()) {
              original = objectFactory.create(type);
            } else {
              original = objectFactory.create(type, constructorArgTypes, constructorArgs);
            }

            PropertyCopier.copyBeanProperties(type, enhanced, original);
            if (lazyLoader.size() > 0) {
              return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
            } else {
              return original;
            }
          } else {

            if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
              if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                lazyLoader.loadAll();
              } else if (PropertyNamer.isProperty(methodName)) {
                final String property = PropertyNamer.methodToProperty(methodName);
                if (lazyLoader.hasLoader(property)) {
                  lazyLoader.load(property);
                }
              }
            }
          }
        }
        return methodProxy.invoke(enhanced, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }

 

参考:

http://elim.iteye.com/blog/2350745

 

  

posted on 2018-11-03 15:29  溪水静幽  阅读(371)  评论(0)    收藏  举报