This is the 30th day of my participation in the August Text Challenge.More challenges in August
The code implementation of nested queries and lazy loading is not described here, see article Mybatis caching, lazy loading. Let’s analyze the source code directly and see how lazy loading is executed.
Sequence diagram
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>
The key code
BaseExecutor#query
- Query method, all queries need to pass through here. If it is a normal query, the query enters the query with queryStack=1 and the query ends with queryStack=0
- For nested queries, queryStack=1 when entering the nested external query, queryStack=2 when entering the nested internal query, queryStack=1 after the nested internal query, and queryStack=0 after the nested external query.
/** * query data in the database *@paramMs mapping statement *@paramParameter Specifies the parameter object *@paramRowBounds Page turn restrictions *@paramResultHandler resultHandler *@paramKey The cached key *@paramBoundSql query statement *@param<E> Result type *@returnResult list@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) {
// The actuator is closed
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) { // New query stack and request to clear cache
// New query stack, so clear local cache, i.e. clear level cache
clearLocalCache();
}
List<E> list;
try {
/ * * * query stack, entering for the first time, the pressure of stack * ordinary query, execute queries after pressure stack, stack, after the completion of nested query, query the stack of 0 * for the first time into the stack, after the first query has yet to be completed (query not out of the stack) executed when nested query, will pop down again, so there will be a query * / stack is greater than 1
queryStack++;
// Try to get the result from the local cache
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if(list ! =null) {
// If there is a result IN the local cache, you also need to bind to the IN/INOUT parameter for the CALLABLE statement
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// The local cache has no result, so you need to query the databaselist = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }}finally {
// Exit the stack after the query is complete
queryStack--;
}
if (queryStack == 0) {
// Handle lazy loading operations
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
// If the local cache scope is STATEMENT, the local cache is cleared immediately
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482clearLocalCache(); }}return list;
}
Copy the code
DefaultResultSetHandler#getRowValue
-
In the getRowValue method, the empty object that receives the result is created first
-
Determine whether to enable automatic mapping. The configurations and enumerations for enabling automatic mapping are as follows
<setting name="autoMappingBehavior" value="PARTIAL"/> Copy the code
// Automatic mapping options public enum AutoMappingBehavior { /** * Disables auto-mapping. */ // Disable automatic mapping NONE, /** * Will only auto-map results with no nested result mappings defined inside. */ // Only single-layer attributes are automatically mapped PARTIAL, /** * Will auto-map result mappings of any complexity (containing nested or otherwise). */ // Map all attributes, including nested attributes FULL } Copy the code
-
The mapping is performed based on the mapping relationship configured on the resultMap label
/** * Convert a record to an object **@paramRSW result set packaging *@paramResultMap Indicates the result mapping *@paramColumnPrefix columnPrefix *@returnThe converted object *@throws SQLException
*/
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
/ / lazy loading
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// Create an empty object for this row
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if(rowValue ! =null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// Get the MetaObject from the object
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// Whether to allow automatic mapping of unstated fields
if (shouldApplyAutomaticMappings(resultMap, false)) {
// Automatically map an unspecified field (resultType). The Mapping TypeHandler obtains the attribute type by mapping the attribute name to the parameter type of the set method, and obtains the corresponding TypeHandler accordingly
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// Remap according to explicit fields (resultMap), retrieve TypeHandler when parsing XML
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
Copy the code
DefaultResultSetHandler#createResultObject
- In the createResultObject method, the receiver object for the result set is created based on the Type attribute in the resultMap tag
- If the current attribute is a nested query, and the lazy loading, will create the receiving object proxy objects, the proxy class is EnhancedResultObjectProxyImpl
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
finalList<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 the query is nested and lazily loaded, create a proxy object
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;
}
Copy the code
DefaultResultSetHandler#applyPropertyMappings
- To obtain
ResultSet
The column - To obtain
resultMap
The mapping configuration in the tag - A method is called
getPropertyMappingValue
Gets the corresponding result value - Get the attribute name for the mapping
- call
metaObject.setValue
Method to assign a value to a property
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
// Get the column name in the ResultSet
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
// Obtain the resultMap mapping configuration
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// Get the full column name in the mapping configuration
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) {
// Get the corresponding value based on the column name
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
// Get the attribute name in the mapping configuration
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERRED) {
// If the load is lazy, the current query is set to succeed and the next loop is entered without assigning the current attribute
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')
// Set the value for the propertymetaObject.setValue(property, value); }}}return foundValues;
}
Copy the code
DefaultResultSetHandler#getPropertyMappingValue
- Is called if the current mapping is a nested query
getNestedQueryMappingValue
Method to get the value of the corresponding column - If it is a common query, it is directly called
TypeHandler
In thegetResult
Method to get the value of the corresponding column
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
if(propertyMapping.getNestedQueryId() ! =null) {
// Perform nested queries
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if(propertyMapping.getResultSet() ! =null) {
// Handle the resultSet attribute (this attribute is paired with the multi-result set query attribute resultSets)
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
// Common query
finalTypeHandler<? > typeHandler = propertyMapping.getTypeHandler();final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
returntypeHandler.getResult(rs, column); }}Copy the code
DefaultResultSetHandler#getNestedQueryMappingValue
- If the load is not lazy, the query is executed immediately based on the loaded parameters
BaseExecutor#query
And returns the result - In lazy loading mode, the lazyLoader uses all uppercase values of the current attribute name as the key, encapsulates the conditions required for the query as values, and stores them in the lazyLoader
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
// Get the parameters needed for the nested query
final String nestedQueryId = propertyMapping.getNestedQueryId();
final String property = propertyMapping.getProperty();
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
finalClass<? > nestedQueryParameterType = nestedQuery.getParameterMap().getType();final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
Object value = null;
// The query parameter is not empty
if(nestedQueryParameterObject ! =null) {
/ / access to SQL
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
/ / generated CacheKey
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
finalClass<? > 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);
// Whether to load lazily
if (propertyMapping.isLazy()) {
// Lazy load, put lazy load attributes into the lazyLoader
lazyLoader.addLoader(property, metaResultObject, resultLoader);
value = DEFERRED;
} else {
// Execute the query directly without lazy loadingvalue = resultLoader.loadResult(); }}}return value;
}
Copy the code
If lazy loading is performed, the lazy loading property value is null, and the query ends. If not lazily loaded, the nested internal query will end by assigning this attribute to the result of the query. Lazy loading, on the other hand, returns a proxy object that goes into the Intercept method of the proxy class when a method in the object is called.
Because the CGLIB proxy is configured in the XML, this goes into the CglibProxyFactory class. You can configure the CGLIB proxy or JDK proxy here.
<! -- Specify the proxy tool Mybatis uses to create lazy-loading objects. CGLIB | JAVASSIST-->
<setting name="proxyFactory" value="CGLIB"/>
Copy the code
CglibProxyFactory.EnhancedResultObjectProxyImpl#intercept
-
Normal method call, going into the else code block
-
The aggressive load attribute aggressive can be configured in XML
<! When enabled, any method call loads all properties of the object. Otherwise, each attribute is loaded on demand, default is false--> <setting name="aggressiveLazyLoading" value="false"/> Copy the code
-
When the property’s get method is called and the property is included in the lazyLoader, the value of the property is loaded
/** * Proxy class interceptor method *@paramEnhanced proxy object itself *@paramMethod Specifies the method to be called@paramArgs Arguments to each method called *@paramMethodProxy is used to call the parent class's proxy *@returnMethod returns the value *@throws Throwable
*/
@Override
public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// Retrieve the name of the method called in the propped class
final String methodName = method.getName();
try {
synchronized (lazyLoader) { // Prevent concurrent loading of attributes
if (WRITE_REPLACE_METHOD.equals(methodName)) { // The writeReplace method is called
// Create a primitive object
Object original;
if (constructorArgTypes.isEmpty()) {
original = objectFactory.create(type);
} else {
original = objectFactory.create(type, constructorArgTypes, constructorArgs);
}
// Copy the properties of the proxied object into the newly created object
PropertyCopier.copyBeanProperties(type, enhanced, original);
if (lazyLoader.size() > 0) { // Lazy loading property exists
// More information is returned, not only about the original object, but also about lazy loading Settings. So use CglibSerialStateHolder for one encapsulation
return new CglibSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
} else {
// There are no unloaded attributes, so return the original object for serialization
returnoriginal; }}else {
if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { // Lazy attribute exists and finalize method is not called
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { // Methods with aggressive lazy loading set or called are methods that trigger global lazy loading
// Complete lazy loading of all attributes
lazyLoader.loadAll();
} else if (PropertyNamer.isSetter(methodName)) { // The property write method is called
// Clear lazy load Settings for this property. This property does not need to be lazily loaded
final String property = PropertyNamer.methodToProperty(methodName);
lazyLoader.remove(property);
} else if (PropertyNamer.isGetter(methodName)) { // The property read method was called
final String property = PropertyNamer.methodToProperty(methodName);
// If the property is a lazy loaded property that has not been loaded, lazy loading is performed
if (lazyLoader.hasLoader(property)) {
lazyLoader.load(property);
}
}
}
}
}
// Trigger the corresponding method of the proxied class. Methods other than writeReplace, such as accessor methods, toString methods, and so on, can do this
return methodProxy.invokeSuper(enhanced, args);
} catch (Throwable t) {
throwExceptionUtil.unwrapThrowable(t); }}Copy the code
Above is the realization principle of lazy loading of Mybatis.