MyBatis源码之:懒加载机制

1. 懒加载概述

1.1. 懒加载示例

假设实体类Blog中有一个Author author;字段,并且在查询博客时,作者信息是懒加载的:

xml 复制代码
<?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="com.xxx.mapper.LazyDemoMapper">
    
    <!-- 查询作者信息 -->
    <select id="getAuthorById" resultType="com.xxx.entity.Author">
        SELECT * FROM author WHERE id = #{id}
    </select>

    <!-- 查询博客信息 -->
    <select id="getBlogById" resultMap="blogResult">
        SELECT * FROM blog WHERE id = #{id}
    </select>

    <!-- 封装博客信息,其中的作者信息是懒加载的 -->
    <resultMap id="blogResult" type="com.xxx.entity.Blog">
        <association property="author" column="author_id" select="getAuthorById" fetchType="lazy" />
    </resultMap>

</mapper>
java 复制代码
public class LazyDemo {
    public void lazyBlogDemo(SqlSession sqlSession, Long blogId) {
        
        // 根据id查询博客信息
        Blog blog = sqlSession.selectOne("com.xxx.mapper.LazyDemoMapper.getBlogById", blogId);

        // 打印博客标题;此时显然不会执行SQL语句
        System.out.println(blog.getTitle());

        // 打印作者信息;此时会执行SQL语句来查询作者信息
        System.out.println(blog.getAuthor());
    }
}
  1. 显然,程序中的blog肯定不是原始的实体类对象,而应该是个代理对象
  2. 当调用blog.getAuthor()方法时,代理对象会去执行SQL语句,查询出作者信息

值得注意的是,如果使用Java的序列化机制对Blog类的代理对象进行序列化,那么序列化时不会加载Author信息

当我们调用反序列化后的对象的getAuthor()方法时,才会执行SQL语句查询出Author信息

1.2. writeReplace()/readResolve()

既然提到了Java的序列化机制,那这里就顺便说明一下这两个和序列化相关的方法:

  1. 在对A对象进行序列化时,如果发现A有Object writeReplace()方法,则会调用该方法,并对其返回值进行序列化
  2. 当反序列化得到B对象时,如果发现B有Object readResolve()方法,则会将该方法的返回值作为最终反序列化的结果
  3. 另外,Java在反序列化时,并不是通过构造器来实例化对象的;反序列化本身也是一种实例化对象的方式;clone()方法同理

先来看一个简单的例子:

java 复制代码
public class SerializeDemo implements Serializable {
    private final String name;
    public SerializeDemo(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return super.toString() + "{name='" + name + "'}";
    }
    
    private Object writeReplace() {
        System.out.println("writeReplace: " + this); // 打印日志
        return this;                                 // 返回当前对象,后续会对当前对象进行序列化
    }

    private Object readResolve() {
        System.out.println("readResolve: " + this);  // 打印日志
        return this;                                 // 返回当前对象,将当前对象作为反序列化结果
    }

    /*
     * 测试程序,最终打印结果:
     * origin:       com.xxx.SerializeDemo@4f2410ac{name='serializeDemo'}
     * writeReplace: com.xxx.SerializeDemo@4f2410ac{name='serializeDemo'}
     * readResolve:  com.xxx.SerializeDemo@5fdef03a{name='serializeDemo'}
     * result:       com.xxx.SerializeDemo@5fdef03a{name='serializeDemo'}
     */
    public static void main(String[] args) throws Exception {
        SerializeDemo origin = new SerializeDemo("serializeDemo");
        System.out.println("origin: " + origin);
        new ObjectOutputStream(new FileOutputStream("C:\\Users\\admin\\Desktop\\SerializeDemo")).writeObject(origin);

        Object res = new ObjectInputStream(new FileInputStream("C:\\Users\\admin\\Desktop\\SerializeDemo")).readObject();
        System.out.println("result: " + res);
    }
}

