mybatis源码学习--mybatis懒加载内部原理

笔者最近研究mybatis比较多,所以打算把最近研究的mybatis的心得写出来,以免以后忘记,算是对这阶段的总结吧


环境:

mybatis-3.2.7



mybatis的懒加载配置什么的我就不详细说了,可以到我的github地址,看我的mybatis-demo  ,里边有详细的例子


我在这里画了一个图,简单的描述一下懒加载的流程,(画的不好。。。)


mybatis源码学习--mybatis懒加载内部原理_第1张图片



画的不好,,,可能大家看不懂,我来简单的给大家说一下流程,其实主要分两个逻辑:

启动懒加载

   mybatis初始化返回类型的时候,会返回一个cglib代理对象,这样的话,该对象的关联对象(例如一对多,多对一)相关信息就会在loadpair里边,并且添加到loadmap中,cglib对象会过滤get,set ,is,"equals", "clone", "hashCode", "toString"触发方法,然后才会调用loadpair来加载关联对象的值,这部分的详细源码下面我会带大家看


不启动懒加载

  不会返回代理对象,返回原生对象,然后会在一开始的时候就加载关联对象和sql中指定的所有属性




接下来,我会详细跟大家说一下mybatis是怎样实现懒加载的:


首先,懒加载会用到这两个配置


		
		
		

这两个配置,第一个毫无疑问,就是启动懒加载的;第二个呢,是mybatis执行完sql语句,组装返回的对象的时候,按sql指定的字段来加载,详细点说,就是如果没有调用指定的字段或者对象的get,set ,is,"equals", "clone", "hashCode", "toString"方法,该属性或者关联对象就不会在返回的对象结果中


使用懒加载的sql:

/**
	 * 根据用户id查询用户角色
	 * @param userId
	 * @return
	 */
	@Select("select * from role_main a INNER JOIN user_role b ON a.id = b.role_id WHERE b.user_id=#{userId}")
	public List getRolesByUserId(@Param("userId")Integer userId);
	
	
	/**
	 * 根据用户id查询用户角色名
	 * @param userId
	 * @return
	 */
	@Select("select a.role_name from role_main a INNER JOIN user_role b ON a.id = b.role_id WHERE b.user_id=#{userId}")
	public Set getRoleNamesByUserId(@Param("userId")Integer userId);
	
	
	/**
	 * 根据userid查询用户的所有权限
	 * @param userId
	 * @return
	 */
	@Select("SELECT a.permission_name FROM permission_main a INNER JOIN role_permission b ON a.id=b.permission_id WHERE b.role_id IN (SELECT d.role_id from user_main c INNER JOIN user_role d ON c.id = d.user_id WHERE c.id=#{userId})")
	public Set getPermissionsByUserId(@Param("userId")Integer userId);
	
	/**
	 * 通过用户名查询用户信息
	 * @param username
	 * @return
	 */
	@Select("select * from user_main where username=#{username}")
	@Results({
	      @Result(property = "roleNames", column = "id", many = @Many(fetchType=FetchType.LAZY,select = "getRoleNamesByUserId")),
	      @Result(property = "permissionNames", column = "id", many = @Many(fetchType=FetchType.LAZY,select = "getPermissionsByUserId"))
	  })
	public UserPO getUserByUsername(@Param("username")String username);

方法getUserByUserName就是使用了懒加载的查询,根据上面所描述的,以roleNames和permissionNames为例,在这里,它们俩就是所说的关联对象,如果没有调用它们的get,set ,is,"equals", "clone", "hashCode", "toString"方法,这两个关联的集合对象是不会有值的,只有触发了方法,才会从数据库中查询值



接下来,我带大家看看,mybatis在使用懒加载的时候做了什么,然后又在触发懒加载的时候做了什么

mybatis在使用懒加载的时候,是怎样保留懒加载的信息的?

这里额外的补充一下,在mybatis中,使用了很多动态代理,非常的普遍,使用最多的是jdk proxy,然后就是懒加载用到的cglib(默认)和javassist,关于懒加载这部分,笔者打算只是讲述cglib实现的懒加载,javassist是和cglib差不多的,只是技术不同,这两种懒加载笔者也就不说谁好谁坏了


