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());
}
}
- 显然,程序中的
blog
肯定不是原始的实体类对象,而应该是个代理对象 - 当调用
blog.getAuthor()
方法时,代理对象会去执行SQL语句,查询出作者信息
值得注意的是,如果使用Java的序列化机制对
Blog
类的代理对象进行序列化,那么序列化时不会加载Author
信息当我们调用反序列化后的对象的
getAuthor()
方法时,才会执行SQL语句查询出Author
信息
1.2. writeReplace()/readResolve()
既然提到了Java的序列化机制,那这里就顺便说明一下这两个和序列化相关的方法:
- 在对A对象进行序列化时,如果发现A有
Object writeReplace()
方法,则会调用该方法,并对其返回值进行序列化 - 当反序列化得到B对象时,如果发现B有
Object readResolve()
方法,则会将该方法的返回值作为最终反序列化的结果 - 另外,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;
}
}
EnhancedResultObjectProxyImpl
是JavassistProxyFactory
的静态内部类:
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. 反序列化时代理对象的创建
- 我们知道,代理对象在序列化时,如果它还有懒加载字段,那么实际上序列化的是
JavassistSerialStateHolder
对象 - 那么,在进行反序列化时,得到的自然也是
JavassistSerialStateHolder
对象 JavassistSerialStateHolder
的父类AbstractSerialStateHolder
提供了readResolve()
方法- 父类的
readResolve()
方法会准备一些必要数据,然后调用抽象方法createDeserializationProxy()
并将其返回值返回 - 因此,
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);
}
EnhancedDeserializationProxyImpl
是JavassistProxyFactory
的静态内部类:
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个字段:
property
:目标字段名称metaResultObject
:目标对象resultLoader
:用于执行SQL语句,获取查询结果(即目标对象的目标字段的值)
MyBatis的懒加载是通过动态代理来实现的;代理对象可分为两种:原始的代理对象 和反序列化后的代理对象:
- 原始的代理对象底层维护一个
ResultLoaderMap
,该组件底层是Map<String, LoadPair>
- 反序列化后的代理对象底层则直接维护
Map<String, LoadPair>
- 原始的代理对象中的
LoadPair
的信息是完整的(包含目标对象),因此在加载字段时,无需指定目标对象,直接调用load()
方法 - 反序列化后的代理对象的
LoadPair
丢失了目标对象信息,因此在加载字段时,需要指定目标对象,调用load(userObject)
方法 LoadPair
在序列化时,没有直接序列化底层的ResultLoader
,而是序列化了一些必要的字段- 反序列化后的
LoadPair
对象在加载目标字段时,会先通过这些字段还原出ResultLoader
对象,再加载目标字段