再来看一个比较复杂的例子;修改上面例子中的writeReplace()readResolve()方法,其它代码保持不变:

java 复制代码
public class SerializeDemo implements Serializable {
    
    private Object writeReplace() {
        System.out.println("before writeReplace: " + this);                     // 打印替换前的对象
        SerializeDemo replace = new SerializeDemo(this.name + "-writeReplace"); // 创建一个新的对象
        System.out.println("after writeReplace: " + replace);                   // 打印替换后的对象
        return replace;                                                         // 对替换后的对象进行序列化
    }

    private Object readResolve() {
        System.out.println("before readResolve: " + this);                      // 打印替换前的对象
        SerializeDemo resolve = new SerializeDemo(this.name + "-readResolve");  // 创建一个新的对象
        System.out.println("after readResolve: " + resolve);                    // 打印替换后的对象
        return resolve;                                                         // 将替换后的对象作为反序列化的结果
    }

    /*
     * 最终打印结果:
     * origin:              com.xxx.SerializeDemo@4f2410ac{name='serializeDemo'}
     * before writeReplace: com.xxx.SerializeDemo@4f2410ac{name='serializeDemo'}
     * after writeReplace:  com.xxx.SerializeDemo@5d6f64b1{name='serializeDemo-writeReplace'}
     * before readResolve:  com.xxx.SerializeDemo@3b22cdd0{name='serializeDemo-writeReplace'}
     * after readResolve:   com.xxx.SerializeDemo@1e81f4dc{name='serializeDemo-writeReplace-readResolve'}
     * result:              com.xxx.SerializeDemo@1e81f4dc{name='serializeDemo-writeReplace-readResolve'}
     */
}

2. 代理对象

注意,本文涉及到的动态代理逻辑的源码都以Javassist动态代理为例,Cglib方式的原理也是差不多的

2.1. 代理对象的创建

MyBatis是通过JavassistProxyFactory组件来创建代理对象的:

java 复制代码
public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory {

    /**
     * 创建代理对象
     * 
     * @param target              被代理的POJO对象
     * @param lazyLoader          target的懒加载字段的信息;这个之后会说
     * @param constructorArgTypes target所属类的构造参数类型
     * @param constructorArgs     创建target时使用的构造参数
     */
    @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);
    }

    /**
     * 实例化代理对象的通用工具方法
     */
    static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes,
            List<Object> constructorArgs) {

        // 创建代理工厂,并将POJO类指定为父类
        ProxyFactory enhancer = new ProxyFactory();
        enhancer.setSuperclass(type);

        // 查看POJO类中是否有writeReplace()方法
        // 如果有,则开发者应该确保该方法返回this;如果没有,则让子类实现WriteReplaceInterface接口
        try {
            type.getDeclaredMethod(WRITE_REPLACE_METHOD);
        } 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;
    }
}

EnhancedResultObjectProxyImplJavassistProxyFactory的静态内部类:

java 复制代码
class EnhancedResultObjectProxyImpl implements MethodHandler {
    
    /**
     * 创建代理对象
     */
    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);

        // 调用工具方法创建代理对象,并把原始的POJO对象中的数据拷贝到代理对象中,然后返回代理对象
        Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
        PropertyCopier.copyBeanProperties(type, target, enhanced);
        return enhanced;
    }
}

2.2. 代理对象的增强逻辑

java 复制代码
class EnhancedResultObjectProxyImpl implements MethodHandler {
    private final Class<?> type;                      // 被代理对象的类型(POJO类的Class对象)
    private final List<Class<?>> constructorArgTypes; // 被代理对象所属类的构造参数类型
    private final List<Object> constructorArgs;       // 创建被代理对象时使用的构造参数
    private final ResultLoaderMap lazyLoader;         // 记录了所有懒加载字段的信息
    private final ObjectFactory objectFactory;        // 对象工厂
    private final boolean aggressive;                 // 是否只要调用了任意一个方法,就触发所有懒加载字段的加载(全加载)
    private final Set<String> lazyLoadTriggerMethods; // 触发全加载的方法,默认为configuration.getLazyLoadTriggerMethods()