回到正题,mybatis在懒加载的时候是怎么保留关联对象查询数据库的信息的:

以mybatis执行getUserByUserName的查询为例,在初始化UserPO赋值从数据库中查询的value的时候,因为启用了懒加载,所以USerPO的引用对象会是一个cglib代理对象,代理对象的初始化部分如下CglibProxyFactory,EnhancedResultObjectProxyImpl:


/**
 * @author Clinton Begin
 */
public class CglibProxyFactory implements ProxyFactory {

  private static final Log log = LogFactory.getLog(CglibProxyFactory.class);
  private static final String FINALIZE_METHOD = "finalize";
  private static final String WRITE_REPLACE_METHOD = "writeReplace";

  
  /**
   * 无参构造函数,初始化CglibProxyFactory
   * 加载net.sf.cglib.proxy.Enhancer(cglib)
   * 
   */
  public CglibProxyFactory() {
    try {
      Resources.classForName("net.sf.cglib.proxy.Enhancer");
    } catch (Throwable e) {
      throw new IllegalStateException("Cannot enable lazy loading because CGLIB is not available. Add CGLIB to your classpath.", e);
    }
  }

  /**
   * 使用内部类EnhancedResultObjectProxyImpl构建target类的代理
   */
  public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) {
    return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
  }

  public Object createDeserializationProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) {
    return EnhancedDeserializationProxyImpl.createProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
  }

  public void setProperties(Properties properties) {
  }

  
  /**
   * 使用cglib创建代理对象
   * @param type                            原生的对象类型
   * @param callback                       回调方法,就是实现了MethodInterceptor接口的子类,会有accept方法
   * @param constructorArgTypes    构造函数的参数类型
   * @param constructorArgs            构造函数的参数值
   * @return
   */
  private static Object crateProxy(Class type, Callback callback, List> constructorArgTypes, List constructorArgs) {
    Enhancer enhancer = new Enhancer();
    //回调方法,就是实现了MethodInterceptor接口的子类,会有accept方法
    enhancer.setCallback(callback);
    //要生成代理对象的原生类
    enhancer.setSuperclass(type);
    
    
    try {
      //获取目标类型的 writeReplace 方法,如果没有,异常中代理类设置enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class});
      type.getDeclaredMethod(WRITE_REPLACE_METHOD);
      // ObjectOutputStream will call writeReplace of objects returned by writeReplace
      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 = null;
    //如果构造函数没有参数,创建代理对象
    if (constructorArgTypes.isEmpty()) {
      enhanced = enhancer.create();
    } else {
      //否则,初始化带有参数的构造函数
      Class[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
      Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
      //创建带有参数的代理对象
      enhanced = enhancer.create(typesArray, valuesArray);
    }
    return enhanced;
  }

  
  
  /**
   * 
   * 类CglibProxyFactory.java的实现描述:cglib的callable回调的类
   * @author yuezhihua 2015年5月19日 下午6:26:05
   */
  private static class EnhancedResultObjectProxyImpl implements MethodInterceptor {
	//要代理的对象类型
    private Class type;
    private ResultLoaderMap lazyLoader;
    private boolean aggressive;
    private Set lazyLoadTriggerMethods;
    private ObjectFactory objectFactory;
    private List> constructorArgTypes;
    private List constructorArgs;

    
    /**
     * 构造函数
     * @param type                               要代理的对象类型                             
     * @param lazyLoader                      TODO:
     * @param configuration                   配置
     * @param objectFactory                 objectFactory用来初始化对象
     * @param constructorArgTypes       type的有参构造函数的参数类型和参数名
     * @param constructorArgs               type的有参构造函数的参数类型和参数名
     */
    private EnhancedResultObjectProxyImpl(Class type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) {
    	//要代理的对象类型
      this.type = type;
      this.lazyLoader = lazyLoader;
      //是否懒加载aggressive,默认为true
      this.aggressive = configuration.isAggressiveLazyLoading();
      //懒加载触发方法  "equals", "clone", "hashCode", "toString" 
      this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
      //objectFactory用来初始化对象
      this.objectFactory = objectFactory;
      //type的有参构造函数的参数类型和参数名
      this.constructorArgTypes = constructorArgTypes;
      this.constructorArgs = constructorArgs;
    }

    
    /**
     * 构建target类的代理
     * @param target                            要构建代理的对象
     * @param lazyLoader                     在DefaultResultSetHandler的getRowValue方法初始化
     * @param configuration                  配置类
     * @param objectFactory                class实例化为object的工场                
     * @param constructorArgTypes      构造函数的参数类型
     * @param constructorArgs             构造函数的参数值
     * @return
     */
    public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List constructorArgs) {
      final Class type = target.getClass();
      //初始化一个EnhancedResultObjectProxyImpl内部类
      EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
      //使用cglib创建代理对象
      Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
      //target是type的初始化对象
      //enhanced可能是type的代理对象
      //将target对象的值赋值到enhanced,包括父类的字段赋值
      PropertyCopier.copyBeanProperties(type, target, enhanced);
      //返回代理enhanced
      return enhanced;
    }

    
    /**
     * cglib拦截器实现
     */
    public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) 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);
            }
            // 将sourceBean对象的值赋值到destinationBean,包括父类的字段赋值
            PropertyCopier.copyBeanProperties(type, enhanced, original);
            //lazyLoader.size()保存需要延迟加载属性列表的个数
            if (lazyLoader.size() > 0) {
              return new CglibSerialStateHolder(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 就会触发ResultLoader的loadResult方法完成数据的加载实现。
                lazyLoader.loadAll();
              } else if (PropertyNamer.isProperty(methodName)) {
            	 //将get或set方法名获取property字段名
                final String property = PropertyNamer.methodToProperty(methodName);
                
                if (lazyLoader.hasLoader(property)) {
                  lazyLoader.load(property);
                }
              }
            }
          }
        }
        return methodProxy.invokeSuper(enhanced, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }

  private static class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy implements MethodInterceptor {

    private EnhancedDeserializationProxyImpl(Class type, Map unloadedProperties, ObjectFactory objectFactory,
            List> constructorArgTypes, List constructorArgs) {
      super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
    }

    public static Object createProxy(Object target, Map unloadedProperties, ObjectFactory objectFactory,
            List> constructorArgTypes, List constructorArgs) {
      final Class type = target.getClass();
      EnhancedDeserializationProxyImpl callback = new EnhancedDeserializationProxyImpl(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
      Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
      PropertyCopier.copyBeanProperties(type, target, enhanced);
      return enhanced;
    }

    @Override
    public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
      final Object o = super.invoke(enhanced, method, args);
      return (o instanceof AbstractSerialStateHolder) ? o : methodProxy.invokeSuper(o, args);
    }

    @Override
    protected AbstractSerialStateHolder newSerialStateHolder(Object userBean, Map unloadedProperties, ObjectFactory objectFactory,
            List> constructorArgTypes, List constructorArgs) {
      return new CglibSerialStateHolder(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
    }
  }
} 
  



