这是我参与8月更文挑战的第30天,活动详情查看:8月更文挑战
嵌套查询与懒加载的代码实现这里不再赘述,见文章Mybatis之缓存、懒加载。下面我们来直接分析源码,看看懒加载的执行过程。
时序图
sequenceDiagram
participant A as BaseExecutor
participant B as SimpleExecutor
participant C as PreparedStatementHandler
participant D as DefaultResultSetHandler
A ->> A : query
A ->> A : queryFromDatabase
A ->> B : doQuery
B ->> C : query
C ->>+ D : handleResultSets
D ->> D : handleResultSet
D ->> D : handleRowValues
D ->> D : handleRowValuesForSimpleResultMap
D ->> D : getRowValue
D ->> D : applyPropertyMappings
D ->> D : getPropertyMappingValue
D ->> D : getPropertyMappingValue
D -->>- A : List<Object>
关键代码
BaseExecutor#query
- query方法,所有查询都需要经过这里,如果是普通查询,则进入查询时queryStack=1,查询结束后queryStack=0
- 如果是嵌套查询,进入嵌套外部查询时,queryStack=1,进入嵌套内部查询时,queryStack=2,嵌套内部查询结束后queryStack=1,嵌套外部查询结束后queryStack=0.
/**
* 查询数据库中的数据
* @param ms 映射语句
* @param parameter 参数对象
* @param rowBounds 翻页限制条件
* @param resultHandler 结果处理器
* @param key 缓存的键
* @param boundSql 查询语句
* @param <E> 结果类型
* @return 结果列表
* @throws SQLException
*/
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
// 执行器已经关闭
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) { // 新的查询栈且要求清除缓存
// 新的查询栈,故清除本地缓存,即清除一级缓存
clearLocalCache();
}
List<E> list;
try {
/**
* 查询栈,第一次进入时,压栈
* 普通查询时,压栈后执行查询,完成后出栈,查询栈为0
* 嵌套查询时,第一次进入后压栈,在第一次查询还未完成(查询栈未出栈)时执行嵌套查询,会再次压栈,因此就会出现查询栈大于1的情况
*/
queryStack++;
// 尝试从本地缓存获取结果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 本地缓存中有结果,则对于CALLABLE语句还需要绑定到IN/INOUT参数上
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 本地缓存没有结果,故需要查询数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 查询结束后,出栈
queryStack--;
}
if (queryStack == 0) {
// 懒加载操作的处理
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
// 如果本地缓存的作用域为STATEMENT,则立刻清除本地缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
DefaultResultSetHandler#getRowValue
-
在getRowValue方法中,会先创建接收结果的空对象
-
判断是否开启自动映射,开启自动映射的配置及枚举如下
<setting name="autoMappingBehavior" value="PARTIAL"/>// 自动映射选项 public enum AutoMappingBehavior { /** * Disables auto-mapping. */ // 关闭自动映射 NONE, /** * Will only auto-map results with no nested result mappings defined inside. */ // 仅仅自动映射单层属性 PARTIAL, /** * Will auto-map result mappings of any complexity (containing nested or otherwise). */ // 映射所有属性,含嵌套属性 FULL } -
根据resultMap标签配置的映射关系进行映射
/**
* 将一条记录转化为一个对象
*
* @param rsw 结果集包装
* @param resultMap 结果映射
* @param columnPrefix 列前缀
* @return 转化得到的对象
* @throws SQLException
*/
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
// 懒加载
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 创建这一行记录对应的空对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 根据对象得到其MetaObject
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 是否允许自动映射未明示的字段
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 自动映射未明示的字段(resultType),映射时的TypeHandler通过属性名与set方法参数类型的映射来获取属性的类型,并据此获取对应的TypeHandler
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 按照明示的字段进行重新映射(resultMap),解析XML时获取TypeHandler
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
DefaultResultSetHandler#createResultObject
- 在createResultObject方法中,会根据resultMap标签中的type属性来创建结果集的接收对象
- 如果当前属性是嵌套查询,并且是懒加载,则会创建接收对象的代理对象,代理类是EnhancedResultObjectProxyImpl
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
final List<Class<?>> constructorArgTypes = new ArrayList<>();
final List<Object> constructorArgs = new ArrayList<>();
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149
// 如果是嵌套查询,并且是懒加载,则创建代理对象
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
DefaultResultSetHandler#applyPropertyMappings
- 获取
ResultSet中的列名 - 获取
resultMap标签中的映射配置 - 调用方法
getPropertyMappingValue获取对应的结果值 - 拿到映射关系对应的属性名称
- 调用
metaObject.setValue方法对属性进行赋值
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
// 获取ResultSet中的列名
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
// 获取resultMap映射配置
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// 获取映射配置中的完整列名
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
// 根据列名获取对应的值
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
// 获取映射配置中那个属性名
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERRED) {
// 如果是懒加载,则设置当前查询成功,并且直接进入下一次循环,不再为当前属性赋值
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
// 为属性设置值
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
DefaultResultSetHandler#getPropertyMappingValue
- 如果当前映射是嵌套查询,则调用
getNestedQueryMappingValue方法获取对应列的值 - 如果是普通查询,则直接调用
TypeHandler中的getResult方法获取对应列的值
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
if (propertyMapping.getNestedQueryId() != null) {
// 执行嵌套查询
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
// 处理resultSet属性(该属性与多结果集查询属性resultSets搭配)
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
// 普通查询
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}
DefaultResultSetHandler#getNestedQueryMappingValue
- 如果是非懒加载,则会根据加载好的参数,立即执行查询
BaseExecutor#query,并将结果返回 - 如果是懒加载,则会以当前属性名的全大写作为key,将执行查询所需的各项条件封装值作为value,存入lazyLoader中
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
// 获取嵌套查询的需要的参数
final String nestedQueryId = propertyMapping.getNestedQueryId();
final String property = propertyMapping.getProperty();
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
Object value = null;
// 查询参数不为空
if (nestedQueryParameterObject != null) {
// 获取SQL
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
// 生成CacheKey
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
final Class<?> targetType = propertyMapping.getJavaType();
if (executor.isCached(nestedQuery, key)) {
executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
value = DEFERRED;
} else {
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
// 是否懒加载
if (propertyMapping.isLazy()) {
// 懒加载,将懒加载的属性放入lazyLoader中
lazyLoader.addLoader(property, metaResultObject, resultLoader);
value = DEFERRED;
} else {
// 非懒加载直接执行查询
value = resultLoader.loadResult();
}
}
}
return value;
}
到这里,如果是懒加载,则懒加载的属性值为空,查询结束;如果不是懒加载,嵌套内部查询在查询出结果后,对该属性赋值,整个嵌套查询就结束了。而如果是懒加载,返回的对象是一个代理对象,当调用对象中的方法时就会走到代理类的
intercept方法中。
由于在XML中配置了CGLIB代理,因此这里走到了CglibProxyFactory类中。这里可以配置使用CGLIB代理或者JDK代理。
<!--指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。CGLIB | JAVASSIST-->
<setting name="proxyFactory" value="CGLIB"/>
CglibProxyFactory.EnhancedResultObjectProxyImpl#intercept
-
普通方法调用,进入到else代码块
-
对于激进加载属性
aggressive,可以在XML中进行配置<!--当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载,默认值false--> <setting name="aggressiveLazyLoading" value="false"/> -
当调用属性的get方法,并且lazyLoader中包含有该属性,则加载该属性的值
/**
* 代理类的拦截方法
* @param enhanced 代理对象本身
* @param method 被调用的方法
* @param args 每调用的方法的参数
* @param methodProxy 用来调用父类的代理
* @return 方法返回值
* @throws Throwable
*/
@Override
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)) { // 被调用的是writeReplace方法
// 创建一个原始对象
Object original;
if (constructorArgTypes.isEmpty()) {
original = objectFactory.create(type);
} else {
original = objectFactory.create(type, constructorArgTypes, constructorArgs);
}
// 将被代理对象的属性拷贝进入新创建的对象
PropertyCopier.copyBeanProperties(type, enhanced, original);
if (lazyLoader.size() > 0) { // 存在懒加载属性
// 则此时返回的信息要更多,不仅仅是原对象,还有相关的懒加载的设置等信息。因此使用CglibSerialStateHolder进行一次封装
return new CglibSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
} else {
// 没有未懒加载的属性了,那直接返回原对象进行序列化
return original;
}
} else {
if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { // 存在懒加载属性且被调用的不是finalize方法
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { // 设置了激进懒加载或者被调用的方法是能够触发全局懒加载的方法
// 完成所有属性的懒加载
lazyLoader.loadAll();
} else if (PropertyNamer.isSetter(methodName)) { // 调用了属性写方法
// 则先清除该属性的懒加载设置。该属性不需要被懒加载了
final String property = PropertyNamer.methodToProperty(methodName);
lazyLoader.remove(property);
} else if (PropertyNamer.isGetter(methodName)) { // 调用了属性读方法
final String property = PropertyNamer.methodToProperty(methodName);
// 如果该属性是尚未加载的懒加载属性,则进行懒加载
if (lazyLoader.hasLoader(property)) {
lazyLoader.load(property);
}
}
}
}
}
// 触发被代理类的相应方法。能够进行到这里的是除去writeReplace方法外的方法,例如读写方法、toString方法等
return methodProxy.invokeSuper(enhanced, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
以上就是Mybatis懒加载的实现原理。