    /**
     * 调用代理对象的方法时,会调用到该方法
     */
    @Override
    public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
        final String methodName = method.getName();
        try {
            synchronized (lazyLoader) { // 加锁
                
                // 如果调用的是writeReplace()方法,说明正准备将代理对象进行序列化
                if (WRITE_REPLACE_METHOD.equals(methodName)) {
                    
                    // 创建一个新的POJO对象,并将代理对象中的字段值拷贝到POJO对象中
                    Object original;
                    if (constructorArgTypes.isEmpty()) {
                        original = objectFactory.create(type);
                    } else {
                        original = objectFactory.create(type, constructorArgTypes, constructorArgs);
                    }
                    PropertyCopier.copyBeanProperties(type, enhanced, original);
                    
                    // 如果还有懒加载字段,则根据POJO对象和懒加载字段的信息创建一个JavassistSerialStateHolder对象,并将其返回
                    if (lazyLoader.size() > 0) {
                        return new JavassistSerialStateHolder(original, lazyLoader.getProperties(),
                                objectFactory, constructorArgTypes, constructorArgs);
                    
                    // 否则,直接返回该POJO对象,后续将会对其进行序列化
                    } else {
                        return original;
                    }
                
                // 如果调用的是其它普通的方法
                } else {
                    
                    // 如果有懒加载字段,并且调用的不是finalize()方法,说明确实可能需要进行懒加载
                    if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
                        
                        // 如果满足全加载的条件,则直接进行全加载
                        if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                            lazyLoader.loadAll();
                        
                        // 否则,如果调用的是setXxx()方法,则直接将xxx字段的懒加载信息移除
                        } else if (PropertyNamer.isSetter(methodName)) {
                            final String property = PropertyNamer.methodToProperty(methodName);
                            lazyLoader.remove(property);
                        
                        // 否则,如果调用的是getXxx()方法,并且xxx字段是懒加载的,则调用lazyLoader.load(xxx)方法给xxx字段赋值
                        } 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);
        }
    }
}

3. 反序列化后的代理对象

3.1. 反序列化时代理对象的创建

  1. 我们知道,代理对象在序列化时,如果它还有懒加载字段,那么实际上序列化的是JavassistSerialStateHolder对象
  2. 那么,在进行反序列化时,得到的自然也是JavassistSerialStateHolder对象
  3. JavassistSerialStateHolder的父类AbstractSerialStateHolder提供了readResolve()方法
  4. 父类的readResolve()方法会准备一些必要数据,然后调用抽象方法createDeserializationProxy()并将其返回值返回
  5. 因此,JavassistSerialStateHolder对象的createDeserializationProxy()方法的返回值才是真正的反序列化后的对象
java 复制代码
class JavassistSerialStateHolder extends AbstractSerialStateHolder {

    /**
     * 获取反序列化后的代理对象
     */
    protected Object createDeserializationProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, 
            ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        
        return new JavassistProxyFactory().createDeserializationProxy(target, unloadedProperties,
                objectFactory, constructorArgTypes, constructorArgs);
    }
}
java 复制代码
public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory {
    
    /**
     * 创建反序列化后的代理对象
     *
     * @param target              原始的POJO对象
     * @param unloadedProperties  POJO对象的懒加载字段的信息
     * @param constructorArgTypes POJO对象所属类的构造参数类型
     * @param constructorArgs     创建POJO对象时使用的构造参数
     */
    public Object createDeserializationProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, 
            ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {

        // EnhancedDeserializationProxyImpl的createProxy()方法和EnhancedResultObjectProxyImpl的createProxy()方法很相似
        return EnhancedDeserializationProxyImpl.createProxy(target, unloadedProperties,
                objectFactory, constructorArgTypes, constructorArgs);
    }
}

