MyBatis从入门到精通—源码剖析之延迟加载源码细节

Posted 共饮一杯无

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis从入门到精通—源码剖析之延迟加载源码细节相关的知识,希望对你有一定的参考价值。

什么是延迟加载?

在开发过程中很多时候我们并不需要总是在加载⽤户信息时就⼀定要加载他的订单信息。此时就是我
们所说的延迟加载。
举个栗⼦:

延迟加载
就是在需要⽤到数据时才进⾏加载,不需要⽤到数据时就不加载数据。延迟加载也称懒加载。

  • 优点:

先从单表查询,需要时再从关联表去关联查询,⼤⼤提⾼数据库性能,因为查询单表要⽐关联查询多张表速度要快。

  • 缺点:

因为只有当需要⽤到数据时,才会进⾏数据库查询,这样在⼤批量数据查询时,因为查询⼯作也要消耗时间,所以可能造成⽤户等待时间变⻓,造成⽤户体验下降。
在多表中:
⼀对多,多对多:通常情况下采⽤延迟加载
⼀对⼀(多对⼀):通常情况下采⽤⽴即加载
注意
延迟加载是基于嵌套查询来实现的。

实现

局部延迟加载

在association和collection标签中都有⼀个fetchType属性,通过修改它的值,可以修改局部的加载策略。

<!-- 开启⼀对多 延迟加载 -->
<resultMap id="userMap" type="user">
  <id column="id" property="id"></id>
  <result column="username" property="username"></result>
  <result column="password" property="password"></result>
  <result column="birthday" property="birthday"></result>
  <!--
  fetchType="lazy" 懒加载策略
  fetchType="eager" ⽴即加载策略
  -->
  <collection property="orderList" ofType="order" column="id" select="com.zjq.dao.OrderMapper.findByUid" fetchType="lazy">
  </collection>
</resultMap>
<select id="findAll" resultMap="userMap">
  SELECT * FROM `user`
</select>

全局延迟加载

在Mybatis的核⼼配置⽂件中可以使⽤setting标签修改全局的加载策略。

<settings>
  <!--开启全局延迟加载功能-->
  <setting name="lazyLoadingEnabled" value="true"/>
</settings>

注意:

<!-- 关闭⼀对⼀ 延迟加载 -->
<resultMap id="orderMap" type="order">
  <id column="id" property="id"></id>
  <result column="ordertime" property="ordertime"></result>
  <result column="total" property="total"></result>
  <!--
  fetchType="lazy" 懒加载策略
  fetchType="eager" ⽴即加载策略
  -->
  <association property="user" column="uid" javaType="user"
    select="com.zjq.dao.UserMapper.findById" fetchType="eager">
  </association>
</resultMap>
<select id="findAll" resultMap="orderMap">
  SELECT * from orders
</select>

延迟加载原理实现

它的原理是,使⽤ CGLIB 或 Javassist( 默认 ) 创建⽬标对象的代理对象。当调⽤代理对象的延迟加载属性的 getting ⽅法时,进⼊拦截器⽅法。⽐如调⽤ a.getB().getName() ⽅法,进⼊拦截器的invoke(...) ⽅法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B对象的 SQL ,把 B 查询上来,然后调⽤a.setB(b) ⽅法,于是 a 对象 b 属性就有值了,接着完成a.getB().getName() ⽅法的调⽤。这就是延迟加载的基本原理
总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定⽅法,执⾏数据加载。

延迟加载原理(源码剖析)

MyBatis延迟加载主要使⽤:Javassist,Cglib实现,类图展示:

Setting 配置加载:

public class Configuration 
    /** aggressiveLazyLoading:
    * 当开启时,任何⽅法的调⽤都会加载该对象的所有属性。否则,每个属性会按需加载(参考
    lazyLoadTriggerMethods).
    * 默认为true
    * */
    protected boolean aggressiveLazyLoading;
    /**
    * 延迟加载触发⽅法
    */
    protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[]  "equals", "clone", "hashCode", "toString" ));
    /** 是否开启延迟加载 */
    protected boolean lazyLoadingEnabled = false;
    /**
    * 默认使⽤Javassist代理⼯⼚
    * @param proxyFactory
    */
    public void setProxyFactory(ProxyFactory proxyFactory) 
        if (proxyFactory == null) 
            proxyFactory = new JavassistProxyFactory();
        
        this.proxyFactory = proxyFactory;
    
    //省略...

延迟加载代理对象创建

