mybatis-deffload
Posted siye1989
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis-deffload相关的知识,希望对你有一定的参考价值。
1. 概述
本文,我们来分享 SQL 执行的第五部分,延迟加载的功能的实现,涉及 executor/loader
包。整体类图如下:
- 从类图,我们发现,延迟加载的功能,是通过动态代理实现的。也就是说,通过拦截指定方法,执行数据加载,从而实现延迟加载。
- 并且,MyBatis 提供了 Cglib 和 Javassist 两种动态代理的创建方式。
在 《精尽 MyBatis 源码分析 —— SQL 执行(四)之 ResultSetHandler》 方法中,我们已经看到延迟加载相关的代码,下面让我们一处一处来看看。
另外,如果胖友并未使用过 MyBatis 的延迟加载的功能,可以先看看 《【MyBatis框架】高级映射-延迟加载》 文章。
2. ResultLoader
在 DefaultResultSetHandler 的 #getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)
方法中,我们可以看到 ResultLoader 的身影。代码如下:
// DefaultResultSetHandler.java
|
<x>
处,因为是结果对象的构造方法中使用的值,无法使用延迟加载的功能,所以使用 ResultLoader 直接加载。
org.apache.ibatis.executor.loader.ResultLoader
,结果加载器。
2.1 构造方法
// ResultLoader.java
|
- 重点属性,看添加了中文注释的。
2.2 loadResult
#loadResult()
方法,加载结果。代码如下:
// ResultLoader.java
|
<1>
处,调用#selectList()
方法,查询结果。详细解析,见 「2.3 selectList」 。<2>
处,调用ResultExtractor#extractObjectFromList(List<Object> list, Class<?> targetType)
方法,提取结果。详细解析,见 「3. ResultExtractor」 。<3>
处,返回结果。
2.3 selectList
#selectList()
方法,查询结果。代码如下:
// ResultLoader.java
|
-
<1>
处,如果当前线程不是创建线程,则调用#newExecutor()
方法,创建 Executor 对象,因为 Executor 是非线程安全的。代码如下:// ResultLoader.java
private Executor newExecutor()
// 校验 environment
final Environment environment = configuration.getEnvironment();
if (environment == null)
throw new ExecutorException("ResultLoader could not load lazily. Environment was not configured.");
// 校验 ds
final DataSource ds = environment.getDataSource();
if (ds == null)
throw new ExecutorException("ResultLoader could not load lazily. DataSource was not configured.");
// 创建 Transaction 对象
final TransactionFactory transactionFactory = environment.getTransactionFactory();
final Transaction tx = transactionFactory.newTransaction(ds, null, false);
// 创建 Executor 对象
return configuration.newExecutor(tx, ExecutorType.SIMPLE); -
<2>
处,调用Executor#query(...)
方法,执行查询。 <3>
处,如果是新创建的 Executor 对象,则调用Executor#close()
方法,关闭 Executor 对象。
2.4 wasNull
#wasNull()
方法,是否结果为空。代码如下:
// ResultLoader.java
|
3. ResultExtractor
org.apache.ibatis.executor.ResultExtractor
,结果提取器。代码如下:
// ResultExtractor.java
|
- 分成四种情况,胖友看下代码注释。
4. ResultLoaderMap
在 DefaultResultSetHandler 的 #getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
方法中,我们可以看到 ResultLoaderMap 的身影。代码如下:
// DefaultResultSetHandler.java
|
<x>
处,因为是结果对象的 setting 方法中使用的值,可以使用延迟加载的功能,所以使用 ResultLoaderMap 记录。最终会创建结果对象的代理对象,而 ResultLoaderMap 对象会传入其中,作为一个参数。从而能够,在加载该属性时,能够调用ResultLoader#loadResult()
方法,加载结果。-
另外,在
<y>
处,检查缓存中已存在,则会调用Executor#deferLoad(...)
方法,尝试加载结果。代码如下:老艿艿:此处是插入,?? 找不到适合放这块内容的地方了,哈哈哈。
// 该方法在 BaseExecutor 抽象类中实现
// BaseExecutor.java
/**
* DeferredLoad( 延迟加载 ) 队列
*/
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;- 代码比较简单,胖友自己瞅瞅。
org.apache.ibatis.executor.loader.ResultLoaderMap
, ResultLoader 的映射。该映射,最终创建代理对象时,会作为参数传入代理。
4.1 构造方法
// ResultLoaderMap.java
|
4.2 addLoader
#addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader)
方法,添加到 loaderMap
中。代码如下:
// ResultLoaderMap.java
|
-
其中,LoadPair 是 ResultLoaderMap 的内部静态类。代码如下:
// ResultLoaderMap.java
public static class LoadPair implements Serializable
private static final long serialVersionUID = 20130412;
/**
* Name of factory method which returns database connection.
*/
private static final String FACTORY_METHOD = "getConfiguration";
/**
* Object to check whether we went through serialization..
*/
private final transient Object serializationCheck = new Object();
/**
* Meta object which sets loaded properties.
*/
private transient MetaObject metaResultObject;
/**
* Result loader which loads unread properties.
*/
private transient ResultLoader resultLoader;
/**
* Wow, logger.
*/
private transient Log log;
/**
* Factory class through which we get database connection.
*/
private Class<?> configurationFactory;
/**
* Name of the unread property.
*/
private String property;
/**
* ID of SQL statement which loads the property.
*/
private String mappedStatement;
/**
* Parameter of the sql statement.
*/
private Serializable mappedParameter;
private LoadPair(final String property, MetaObject metaResultObject, ResultLoader resultLoader)
this.property = property;
this.metaResultObject = metaResultObject;
this.resultLoader = resultLoader;
/* Save required information only if original object can be serialized. */
// 当 `metaResultObject.originalObject` 可序列化时,则记录 mappedStatement、mappedParameter、configurationFactory 属性
if (metaResultObject != null && metaResultObject.getOriginalObject() instanceof Serializable)
final Object mappedStatementParameter = resultLoader.parameterObject;
/* @todo May the parameter be null? */
if (mappedStatementParameter instanceof Serializable)
this.mappedStatement = resultLoader.mappedStatement.getId();
this.mappedParameter = (Serializable) mappedStatementParameter;
this.configurationFactory = resultLoader.configuration.getConfigurationFactory();
else
Log log = this.getLogger();
if (log.isDebugEnabled())
log.debug("Property [" + this.property + "] of ["
+ metaResultObject.getOriginalObject().getClass() + "] cannot be loaded "
+ "after deserialization. Make sure it‘s loaded before serializing "
+ "forenamed object.");
// ... 暂时省略其它方法
4.3 load
#load(String property)
方法,执行指定属性的加载。代码如下:
// ResultLoaderMap.java
|
- 调用
LoadPair#load()
方法,执行加载。代码如下:
// ResultLoaderMap.java
|
<1>
和<2>
处,胖友可以暂时无视,主要用于延迟加载在序列化和反序列化的时候,一般很少碰到。当然,感兴趣的胖友,可以调试下org.apache.ibatis.submitted.lazy_deserialize.LazyDeserializeTest
单元测试类。- 【重点】
<3>
处,调用ResultLoader#loadResult()
方法,执行查询结果。 <3>
处,调用MetaObject#setValue(String name, Object value)
方法,设置属性。
4.4 loadAll
#loadAll()
方法,执行所有属性的加载。代码如下:
// ResultLoaderMap.java
|
4.5 其它方法
ResultLoaderMap 还有其它方法,比较简单,胖友可以自己看看。
5. ProxyFactory
在 DefaultResultSetHandler 的 #createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix)
方法中,我们可以看到 ProxyFactory 的身影。代码如下:
// DefaultResultSetHandler.java
|
<x>
处,调用ProxyFactory#createProxy(...)
方法,创建结果对象的代理对象。
org.apache.ibatis.executor.loader.ProxyFactory
,代理工厂接口,用于创建需要延迟加载属性的结果对象。代码如下:
// ProxyFactory.java
|
-
ProxyFactory 有 JavassistProxyFactory 和 CglibProxyFactory 两个实现类,默认使用前者。原因见如下代码:
// Configuration.java
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
5.1 JavassistProxyFactory
org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory
,实现 ProxyFactory 接口,基于 Javassist 的 ProxyFactory 实现类。
5.1.1 构造方法
// JavassistProxyFactory.java
|
5.1.2 createDeserializationProxy
#createDeserializationProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)
方法,创建支持反序列化的代理对象。代码如下:
// JavassistProxyFactory.java
|
- 因为实际场景下,不太使用该功能,所以本文暂时无视。
5.1.3 createProxy 普通方法
#createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)
方法,创建代理对象。代码如下:
// JavassistProxyFactory.java
|
5.1.4 crateProxy 静态方法
#crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)
静态方法,创建代理对象。代码如下:
// JavassistProxyFactory.java
|
- 常见的基于 Javassist 的 API ,创建代理对象。
<x>
处,设置代理对象的执行器。该执行器,就是 EnhancedResultObjectProxyImpl 对象。详细解析,见 「5.1.5 EnhancedResultObjectProxyImpl」 。
5.1.5 EnhancedResultObjectProxyImpl
EnhancedResultObjectProxyImpl ,是 JavassistProxyFactory 的内部静态类,实现 javassist.util.proxy.MethodHandler
接口,方法处理器实现类。
5.1.5.1 构造方法
// JavassistProxyFactory.java
|
-
涉及的
aggressive
和lazyLoadTriggerMethods
属性,在 Configuration 定义如下:// Configuration.java
/**
* 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods)
*/
protected boolean aggressiveLazyLoading;
/**
* 指定哪个对象的方法触发一次延迟加载。
*/
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
5.1.5.2 createProxy
#createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)
方法,创建代理对象,并设置方法处理器为 EnhancedResultObjectProxyImpl 对象。代码如下:
因为方法名 createProxy 一直在重复,所以这里艿艿说下调用链:
「5.1.3 createProxy 普通方法」 => 「5.1.4.2 createProxy」 => 「5.1.4 createProxy 静态方法」
// JavassistProxyFactory.java
|
- 代码比较简单,胖友仔细瞅瞅,不要绕晕噢。
5.1.5.3 invoke
#invoke(Object enhanced, Method method, Method methodProxy, Object[] args)
方法,执行方法。代码如下:
// JavassistProxyFactory.java
|
<1.1>
处,如果满足条件,则调用ResultLoaderMap#loadAll()
方法,加载所有延迟加载的属性。<1.2>
处,如果调用了 setting 方法,则调用ResultLoaderMap#remove(String property)
方法,不在使用延迟加载。因为,具体的值都设置了,无需在延迟加载了。<1.3>
处,如果调用了 getting 方法,则调用ResultLoaderMap#load(String property)
方法,执行指定属性的延迟加载。此处,就会去数据库中查询,并设置到对应的属性。<2>
处,继续执行原方法。
5.2 CglibProxyFactory
org.apache.ibatis.executor.loader.cglib.CglibProxyFactory
,实现 ProxyFactory 接口,基于 Cglib 的 ProxyFactory 实现类。
以上是关于mybatis-deffload的主要内容,如果未能解决你的问题,请参考以下文章