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

来源:互联网 发布:excel数据清洗方法 编辑:程序博客网 时间:2024/05/18 02:47

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


环境:

mybatis-3.2.7



mybatis的懒加载配置什么的我就不详细说了,可以到我的github地址,看我的mybatis-demo  <https://github.com/ht5678/mybatis-demo.git>,里边有详细的例子


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





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

启动懒加载

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


不启动懒加载

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




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


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

<!-- 查询时,关闭关联对象即时加载以提高性能 --><setting name="lazyLoadingEnabled" value="true" /><!-- 设置关联对象加载的形态,此处为按需加载字段(加载字段由SQL指 定),不会加载关联表的所有字段,以提高性能 --><setting name="aggressiveLazyLoading" value="false" />

这两个配置,第一个毫无疑问,就是启动懒加载的;第二个呢,是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<RolePO> 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<String> 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<String> 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<Class<?>> constructorArgTypes, List<Object> constructorArgs) {    return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);  }  public Object createDeserializationProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> 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<Class<?>> constructorArgTypes, List<Object> 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<String> lazyLoadTriggerMethods;    private ObjectFactory objectFactory;    private List<Class<?>> constructorArgTypes;    private List<Object> 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<Class<?>> constructorArgTypes, List<Object> 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<Class<?>> constructorArgTypes, List<Object> 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<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,            List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {      super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);    }    public static Object createProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,            List<Class<?>> constructorArgTypes, List<Object> 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<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,            List<Class<?>> constructorArgTypes, List<Object> 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了,因为这个类比较多,所以笔者就不贴源码了大家可以对照着源码来看一下这部分,我先给大家看一下这个类中处理查询返回结果的方法时序图:



这个是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组件原理呈现给大家,,,希望大家能够支持






1 0
原创粉丝点击