3.2. 反序列化后的代理对象的增强逻辑

反序列化后的代理对象的增强逻辑写在EnhancedDeserializationProxyImpl类中;这里我们先来看它的父类:

java 复制代码
public abstract class AbstractEnhancedDeserializationProxy {
    private final Class<?> type;                                            // 被代理对象的类型(POJO类的Class对象)
    private final List<Class<?>> constructorArgTypes;                       // 被代理对象所属类的构造参数类型
    private final List<Object> constructorArgs;                             // 创建被代理对象时使用的构造参数
    private final Map<String, ResultLoaderMap.LoadPair> unloadedProperties; // 所有懒加载字段的信息;key为字段名称
    private final ObjectFactory objectFactory;                              // 对象工厂
    private final Object reloadingPropertyLock;                             // 锁;在加载字段时需要先获取到该锁
    private boolean reloadingProperty;                                      // 当前是否还在加载字段

    /**
     * 注意,这个invoke()方法只是个普通的方法;代理对象的增强逻辑主要由该方法实现
     */
    public final Object invoke(Object enhanced, Method method, Object[] args) throws Throwable {
        final String methodName = method.getName();
        try {
            // 如果调用的是writeReplace()方法
            if (WRITE_REPLACE_METHOD.equals(methodName)) {

                // 创建一个新的POJO对象,并将代理对象中的字段值拷贝到POJO对象中
                final Object original;
                if (constructorArgTypes.isEmpty()) {
                    original = objectFactory.create(type);
                } else {
                    original = objectFactory.create(type, constructorArgTypes, constructorArgs);
                }
                PropertyCopier.copyBeanProperties(type, enhanced, original);
                
                // 返回一个新的AbstractSerialStateHolder对象(由子类实现)
                // 注意,这里并没有判断是否还有懒加载字段;这点和EnhancedResultObjectProxyImpl不一样
                return this.newSerialStateHolder(original, unloadedProperties, objectFactory,
                        constructorArgTypes, constructorArgs);
            
            // 如果调用的是其它普通的方法
            } else {
                synchronized (this.reloadingPropertyLock) { // 先获取锁
                    
                    // 如果调用的不是finalize()方法,并且调用的是getXxx()/setXxx()/isXxx()方法,并且当前没有在加载字段
                    if (!FINALIZE_METHOD.equals(methodName)&&PropertyNamer.isProperty(methodName)&&!reloadingProperty) {
                        final String property = PropertyNamer.methodToProperty(methodName);
                        final String propertyKey = property.toUpperCase(Locale.ENGLISH);
                        
                        // 如果当前字段确实还未加载,则将该字段从懒加载列表中删除,然后调用loadPair.load()方法加载该字段
                        // 注意,如果调用的是setXxx()方法,则这里也会加载字段;这点和EnhancedResultObjectProxyImpl不一样
                        // 还要注意的是,在加载字段时,这里还会将代理对象传给LoadPair的load()方法
                        if (unloadedProperties.containsKey(propertyKey)) {
                            final ResultLoaderMap.LoadPair loadPair = unloadedProperties.remove(propertyKey);
                            if (loadPair != null) {
                                try {
                                    reloadingProperty = true;
                                    loadPair.load(enhanced);
                                } finally {
                                    reloadingProperty = false;
                                }
                            } else {
                                // I'm not sure if this case can really happen or is just in tests -
                                // we have an unread property but no loadPair to load it.
                                throw new ExecutorException("An attempt has been made to read a not loaded lazy " +
                                        "property '" + property + "' of a disconnected object");
                            }
                        }
                    }

                    // 执行到这里,说明当前方法不是在查询懒加载字段,或者该懒加载字段已加载完成,因此返回代理对象(相当于放行)
                    return enhanced;
                }
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }
    
    protected abstract AbstractSerialStateHolder newSerialStateHolder(Object userBean, 
            Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
}

EnhancedDeserializationProxyImplJavassistProxyFactory的静态内部类:

java 复制代码
class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy implements MethodHandler {

    /**
     * 在对本对象进行序列化时,最终会调用本方法,并对本方法的返回值进行序列化;这里返回的是JavassistSerialStateHolder对象
     * 可以看出,不管是原始的代理对象还是反序列化后的代理对象,在序列化时,都是以JavassistSerialStateHolder形式来存储的
     */
    @Override
    protected AbstractSerialStateHolder newSerialStateHolder(Object userBean, 
            Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, 
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        
        return new JavassistSerialStateHolder(userBean, unloadedProperties, objectFactory,
                constructorArgTypes, constructorArgs);
    }

    /**
     * 调用反序列化后的代理对象的方法时,会调用到该方法
     */
    @Override
    public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
        
        // 先调用父类的invoke()方法
        final Object o = super.invoke(enhanced, method, args);
        
        // 如果父类方法返回AbstractSerialStateHolder,则直接返回该对象;否则放行,即调用POJO类中的原生方法
        return o instanceof AbstractSerialStateHolder ? o : methodProxy.invoke(o, args);
    }
}

4. ResultLoader

ResultLoader代表结果加载器;可以把它理解为一次查询任务,里面包含了查询时的所有要素,就差执行查询操作了:

java 复制代码
public class ResultLoader {
    protected final Configuration configuration;     // MyBatis配置
    protected final Executor executor;               // 该查询任务是由哪个执行器创建的
    protected final long creatorThreadId;            // 该查询任务是由哪个线程创建的
    protected final MappedStatement mappedStatement; // 待执行的SQL语句的select标签
    protected final Object parameterObject;          // 用户传的参数
    protected final Class<?> targetType;             // 目标数据类型
    protected final ObjectFactory objectFactory;     // 对象工厂
    protected final BoundSql boundSql;               // 待执行的SQL语句
    protected final CacheKey cacheKey;               // 缓存key
    protected final ResultExtractor resultExtractor; // 结果提取器
    protected boolean loaded;                        // 是否执行了查询操作;该字段没有任何作用
    protected Object resultObject;                   // 最终的查询结果

    public ResultLoader(Configuration config, Executor executor, MappedStatement mappedStatement,
            Object parameterObject, Class<?> targetType, CacheKey cacheKey, BoundSql boundSql) {
        this.configuration = config;
        this.executor = executor;
        this.mappedStatement = mappedStatement;
        this.parameterObject = parameterObject;
        this.targetType = targetType;
        this.objectFactory = configuration.getObjectFactory();
        this.cacheKey = cacheKey;
        this.boundSql = boundSql;
        this.resultExtractor = new ResultExtractor(configuration, objectFactory);
        this.creatorThreadId = Thread.currentThread().getId();
    }

    /**
     * 执行查询任务,获取查询结果
     */
    public Object loadResult() throws SQLException {
        
        // 执行SQL语句,获取查询结果
        List<Object> list = selectList();
        
        // 通过resultExtractor提取出最终结果,然后将该结果保存起来并返回
        resultObject = resultExtractor.extractObjectFromList(list, targetType);
        return resultObject;
    }

    /**
     * 执行SQL语句
     */
    private <E> List<E> selectList() throws SQLException {
        
        // localExecutor用于执行SQL语句,默认为this.executor
        Executor localExecutor = executor;
        
        // 如果当前线程不是创建该任务的线程,或者this.executor已经关闭了,则创建一个新的Executor
        // 如果是前一种情况,那么这里新创建一个执行器是为了避免并发问题,因为执行器本身不是线程安全的
        if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
            localExecutor = newExecutor();
        }
        
        // 通过localExecutor执行SQL语句;如果localExecutor是新创建的Executor,则执行完SQL后还要将其关闭
        try {
            return localExecutor.<E>query(mappedStatement, parameterObject, RowBounds.DEFAULT,
                    Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
        } finally {
            if (localExecutor != executor) {
                localExecutor.close(false);
            }
        }
    }

    /**
     * 判断查询结果是否为null
     */
    public boolean wasNull() {
        return resultObject == null;
    }
}

5. ResultLoaderMap

代理对象底层维护了一个ResultLoaderMap组件;ResultLoaderMap中记录了当前还有哪些字段未被加载

5.1. LoadPair

java 复制代码
public class ResultLoaderMap {

    /**
     * 已关闭的执行器;这个我们之前有提到过;源码很简单,因此省略
     */
    private static final class ClosedExecutor extends BaseExecutor { }
    
    /**
     * 本类用于表示尚未被加载的字段(即处于懒加载状态的字段)信息
     * 本类是对ResultLoader的封装,负责通过ResultLoader加载结果,并将结果设置给目标对象的目标字段
     * 注意,本类实现了Serializable接口,这样就保证了代理对象在序列化及反序列化后,懒加载的信息不会丢失
     */
    public static class LoadPair implements Serializable {
        private static final long serialVersionUID = 20130412;
        private transient Log log;

        /**
         * Configuration工厂类,主要用于获取Configuration对象
         */
        private Class<?> configurationFactory;
        
        /**  
         * Configuration工厂类的工厂方法名称;注意,这个工厂方法必须是静态的
         */
        private static final String FACTORY_METHOD = "getConfiguration";
        
        /** 
         * 注意,该字段是final transient的,因此,如果本对象是被反序列化出来的,则该字段一定为空;否则一定不为空
         */
        private final transient Object serializationCheck = new Object();

        /** 
         * 懒加载字段的名称(目标字段)
         */
        private String property;
        
        /** 
         * 懒加载字段所属的对象(目标对象)的MetaObject形式
         */
        private transient MetaObject metaResultObject;
        
        /** 
         * 结果加载器,用于查询懒加载字段的值
         */
        private transient ResultLoader resultLoader;
        
        /** 
         * 查询语句的id,即select标签的id
         */
        private String mappedStatement;
        
        /** 
         * 执行SQL语句时使用的参数
         */
        private Serializable mappedParameter;
        
        // 可以发现,在序列化时,只会序列化configurationFactory、property、mappedStatement和mappedParameter字段
        // 也就是说,在通过反序列化得到的LoadPair对象中,只有以上这4个字段是有值的

        /**
         * 构造方法;需指定目标字段、目标对象和相应的结果加载器
         * 注意,MyBatis在创建LoadPair实例时,传的metaResultObject/resultLoader参数都不为null
         */
        private LoadPair(final String property, MetaObject metaResultObject, ResultLoader resultLoader) {
            this.property = property;
            this.metaResultObject = metaResultObject;
            this.resultLoader = resultLoader;

            // 如果目标对象是可序列化的,那么需要考虑代理对象的序列化和反序列化问题
            // 因此,这里需要给一些必要字段进行赋值,确保在反序列化回来后,必要的信息不会丢失
            if (metaResultObject != null && metaResultObject.getOriginalObject() instanceof Serializable) {
                final Object mappedStatementParameter = resultLoader.parameterObject;

                // 如果SQL参数也是可序列化的,则给mappedStatement/mappedParameter/configurationFactory字段赋值
                // 这3个字段的主要作用是:在反序列化回来后,重新构造ResultLoader组件(因为resultLoader字段不会被序列化)
                if (mappedStatementParameter instanceof Serializable) {
                    this.mappedStatement = resultLoader.mappedStatement.getId();
                    this.mappedParameter = (Serializable) mappedStatementParameter;
                    this.configurationFactory = resultLoader.configuration.getConfigurationFactory();
                
                // 否则,在反序列化回来后,由于缺少SQL参数,当前字段肯定是无法被加载的
                // 因此,在序列化之前,用户需要先手动加载当前字段;否则反序列化回来之后,该字段就没法再重新加载了
                // 需要注意的是,如果mappedStatementParameter为null,即SQL语句不需要参数,那么此时也会执行else块,这就有问题了
                } else {
                    // 省略打印日志的代码
                }
            }
        }

        /**
         * 代理对象底层通过该方法来加载字段
         */
        public void load() throws SQLException {
            
            // 这两个判断是在避免反序列化后的代理对象直接调用本方法
            if (this.metaResultObject == null) {
                throw new IllegalArgumentException("metaResultObject is null");
            }
            if (this.resultLoader == null) {
                throw new IllegalArgumentException("resultLoader is null");
            }

            this.load(null);
        }

        /**
         * 该方法负责通过ResultLoader加载结果,并将结果设置给目标对象的目标字段
         * 
         * 反序列化后的代理对象底层通过该方法来加载字段
         * 由于在反序列化后,LoadPair底层的目标对象就丢失了,因此这种情况下需要调用者自己提供备选的目标对象
         * 
         * @param userObject 备选的目标对象;如果LoadPair是反序列化得到的,则拿该对象作为目标对象
         */
        public void load(final Object userObject) throws SQLException {
            
            // 如果metaResultObject/resultLoader为null,说明LoadPair是通过反序列化得到的
            if (this.metaResultObject == null || this.resultLoader == null) {
                
                // 由于是反序列化来的,因此有效的信息只有configurationFactory、mappedStatement、mappedParameter、property字段
                // 因此,这里我们需要根据这4个字段来还原ResultLoader对象(相当于将ResultLoader对象反序列化回来)
                
                // 前面说过,SQL语句有可能是不需要参数的,因此这里的非空判断是有点问题的
                if (this.mappedParameter == null) {
                    throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
                            + "required parameter of mapped statement ["
                            + this.mappedStatement + "] is not serializable.");
                }

                // 获取Configuration对象,主要流程为:
                // 1. 如果工厂类(configurationFactory)为null,则报错
                // 2. 获取工厂类的名称为FACTORY_METHOD的方法,即getConfiguration()方法
                // 3. 如果getConfiguration()方法不是静态的,则报错
                // 4. 通过反射调用工厂类的静态getConfiguration()方法
                // 5. 如果getConfiguration()方法返回值不是Configuration类型,则报错
                final Configuration config = this.getConfiguration();
                
                // 获取到要执行的MappedStatement
                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作为目标对象
                this.metaResultObject = config.newMetaObject(userObject);
                
                // 重新创建ResultLoader实例,其底层的执行器是ClosedExecutor
                // 这样的话,当调用ResultLoader加载数据时,ResultLoader会自动创建一个新的Executor来执行SQL语句
                this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
                        metaResultObject.getSetterType(this.property), null, null);
            }

            // 如果LoadPair是反序列化来的,则将ResultLoader内部的执行器改为ClosedExecutor(这里貌似重复了)
            // 这是因为序列化的线程和反序列化的线程很有可能不是同一个,因此ResultLoader需要重新创建一个新的执行器
            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);
            }