上面是从mybatis中粘出来的源码,大家注意带有注释的crateProxy方法和EnhancedResultObjectProxyImpl中的intercept方法,这两个方法比较重要,crateProxy是初始化返回对象类型的代理对象,这里会默认给代理对象增加一个WriteReplaceInterface接口,这个接口也是用来做懒加载触发的,笔者不是很清楚,不做说明;其实上面说了这么多次的懒加载触发,什么的,实际点说,就是intercept方法,这里我要说一下,代理对象的每个方法调用都会经过intercept方法,大家看intercept方法中的判断lazyLoadTriggerMethods,这个lazyLoadTriggerMethods就是上面一直说的懒加载的默认触发方法,"equals", "clone", "hashCode", "toString",因为这个默认触发方法,笔者还吃了个亏,详细请看,mybatis注解方式懒加载失效分析,这几个默认方法是在configuration中配置的,大家可以根据需要在配置文件中自定义触发方法;除了这几个默认的触发方法,还有PropertyNamer.isProperty(methodName)这段代码,他会判断当前方法是不是代理对象的get,is,set方法,如果是的话,也会触发懒加载来查询数据库。


但是到这里懒加载保存信息部分还是没有完,我们来看,mybatis组装返回对象的时候,是怎么保留懒加载信息的,懒加载信息又是什么:

这里就要看DefaultResultSetHandler了,因为这个类比较多,所以笔者就不贴源码了大家可以对照着源码来看一下这部分,我先给大家看一下这个类中处理查询返回结果的方法时序图:

mybatis源码学习--mybatis懒加载内部原理_第2张图片


这个是DefaultResultSetHandler处理从数据库查询的数据的处理流程图,其中懒加载是在getNestedQueryMappingValue方法中的

//初始化resultloader
        final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
        //如果启用了懒加载,初始化一个loadpair到loadermap里边,如果没有启用,直接获取value并且返回
        if (propertyMapping.isLazy()) {
          lazyLoader.addLoader(property, metaResultObject, resultLoader);
        } else {//直接加载
          value = resultLoader.loadResult();
        }

这一段,mybatis会将关联对象,就是上面提到的roleNames和permissionNames两个一对多的嵌套查询会以loadpair对象的方式加到ResultLoaderMap.loadmap中,

其中loadpair记录了关联对象mybatis查询要用到的信息,metaResultObject(返回对象的metaobject),ResultLoader(包含了参数,返回映射,缓存key,配置等),executor(sql执行器)。。。

然后包含这些信息的loadpair就会放到loadmap中了,这部分完成解说了,接下来,就要看mybatis懒加载触发的时候是怎么使用loadpair来查询数据库的。


mybatis触发懒加载使用loadpair查询数据库并且返回组装对象:

说到触发的话,就要回到CglibProxyFactory.EnhancedResultObjectProxyImpl的intercept方法上了,懒加载方法被触发以后会调用lazyLoader.load(property);方法,

这个方法会先从loadmap中将loadpair移除,然后调用loadpair的load方法,

/**
     * 执行懒加载查询,获取数据并且set到userObject中返回
     * @param userObject
     * @throws SQLException
     */
    public void load(final Object userObject) throws SQLException {
    	
    	//合法性校验
      if (this.metaResultObject == null || this.resultLoader == null) {
        if (this.mappedParameter == null) {
          throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
                  + "required parameter of mapped statement ["
                  + this.mappedStatement + "] is not serializable.");
        }
        
        //获取mappedstatement并且校验
        final Configuration config = this.getConfiguration();
        final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
        if (ms == null) {
          throw new ExecutorException("Cannot lazy load property [" + this.property
                  + "] of deserialized object [" + userObject.getClass()
                  + "] because configuration does not contain statement ["
                  + this.mappedStatement + "]");
        }

        //使用userObject构建metaobject,并且重新构建resultloader对象
        this.metaResultObject = config.newMetaObject(userObject);
        this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
                metaResultObject.getSetterType(this.property), null, null);
      }

      /* We are using a new executor because we may be (and likely are) on a new thread
       * and executors aren't thread safe. (Is this sufficient?)
       *
       * A better approach would be making executors thread safe. */
      if (this.serializationCheck == null) {
        final ResultLoader old = this.resultLoader;
        this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
                old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
      }

     //获取数据库查询结果并且set到结果对象返回
      this.metaResultObject.setValue(property, this.resultLoader.loadResult());
    }

方法中会将this.resultLoader.loadResult()的值赋给返回对象的property字段,loadResult方法中会使用executor来执行查询获取结果然后组装成返回对象返回


现在懒加载部分的原理已经说完了,当然,里边有很多没有详细讲解的部分,也有很多大家还不详细理解的部分,比如executor,其他的查询结构,mappedstatement,metaobject...等等,笔者打算做一个专题,尽量将自己领悟的mybatis组件原理呈现给大家,,,希望大家能够支持






你可能感兴趣的:(mybatis,jdk)