MyBatis延遲載入原理(原始碼剖析)
阿新 • • 發佈:2020-12-15
MyBatis延遲載入原理:
它的原理是,使用 CGLIB 或 Javassist( 預設 ) 建立目標物件的代理物件。當呼叫代理物件的延遲載入屬性的 getting 方法時,進入攔截器方法。比如呼叫 a.getB().getName() 方法,進入攔截器的
invoke(…) 方法,發現 a.getB() 需要延遲載入時,那麼就會單獨傳送事先儲存好的查詢關聯 B 物件的 SQL ,把 B 查詢上來,然後呼叫 a.setB(b) 方法,於是 a 物件 b 屬性就有值了,接著完成
a.getB().getName() 方法的呼叫。這就是延遲載入的基本原理
代理物件的生成原理
- Mybatis的查詢結果是由ResultSetHandler介面的handleResultSets()方法處理的。ResultSetHandler介面只有一個實現,DefaultResultSetHandler,接下來看下延遲載入相關的一個核心的方法createResultObject()
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
this.useConstructorMappings = false;
List<Class<?>> constructorArgTypes = new ArrayList();
List<Object> constructorArgs = new ArrayList();
// 獲取返回值結果真實物件
Object resultObject = this.createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
// 返回值不等於null且返回值型別指定了resultMap標籤
if (resultObject != null && !this.hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 對於resultMap標籤裡的全部子標籤如:id,result,association,collection
List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
Iterator var9 = propertyMappings.iterator();
while(var9.hasNext()) {
ResultMapping propertyMapping = (ResultMapping)var9.next();
//判斷屬性有沒配置巢狀查詢(association,collection中的select屬性指定了StatemenId),且備註懶載入,如果有就建立代理物件
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
// 獲取代理物件
resultObject = this.configuration.getProxyFactory().createProxy(resultObject, lazyLoader, this.configuration, this.objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty();
return resultObject;
}
2.進入configuration.getProxyFactory()
// 預設為Javassist代理工廠
private proxyFactory = new JavassistProxyFactory();
public ProxyFactory getProxyFactory() {
return this.proxyFactory;
}
3.進入JavassistProxyFactory的createProxy()方法發現底層呼叫的是JavassistProxyFactory的靜態內部類EnhancedResultObjectProxyImpl的createProxy()方法
public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
// 結果集的物件型別
Class<?> type = target.getClass();
// 類似JDK動態代理InvocationHandler介面的實現類一樣,要實現invoke方法
JavassistProxyFactory.EnhancedResultObjectProxyImpl callback = new JavassistProxyFactory.EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
// 呼叫JavassistProxyFactory的靜態方法獲取代理物件
Object enhanced = JavassistProxyFactory.crateProxy(type, callback, constructorArgTypes, constructorArgs);
PropertyCopier.copyBeanProperties(type, target, enhanced);
return enhanced;
}
- 進入JavassistProxyFactory的靜態crateProxy方法
static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
org.apache.ibatis.javassist.util.proxy.ProxyFactory enhancer = new org.apache.ibatis.javassist.util.proxy.ProxyFactory();
enhancer.setSuperclass(type);
try {
type.getDeclaredMethod("writeReplace");
if (log.isDebugEnabled()) {
log.debug("writeReplace method was found on bean " + type + ", make sure it returns this");
}
} catch (NoSuchMethodException var10) {
enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class});
} catch (SecurityException var11) {
}
Class<?>[] typesArray = (Class[])constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
Object enhanced;
try {
// 建立代理類
enhanced = enhancer.create(typesArray, valuesArray);
} catch (Exception var9) {
throw new ExecutorException("Error creating lazy proxy. Cause: " + var9, var9);
}
// 為代理類繫結處理執行器
((Proxy)enhanced).setHandler(callback);
return enhanced;
}
總結:當返回結果集有配置巢狀查詢(association,collection中的select屬性指定了StatemenId),且備註懶載入,如果有就基於Javassis產生的代理類
代理物件呼叫懶載入屬性的載入原理
1.當懶載入代理物件執行方法是實際執行的是代理物件執行器EnhancedResultObjectProxyImpl的invoke方法
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
String methodName = method.getName();
try {
synchronized(this.lazyLoader) {
if ("writeReplace".equals(methodName)) {
Object original;
if (this.constructorArgTypes.isEmpty()) {
original = this.objectFactory.create(this.type);
} else {
original = this.objectFactory.create(this.type, this.constructorArgTypes, this.constructorArgs);
}
PropertyCopier.copyBeanProperties(this.type, enhanced, original);
if (this.lazyLoader.size() > 0) {
return new JavassistSerialStateHolder(original, this.lazyLoader.getProperties(), this.objectFactory, this.constructorArgTypes, this.constructorArgs);
}
return original;
}
//延遲載入數量大於0
if (this.lazyLoader.size() > 0 && !"finalize".equals(methodName)) {
//aggressive 一次載入性所有需要要延遲載入屬性或者包含觸發延遲載入方法
//對於延遲載入的兩個可配置屬性,一個為是否一個方法執行全部屬性都載入,第二個為指定方法執行是進行全部延時載入(預設指定方法包括hashCode,toString等)
if (!this.aggressive && !this.lazyLoadTriggerMethods.contains(methodName)) {
String property;
// 是否為set方法
if (PropertyNamer.isSetter(methodName)) {
property = PropertyNamer.methodToProperty(methodName);
this.lazyLoader.remove(property);
// 是否為get方法
} else if (PropertyNamer.isGetter(methodName)) {
property = PropertyNamer.methodToProperty(methodName);
// 該屬性是否設定懶載入
if (this.lazyLoader.hasLoader(property)) {
// 執行預先快取好的sql查詢,載入當前屬性 ,查詢後呼叫set方法為該屬性賦值
this.lazyLoader.load(property);
}
}
} else {
// 執行sql查詢,載入全部屬性
this.lazyLoader.loadAll();
}
}
}
// 繼續執行原方法
return methodProxy.invoke(enhanced, args);
} catch (Throwable var10) {
throw ExceptionUtil.unwrapThrowable(var10);
}
}
總結:當懶載入代理物件執行方法是會被代理物件的攔截處理器監聽執行invoke方法,在invoke方法中會判斷改屬性是否需要延遲載入以及是否會導致全部屬性延遲載入,如果會就執行事先儲存好的查詢sql屬性結果並呼叫set方法為該懶載入物件的屬性賦值,並繼續執行原方法。