            // 通过ResultLoader加载数据,并将查询结果设置给目标对象的目标字段
            this.metaResultObject.setValue(property, this.resultLoader.loadResult());
        }
    }
}

5.2. ResultLoaderMap

java 复制代码
public class ResultLoaderMap {

    /**
     * key为属性名称的大写形式,value为该属性的懒加载字段信息
     */
    private final Map<String, LoadPair> loaderMap = new HashMap<String, LoadPair>();

    /**
     * 添加一个懒加载字段的信息
     */
    public void addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader) {
        
        // 这里的property有可能含有多级字段,而我们只需要取出第一级字段的名称并将其转成大写即可
        String upperFirst = getUppercaseFirstProperty(property);
        
        // 如果property确实包含多级字段,且第一级字段已经添加了,说明重复添加,此时报错
        // 需要注意的是,如果先添加friend.address字段,再添加friend字段,那么此时不会报错,而是覆盖
        if (!upperFirst.equalsIgnoreCase(property) && loaderMap.containsKey(upperFirst)) {
            throw new ExecutorException();
        }
        
        loaderMap.put(upperFirst, new LoadPair(property, metaResultObject, resultLoader));
    }
    
    public final Map<String, LoadPair> getProperties() {
        return new HashMap<String, LoadPair>(this.loaderMap);
    }

    public Set<String> getPropertyNames() {
        return loaderMap.keySet();
    }

    public int size() {
        return loaderMap.size();
    }

    public boolean hasLoader(String property) {
        return loaderMap.containsKey(property.toUpperCase(Locale.ENGLISH));
    }

    public void remove(String property) {
        loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
    }

    /**
     * 加载指定字段,返回值代表是否真的执行了加载操作;代理对象通过该方法来加载字段
     */
    public boolean load(String property) throws SQLException {
        
        // 将该字段从loaderMap中移除;由于是remove操作,因此字段只会加载一次(即使可能加载失败)
        LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
        
        // 如果确实从loaderMap中删除了,说明该字段确实未加载,此时加载该字段并返回true
        if (pair != null) {
            pair.load();
            return true;
        }
        
        // 否则,说明该字段已经加载过了(不管加载是否成功),或者根本不是懒加载字段,此时返回false
        return false;
    }

    /**
     * 加载所有懒加载的字段
     */
    public void loadAll() throws SQLException {
        final Set<String> methodNameSet = loaderMap.keySet();
        
        // 创建一个新数组来存储所有懒加载的字段名称,避免出现并发修改异常
        // 然后遍历该数组,并依次加载这些懒加载字段
        String[] methodNames = methodNameSet.toArray(new String[methodNameSet.size()]);
        for (String methodName : methodNames) {
            load(methodName);
        }
    }

    private static String getUppercaseFirstProperty(String property) {
        String[] parts = property.split("\\.");
        return parts[0].toUpperCase(Locale.ENGLISH);
    }
}