Mybatis的查询结果是由ResultSetHandler接⼝的handleResultSets()⽅法处理的。ResultSetHandler接⼝只有⼀个实现,DefaultResultSetHandler,接下来看下延迟加载相关的⼀个核⼼的⽅法。

  //创建结果对象  
  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException 
    this.useConstructorMappings = false; // reset previous mapping result
    final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>();
    final List<Object> constructorArgs = new ArrayList<Object>();
    //创建返回的结果映射的真实对象
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) 
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
      for (ResultMapping propertyMapping : propertyMappings) 
        // issue gcode #109 && issue #149
        // 判断属性有没配置嵌套查询,如果有就创建代理对象
        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) 
          //创建延迟加载代理对象
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
          break;
        
      
    
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
    return resultObject;
  

默认采⽤javassistProxy进⾏代理对象的创建。

public class Configuration 
    protected ProxyFactory proxyFactory = new JavassistProxyFactory();

JavasisstProxyFactory实现

public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory 
  /**
    * 接⼝实现
    * @param target ⽬标结果对象
    * @param lazyLoader 延迟加载对象
    * @param configuration 配置
    * @param objectFactory 对象⼯⼚
    * @param constructorArgTypes 构造参数类型
    * @param constructorArgs 构造参数值
    * @return
  */
  @Override
  public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) 
    return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
  

   /**
    * 创建代理对象
    * @param type
    * @param callback
    * @param constructorArgTypes
    * @param constructorArgs
    * @return
    */  
  static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) 

    ProxyFactory enhancer = new ProxyFactory();
    enhancer.setSuperclass(type);

    try 
      //通过获取对象⽅法,判断是否存在该⽅法
      type.getDeclaredMethod(WRITE_REPLACE_METHOD);
      // ObjectOutputStream will call writeReplace of objects returned by writeReplace
      if (log.isDebugEnabled()) 
        log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this");
      
      //没找到该⽅法,实现接⼝
     catch (NoSuchMethodException e) 
      enhancer.setInterfaces(new Class[]WriteReplaceInterface.class);
     catch (SecurityException e) 
      // nothing to do here
    

    Object enhanced;
    Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
    Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
    try 
      //创建新的代理对象
      enhanced = enhancer.create(typesArray, valuesArray);
     catch (Exception e) 
      throw new ExecutorException("Error creating lazy proxy.  Cause: " + e, e);
    
    //设置代理执⾏器
    ((Proxy) enhanced).setHandler(callback);
    return enhanced;
  

  /**
    * 内部类代理对象实现,核⼼逻辑执⾏
  */
  private static class EnhancedResultObjectProxyImpl implements MethodHandler 

    private final Class<?> type;
    private final ResultLoaderMap lazyLoader;
    private final boolean aggressive;
    private final Set<String> lazyLoadTriggerMethods;
    private final ObjectFactory objectFactory;
    private final List<Class<?>> constructorArgTypes;
    private final List<Object> constructorArgs;

    private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) 
      this.type = type;
      this.lazyLoader = lazyLoader;
      this.aggressive = configuration.isAggressiveLazyLoading();
      this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
      this.objectFactory = objectFactory;
      this.constructorArgTypes = constructorArgTypes;
      this.constructorArgs = constructorArgs;
    

    public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) 
      final Class<?> type = target.getClass();
      EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
      //调用外部类的方法
      Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
      PropertyCopier.copyBeanProperties(type, target, enhanced);
      return enhanced;
    

    /**
    * 代理对象执⾏
    * @param enhanced 原对象
    * @param method 原对象⽅法
    * @param methodProxy 代理⽅法
    * @param args ⽅法参数
    * @return
    * @throws Throwable
    */
    @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;
            //忽略暂未找到具体作⽤
            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 
            //延迟加载数量⼤于0
            if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) 
              //aggressive ⼀次加载性所有需要要延迟加载属性或者包含触发延迟加载⽅法
              if (aggressive || lazyLoadTriggerMethods.contains(methodName)) 
                //⼀次全部加载
                lazyLoader.loadAll();
               else if (PropertyNamer.isSetter(methodName)) 
                //判断是否为set⽅法,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);
       catch (Throwable t) 
        throw ExceptionUtil.unwrapThrowable(t);
      
    
   

注意事项

IDEA调试问题: 当配置aggressiveLazyLoading=true,在使⽤IDEA进⾏调试的时候,如果断点打到代理执⾏逻辑当中,你会发现延迟加载的代码永远都不能进⼊,总是会被提前执⾏。 主要产⽣的原因在aggressiveLazyLoading,因为在调试的时候,IDEA的Debuger窗体中已经触发了延迟加载对象的⽅法。

以上是关于MyBatis从入门到精通—源码剖析之延迟加载源码细节的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis源码剖析:延迟加载源码剖析

Mybatis从入门到精通系列 11——Mybatis 延迟加载与立即加载

阿里p8私藏的MyBatis笔记,从入门到精通,纵享源码细节

MyBatis从入门到精通:MyBatis入门

MyBatis从入门到放弃六:延迟加载一级缓存二级缓存

Spring IOC源码剖析:lazy-init 延迟加载机制和循环依赖问题