6. 懒加载小结

LoadPair对象用于维护懒加载字段的信息,其主要有以下3个字段:

  1. property:目标字段名称
  2. metaResultObject:目标对象
  3. resultLoader:用于执行SQL语句,获取查询结果(即目标对象的目标字段的值)

MyBatis的懒加载是通过动态代理来实现的;代理对象可分为两种:原始的代理对象反序列化后的代理对象

  1. 原始的代理对象底层维护一个ResultLoaderMap,该组件底层是Map<String, LoadPair>
  2. 反序列化后的代理对象底层则直接维护Map<String, LoadPair>
  3. 原始的代理对象中的LoadPair的信息是完整的(包含目标对象),因此在加载字段时,无需指定目标对象,直接调用load()方法
  4. 反序列化后的代理对象的LoadPair丢失了目标对象信息,因此在加载字段时,需要指定目标对象,调用load(userObject)方法
  5. LoadPair在序列化时,没有直接序列化底层的ResultLoader,而是序列化了一些必要的字段
  6. 反序列化后的LoadPair对象在加载目标字段时,会先通过这些字段还原出ResultLoader对象,再加载目标字段
相关推荐
w_31234544 分钟前
自定义一个maven骨架 | 最佳实践
java·maven·intellij-idea
岁岁岁平安7 分钟前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA10 分钟前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
Q_192849990617 分钟前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
张国荣家的弟弟35 分钟前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S1 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos1 小时前
C++----------函数的调用机制
java·c++·算法
是小崔啊1 小时前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴
黄公子学安全1 小时前
Java的基础概念(一)
java·开发语言·python
liwulin05061 小时前
【JAVA】Tesseract-OCR截图屏幕指定区域识别0.4.2
java·开发语言·ocr