天天看點

Mybatis源碼分析——結果集ResultSet自動映射成實體類對象

前言

上一篇文章我們已經将SQL發送到了資料庫,并傳回了ResultSet,接下來就是将結果集 ResultSet 自動映射成實體類對象。這樣使用者就無需再手動操作結果集,并将資料填充到實體類對象中。這可大大降低開發的工作量,提高工作效率。

映射結果入口

我們來看看上次看源碼的位置

public class PreparedStatementHandler extends BaseStatementHandler {

	@Override
	public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
		PreparedStatement ps = (PreparedStatement) statement;
		//執行資料庫SQL
		ps.execute();
		//進行resultSet自動映射
		return resultSetHandler.<E> handleResultSets(ps);
	}
}
           

結果集的處理入口方法是 handleResultSets

public class DefaultResultSetHandler implements ResultSetHandler {

	public List<Object> handleResultSets(Statement stmt) throws SQLException {
		ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

		final List<Object> multipleResults = new ArrayList<Object>();

		int resultSetCount = 0;
		//擷取第一個ResultSet,通常隻會有一個
		ResultSetWrapper rsw = getFirstResultSet(stmt);
		
		//從配置中讀取對應的ResultMap,通常也隻會有一個,設定多個是通過逗号來分隔,我們平時有這樣設定嗎?
		List<ResultMap> resultMaps = mappedStatement.getResultMaps();
		int resultMapCount = resultMaps.size();
		validateResultMapsCount(rsw, resultMapCount);
		while (rsw != null && resultMapCount > resultSetCount) {
			ResultMap resultMap = resultMaps.get(resultSetCount);
			// 處理結果集
			handleResultSet(rsw, resultMap, multipleResults, null);
			//擷取結果集的下一個結果
			rsw = getNextResultSet(stmt);
			cleanUpAfterHandlingResultSet();
			resultSetCount++;
		}
		
		String[] resultSets = mappedStatement.getResultSets();
		if (resultSets != null) {
			//和resultMaps的周遊處理類似
			while (rsw != null && resultSetCount < resultSets.length) {
			ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
				if (parentMapping != null) {
					String nestedResultMapId = parentMapping.getNestedResultMapId();
					ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
					handleResultSet(rsw, resultMap, null, parentMapping);
				}
				rsw = getNextResultSet(stmt);
				cleanUpAfterHandlingResultSet();
				resultSetCount++;
			}
		}

		return collapseSingleResultList(multipleResults);
	}
}
           

在實際運作過程中,通常情況下一個Sql語句隻傳回一個結果集,對多個結果集的情況不做分析 。實際很少用到。繼續看handleResultSet方法

public class DefaultResultSetHandler implements ResultSetHandler {

	private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
		try {
			if (parentMapping != null) {
				handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
			} else {
				if (resultHandler == null) {
					// 建立預設的結果處理器
					DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
					// 處理結果集的行資料
					handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
					// 将結果加入multipleResults中
					multipleResults.add(defaultResultHandler.getResultList());
				} else {
					handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
				}
			}
		} finally {
			// issue #228 (close resultsets)
			closeResultSet(rsw.getResultSet());
		}
	}
}
           

通過handleRowValues 映射ResultSet結果,最後映射的結果會在defaultResultHandler的ResultList集合中,最後将結果加入到multipleResults中就可以傳回了,我們繼續跟進handleRowValues這個核心方法

public class DefaultResultSetHandler implements ResultSetHandler {

	public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
		if (resultMap.hasNestedResultMaps()) {
			ensureNoRowBounds();
			checkResultHandler();
			// 處理嵌套映射
			handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
		} else {
			// 處理簡單映射
			handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
		}
	}
}
           

我們可以通過resultMap.hasNestedResultMaps()知道查詢語句是否是嵌套查詢,如果resultMap中包含<association> 和 <collection>且其select屬性不為空,則為嵌套查詢。本文先分析簡單的映射

public class DefaultResultSetHandler implements ResultSetHandler {

	private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
	  throws SQLException {
		DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
		// 根據 RowBounds 定位到指定行記錄
		skipRows(rsw.getResultSet(), rowBounds);
		// ResultSet是一個集合,很有可能我們查詢的就是一個List,這就就每條資料周遊處理
		while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
			ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
			// 從 resultSet 中擷取結果
			Object rowValue = getRowValue(rsw, discriminatedResultMap);
			// 存儲結果到resultHandler的ResultList,最後ResultList加入multipleResults中傳回
			storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
		}
	}
}
           

我們查詢的結果很有可能是一個集合,是以這裡要周遊集合,每條結果單獨進行映射,最後映射的結果加入到resultHandler的ResultList

MyBatis 預設提供了 RowBounds 用于分頁,從上面的代碼中可以看出,這并非是一個高效的分頁方式,是查出所有的資料,進行記憶體分頁。除了使用 RowBounds,還可以使用一些第三方分頁插件進行分頁。我們後面文章來講,我們來看關鍵代碼getRowValue,處理一行資料

public class DefaultResultSetHandler implements ResultSetHandler {

	private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
		// 這個Map是用來存儲延遲加載的BountSql的,我們下面來看
		final ResultLoaderMap lazyLoader = new ResultLoaderMap();
		// 建立實體類對象,比如 User 對象
		Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
		if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
			final MetaObject metaObject = configuration.newMetaObject(rowValue);
			boolean foundValues = this.useConstructorMappings;
			if (shouldApplyAutomaticMappings(resultMap, false)) {
				//自動映射,結果集中有的column,但resultMap中并沒有配置  
				foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
			}
			// 根據 <resultMap> 節點中配置的映射關系進行映射
			foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
			foundValues = lazyLoader.size() > 0 || foundValues;
			rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
		}
		return rowValue;
	}
}
           

重要的邏輯已經注釋出來了。分别如下:

  • 1、建立實體類對象。
  • 2、自動映射結果集中有的column,但resultMap中并沒有配置。
  • 3、根據 <resultMap> 節點中配置的映射關系進行映射。

建立實體類對象

我們想将查詢結果映射成實體類對象,第一步當然是要建立實體類對象了,下面我們來看一下 MyBatis 建立實體類對象的過程。

public class DefaultResultSetHandler implements ResultSetHandler {

	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<Class<?>>();
		final List<Object> constructorArgs = new ArrayList<Object>();
		
		// 調用重載方法建立實體類對象
		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
				// 如果開啟了延遲加載,則為 resultObject 生成代理類,如果僅僅是配置的關聯查詢,沒有開啟延遲加載,是不會建立代理類
				if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
					/*
					 * 建立代理類,預設使用 Javassist 架構生成代理類。
					 * 由于實體類通常不會實作接口,是以不能使用 JDK 動态代理 API 為實體類生成代理。
					 * 并且将lazyLoader傳進去了
					 */
					resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
					break;
				}
			}
		}
		this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
		return resultObject;
	}
}
           

我們先來看 createResultObject 重載方法的邏輯

public class DefaultResultSetHandler implements ResultSetHandler {

	private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
	  throws SQLException {
		final Class<?> resultType = resultMap.getType();
		final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
		final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
		if (hasTypeHandlerForResultObject(rsw, resultType)) {
			return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
		} else if (!constructorMappings.isEmpty()) {
			return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
		} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
			// 通過 ObjectFactory 調用目标類的預設構造方法建立執行個體
			return objectFactory.create(resultType);
		} else if (shouldApplyAutomaticMappings(resultMap, false)) {
			return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
		}
		throw new ExecutorException("Do not know how to create an instance of " + resultType);
	}
}
           

一般情況下,MyBatis 會通過 ObjectFactory 調用預設構造方法建立實體類對象。看看是如何建立的

public class DefaultObjectFactory implements ObjectFactory, Serializable {

	@Override
	public <T> T create(Class<T> type) {
		return create(type, null, null);
	}
	
	@Override
	public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
		Class<?> classToCreate = resolveInterface(type);
		// we know types are assignable
		return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
	}
	
	private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
		try {
			Constructor<T> constructor;
			if (constructorArgTypes == null || constructorArgs == null) {
				constructor = type.getDeclaredConstructor();
				if (!constructor.isAccessible()) {
					constructor.setAccessible(true);
				}
				return constructor.newInstance();
			}
			//通過反射擷取構造器
			constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
			if (!constructor.isAccessible()) {
				constructor.setAccessible(true);
			}
			//通過構造器來執行個體化對象
			return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
		} catch (Exception e) {
			StringBuilder argTypes = new StringBuilder();
			if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
				for (Class<?> argType : constructorArgTypes) {
					argTypes.append(argType.getSimpleName());
					argTypes.append(",");
				}
				argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
			}
			StringBuilder argValues = new StringBuilder();
			if (constructorArgs != null && !constructorArgs.isEmpty()) {
				for (Object argValue : constructorArgs) {
					argValues.append(String.valueOf(argValue));
					argValues.append(",");
				}
				argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
			}
			throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
		}
	}
}
           

很簡單,就是通過反射建立對象

結果集映射

映射結果集分為兩種情況:

  • 1、是自動映射(結果集有但在resultMap裡沒有配置的字段),在實際應用中,都會使用自動映射,減少配置的工作。自動映射在Mybatis中也是預設開啟的。
  • 2、是映射ResultMap中配置的。

我們分這兩者映射來看

自動映射

foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;

public class DefaultResultSetHandler implements ResultSetHandler {

	private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
		 // 擷取 UnMappedColumnAutoMapping 清單
		List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
		boolean foundValues = false;
		if (!autoMapping.isEmpty()) {
			for (UnMappedColumnAutoMapping mapping : autoMapping) {
				// 通過 TypeHandler 從結果集中擷取指定列的資料
				final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
				if (value != null) {
					foundValues = true;
				}
				if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
					// gcode issue #377, call setter on nulls (value is not 'found')
					// 通過元資訊對象設定 value 到實體類對象的指定字段上
					metaObject.setValue(mapping.property, value);
				}
			}
		}
		return foundValues;
	}
}
           

首先是擷取 UnMappedColumnAutoMapping 集合,然後周遊該集合,并通過 TypeHandler 從結果集中擷取資料,最後再将擷取到的資料設定到實體類對象中。

UnMappedColumnAutoMapping 用于記錄未配置在 <resultMap> 節點中的映射關系。它的代碼如下:

private static class UnMappedColumnAutoMapping {
	private final String column;
	private final String property;
	private final TypeHandler<?> typeHandler;
	private final boolean primitive;

	public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
		this.column = column;
		this.property = property;
		this.typeHandler = typeHandler;
		this.primitive = primitive;
	}
}
           

僅用于記錄映射關系。下面看一下擷取 UnMappedColumnAutoMapping 集合的過程,如下:

public class DefaultResultSetHandler implements ResultSetHandler {

	private final Configuration configuration;
	private final MappedStatement mappedStatement;
	
	private final TypeHandlerRegistry typeHandlerRegistry;

	private final Map<String, List<UnMappedColumnAutoMapping>> autoMappingsCache = new HashMap<String, List<UnMappedColumnAutoMapping>>();

	private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
		final String mapKey = resultMap.getId() + ":" + columnPrefix;
		// 從緩存中擷取 UnMappedColumnAutoMapping 清單
		List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
		
		// 緩存未命中
		if (autoMapping == null) {
			autoMapping = new ArrayList<UnMappedColumnAutoMapping>();
			
			// 從 ResultSetWrapper 中擷取未配置在 <resultMap> 中的列名
			final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
			for (String columnName : unmappedColumnNames) {
				String propertyName = columnName;
				if (columnPrefix != null && !columnPrefix.isEmpty()) {
					// When columnPrefix is specified,
					// ignore columns without the prefix.
					if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
						propertyName = columnName.substring(columnPrefix.length());
					} else {
						continue;
					}
				}
				
				// 将下劃線形式的列名轉成駝峰式,比如 AUTHOR_NAME -> authorName
				final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
				if (property != null && metaObject.hasSetter(property)) {
					// 檢測目前屬性是否存在于 resultMap 中
					if (resultMap.getMappedProperties().contains(property)) {
						continue;
					}
					
					// 擷取屬性對于的類型
					final Class<?> propertyType = metaObject.getSetterType(property);
					if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
						final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
						// 封裝上面擷取到的資訊到 UnMappedColumnAutoMapping 對象中
						autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
					} else {
						configuration.getAutoMappingUnknownColumnBehavior()
							.doAction(mappedStatement, columnName, property, propertyType);
					}
				} else {
					configuration.getAutoMappingUnknownColumnBehavior()
					  .doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
				}
			}
			// 寫入緩存
			autoMappingsCache.put(mapKey, autoMapping);
		}
		return autoMapping;
	}
}
           

先來看看從 ResultSetWrapper 中擷取未配置在 <resultMap> 中的列名

即跟進

public class ResultSetWrapper {

	private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<String, List<String>>();

	public List<String> getUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
		List<String> unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
		if (unMappedColumnNames == null) {
			// 加載已映射與未映射列名
			loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
			// 擷取未映射列名
			unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
		}
		return unMappedColumnNames;
	}
	
	private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
		List<String> mappedColumnNames = new ArrayList<String>();
		List<String> unmappedColumnNames = new ArrayList<String>();
		final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
		
		// 擷取 <resultMap> 中配置的所有列名
		final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
		
		/*
		 * 周遊 columnNames,columnNames 是 ResultSetWrapper 的成員變量,儲存了目前結果集中的所有列名
		 * 這裡是通過ResultSet中的所有列名來擷取沒有在resultMap中配置的列名
		 * 意思是後面進行自動指派時,隻指派查出來的列名
		 */
		for (String columnName : columnNames) {
			final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
			// 檢測已映射列名集合中是否包含目前列名
			if (mappedColumns.contains(upperColumnName)) {
				mappedColumnNames.add(upperColumnName);
			} else {
				// 将列名存入 unmappedColumnNames 中
				unmappedColumnNames.add(columnName);
			}
		}
		// 緩存列名集合
		mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
		unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
	}
}
           

首先是從目前資料集中擷取列名集合,然後擷取 <resultMap> 中配置的列名集合。之後周遊資料集中的列名集合,并判斷列名是否被配置在了 <resultMap> 節點中。若配置了,則表明該列名已有映射關系,此時該列名存入 mappedColumnNames 中。若未配置,則表明列名未與實體類的某個字段形成映射關系,此時該列名存入 unmappedColumnNames 中。

映射result節點

foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;

接下來分析一下 MyBatis 是如何将結果集中的資料填充到已配置ResultMap映射的實體類字段中的。

public class DefaultResultSetHandler implements ResultSetHandler {

	private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
	  throws SQLException {
		// 擷取已映射的列名
		final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
		boolean foundValues = false;
		// 擷取 ResultMapping集合
		final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
		// 所有的ResultMapping周遊進行映射
		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;
				// 若擷取到的值為 DEFERED,則延遲加載該值
				} else if (value == DEFERED) {
					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;
	}
	
	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) {
			addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
			return DEFERED;
		} else {
			final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
			final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
			// 從 ResultSet 中擷取指定列的值
			return typeHandler.getResult(rs, column);
		}
	}
}
           

從 ResultMap 擷取映射對象 ResultMapping 集合。然後周遊 ResultMapping 集合,再此過程中調用 getPropertyMappingValue 擷取指定指定列的資料,最後将擷取到的資料設定到實體類對象中。

這裡和自動映射有一點不同,自動映射是從直接從ResultSet 中擷取指定列的值,但是通過ResultMap多了一種情況,那就是關聯查詢,也可以說是延遲查詢,此關聯查詢如果沒有配置延遲加載,那麼就要擷取關聯查詢的值,如果配置了延遲加載,則傳回DEFERED。

關聯查詢與延遲加載

我們的查詢經常會碰到一對一,一對多的情況,通常我們可以用一條 SQL 進行多表查詢完成任務。當然我們也可以使用關聯查詢,将一條 SQL 拆成兩條去完成查詢任務。MyBatis 提供了兩個标簽用于支援一對一和一對多的使用場景,分别是 <association> 和 <collection>。下面我來示範一下如何使用 <association> 完成一對一的關聯查詢。先來看看實體類的定義:

/** 作者類 */
@Data
public class Author {
    private Integer id;
    private String name;
    private Integer age;
    private Integer sex;
    private String email;
}

/** 文章類 */
@Data
public class Article {
    private Integer id;
    private String title;
    private String content;
    private Date createTime;
	
	// 一對一關系
    private Author author;
}
           

接下來看一下 Mapper 接口與映射檔案的定義。

public interface ArticleMapper {
    Article findOne(@Param("id") int id);
    Author findAuthor(@Param("id") int authorId);
}
           
<mapper namespace="com.yibo.mapper.ArticleMapper">
    <resultMap id="articleResult" type="Article">
        <result property="createTime" column="create_time"/>
        //column 屬性值僅包含列資訊,參數類型為 author_id 列對應的類型,這裡為 Integer
        //意思是将author_id做為參數傳給關聯的查詢語句findAuthor
        <association property="author" column="author_id" javaType="Author" select="findAuthor"/>
    </resultMap>

    <select id="findOne" resultMap="articleResult">
        SELECT
            id, author_id, title, content, create_time
        FROM
            article
        WHERE
            id = #{id}
    </select>

    <select id="findAuthor" resultType="Author">
        SELECT
            id, name, age, sex, email
        FROM
            author
        WHERE
            id = #{id}
    </select>
</mapper>
           

開啟延遲加載

<!-- 開啟延遲加載 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 關閉積極的加載政策 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 延遲加載的觸發方法 -->
<setting name="lazyLoadTriggerMethods" value="equals,hashCode"/>
           

此時association節點使用了select指向另外一個查詢語句,并且将 author_id作為參數傳給關聯查詢的語句

此時如果不開啟延遲加載,那麼會生成兩條SQL,先執行findOne,然後通過findOne的傳回結果做為參數,執行findAuthor語句,并将結果設定到author屬性

如果開啟了延遲加載呢?那麼隻會執行findOne一條SQL,當調用article.getAuthor()方法時,才會去執行findAuthor進行查詢,我們下面來看看是如何實作的

我們還是要從上面映射result節點說起

public class DefaultResultSetHandler implements ResultSetHandler {
	
	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) {
			addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
			return DEFERED;
		} else {
			final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
			final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
			// 從 ResultSet 中擷取指定列的值
			return typeHandler.getResult(rs, column);
		}
	}
}
           

我們看到,如果ResultMapping設定了關聯查詢,也就是association或者collection配置了select,那麼就要通過關聯語句來查詢結果,并設定到實體類對象的屬性中了。如果沒配置select,那就簡單,直接從ResultSet中通過列名擷取結果。那我們來看看getNestedQueryMappingValue

public class DefaultResultSetHandler implements ResultSetHandler {
	
	private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
	  throws SQLException {
		// 擷取關聯查詢 id,id = 命名空間 + <association> 的 select 屬性值
		final String nestedQueryId = propertyMapping.getNestedQueryId();
		final String property = propertyMapping.getProperty();
		// 根據 nestedQueryId 擷取關聯的 MappedStatement
		final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
		//擷取關聯查詢MappedStatement的參數類型
		final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
		/*
		 * 生成關聯查詢語句參數對象,參數類型可能是一些包裝類,Map 或是自定義的實體類,
		 * 具體類型取決于配置資訊。以上面的例子為基礎,下面分析不同配置對參數類型的影響:
		 *   1. <association column="author_id"> 
		 *      column 屬性值僅包含列資訊,參數類型為 author_id 列對應的類型,這裡為 Integer
		 * 
		 *   2. <association column="{id=author_id, name=title}"> 
		 *      column 屬性值包含了屬性名與列名的複合資訊,MyBatis 會根據列名從 ResultSet 中
		 *      擷取列資料,并将列資料設定到實體類對象的指定屬性中,比如:
		 *          Author{id=1, name="陳浩"}
		 *      或是以鍵值對 <屬性, 列資料> 的形式,将兩者存入 Map 中。比如:
		 *          {"id": 1, "name": "陳浩"}
		 *
		 *      至于參數類型到底為實體類還是 Map,取決于關聯查詢語句的配置資訊。比如:
		 *          <select id="findAuthor">  ->  參數類型為 Map
		 *          <select id="findAuthor" parameterType="Author"> -> 參數類型為實體類
		 */
		final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
		Object value = null;
		if (nestedQueryParameterObject != null) {
			// 擷取 BoundSql,這裡設定了運作時參數,是以這裡是能直接執行的
			final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
			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 = DEFERED;
			} else {
				// 建立結果加載器
				final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
				// 檢測目前屬性是否需要延遲加載
				if (propertyMapping.isLazy()) {
					// 添加延遲加載相關的對象到 loaderMap 集合中
					lazyLoader.addLoader(property, metaResultObject, resultLoader);
					value = DEFERED;
				} else {
					// 直接執行關聯查詢
					// 如果隻是配置關聯查詢,但是沒有開啟懶加載,則直接執行關聯查詢,并傳回結果,設定到實體類對象的屬性中
					value = resultLoader.loadResult();
				}
			}
		}
		return value;
	}
}
           

下面先來總結一下該方法的邏輯:

  • 1、根據 nestedQueryId 擷取 MappedStatement
  • 2、生成參數對象
  • 3、擷取 BoundSql
  • 4、建立結果加載器 ResultLoader
  • 5、檢測目前屬性是否需要進行延遲加載,若需要,則添加延遲加載相關的對象到loaderMap 集合中
  • 6、如不需要延遲加載,則直接通過結果加載器加載結果

以上流程中針對一級緩存的檢查是十分有必要的,若緩存命中,可直接取用結果,無需再在執行關聯查詢 SQL。若緩存未命中,接下來就要按部就班執行延遲加載相關邏輯

我們來看一下添加延遲加載相關對象到 loaderMap 集合中的邏輯,如下:

public class ResultLoaderMap {

	private final Map<String, LoadPair> loaderMap = new HashMap<String, LoadPair>();

	public void addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader) {
		// 将屬性名轉為大寫
		String upperFirst = getUppercaseFirstProperty(property);
		if (!upperFirst.equalsIgnoreCase(property) && loaderMap.containsKey(upperFirst)) {
		  throw new ExecutorException("Nested lazy loaded result property '" + property +
				  "' for query id '" + resultLoader.mappedStatement.getId() +
				  " already exists in the result map. The leftmost property of all lazy loaded properties must be unique within a result map.");
		}
		// 建立 LoadPair,并将 <大寫屬性名,LoadPair對象> 鍵值對添加到 loaderMap 中
		loaderMap.put(upperFirst, new LoadPair(property, metaResultObject, resultLoader));
	}
}
           

我們再來回顧一下文章開始的建立實體類

public class DefaultResultSetHandler implements ResultSetHandler {

	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<Class<?>>();
		final List<Object> constructorArgs = new ArrayList<Object>();
		
		// 調用重載方法建立實體類對象
		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
				// 如果開啟了延遲加載,則為 resultObject 生成代理類,如果僅僅是配置的關聯查詢,沒有開啟延遲加載,是不會建立代理類
				if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
					/*
					 * 建立代理類,預設使用 Javassist 架構生成代理類。
					 * 由于實體類通常不會實作接口,是以不能使用 JDK 動态代理 API 為實體類生成代理。
					 * 并且将lazyLoader傳進去了
					 */
					resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
					break;
				}
			}
		}
		this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
		return resultObject;
	}
}
           

如果開啟了延遲加載,并且有關聯查詢,此時是要建立一個代理對象的,将上面存放BondSql的lazyLoader和建立的目标對象resultObject 作為參數傳進去。

Mybatis提供了兩個實作類CglibProxyFactory和JavassistProxyFactory,分别基于org.javassist:javassist和cglib:cglib進行實作。createProxy方法就是實作懶加載邏輯的核心方法,也是我們分析的目标。

CglibProxyFactory

CglibProxyFactory基于cglib動态代理模式,通過繼承父類的方式生成動态代理類。

public class CglibProxyFactory implements ProxyFactory {

	@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);
	}
	
	public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
		final Class<?> type = target.getClass();
		EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
		//由CglibProxyFactory生成對象
		Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
		//複制屬性
		PropertyCopier.copyBeanProperties(type, target, enhanced);
		return enhanced;
	}
	
	static Object crateProxy(Class<?> type, Callback callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
		Enhancer enhancer = new Enhancer();
		enhancer.setCallback(callback);
		//設定父類對象
		enhancer.setSuperclass(type);
		try {
			type.getDeclaredMethod(WRITE_REPLACE_METHOD);
			// ObjectOutputStream will call writeReplace of objects returned by writeReplace
			if (log.isDebugEnabled()) {
				log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this");
			}
		} catch (NoSuchMethodException e) {
			enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class});
		} catch (SecurityException e) {
			// nothing to do here
		}
		Object enhanced;
		if (constructorArgTypes.isEmpty()) {
			enhanced = enhancer.create();
		} else {
			Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
			Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
			enhanced = enhancer.create(typesArray, valuesArray);
		}
		return enhanced;
	}
}
           

可以看到,初始化Enhancer,并調用構造方法,生成對象。從enhancer.setSuperclass(type);也能看出cglib采用的是繼承父類的方式。

EnhancedResultObjectProxyImpl

EnhancedResultObjectProxyImpl實作了MethodInterceptor接口,此接口是Cglib攔截目标對象方法的入口,對目标對象方法的調用都會通過此接口的intercept的方法。

public class CglibProxyFactory implements ProxyFactory {

	private static class EnhancedResultObjectProxyImpl implements MethodInterceptor {
	
		private final Class<?> type;
		private final ResultLoaderMap lazyLoader;
		private final boolean aggressive;
		private final Set<String> lazyLoadTriggerMethods;
		private final ObjectFactory objectFactory;
		private final List<Class<?>> constructorArgTypes;
		private final List<Object> constructorArgs;

		@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)) {
						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) {
							return new CglibSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
						} else {
							return original;
						}
					} else {
						if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
							/*
							 * 如果 aggressive 為 true,或觸發方法(比如 equals,hashCode 等)被調用,
							 * 則加載所有的所有延遲加載的資料
							 */
							if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
								lazyLoader.loadAll();
							} else if (PropertyNamer.isSetter(methodName)) {
								// 如果使用者顯示調用了 setter 方法,則将相應的延遲加載類從 loaderMap 中移除
								final String property = PropertyNamer.methodToProperty(methodName);
								lazyLoader.remove(property);
							 // 檢測使用者是否調用 getter 方法
							} else if (PropertyNamer.isGetter(methodName)) {
								final String property = PropertyNamer.methodToProperty(methodName);
								if (lazyLoader.hasLoader(property)) {
									// 執行延遲加載邏輯
									lazyLoader.load(property);
								}
							}
						}
					}
				}
				//執行原方法(即父類方法)
				return methodProxy.invokeSuper(enhanced, args);
			} catch (Throwable t) {
				throw ExceptionUtil.unwrapThrowable(t);
			}
		}
	}
}
           

完整的代碼

// cglib代理工廠類,實作延遲加載屬性
public class CglibProxyFactory implements ProxyFactory {

  private static final Log log = LogFactory.getLog(CglibProxyFactory.class);
  //finalize方法
  private static final String FINALIZE_METHOD = "finalize";
  //writeReplace方法
  private static final String WRITE_REPLACE_METHOD = "writeReplace";

  //加載Enhancer,這個是Cglib的入口
  public CglibProxyFactory() {
    try {
      Resources.classForName("net.sf.cglib.proxy.Enhancer");
    } catch (Throwable e) {
      throw new IllegalStateException("Cannot enable lazy loading because CGLIB is not available. Add CGLIB to your classpath.", e);
    }
  }

  /**
   * 建立代理對象
   * @param target 目标對象
   * @param lazyLoader 延遲加載器
   * @param configuration 配置類
   * @param objectFactory 對象工廠
   * @param constructorArgTypes 構造函數類型[]
   * @param constructorArgs  構造函數的值[]
   * @return
   */
  @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);
  }

  /**
   * 建立一個反序列化代理
   * @param target 目标
   * @param unloadedProperties
   * @param objectFactory 對象工廠
   * @param constructorArgTypes 構造函數類型數組
   * @param constructorArgs 構造函數值
   * @return
   */
  public Object createDeserializationProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    return EnhancedDeserializationProxyImpl.createProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
  }

  @Override
  public void setProperties(Properties properties) {
      // Not Implemented
  }

  /**
   * 傳回代理對象, 這個代理對象在調用任何方法都會調用本類的intercept方法
   * Enhancer 認為這個就是自定義類的工廠,比如這個類需要實作什麼接口
   * @param type 目标類型
   * @param callback 結果對象代理實作類,當中有invoke回調方法
   * @param constructorArgTypes 構造函數類型數組
   * @param constructorArgs 構造函數對應字段的值數組
   * @return
   */
  static Object crateProxy(Class<?> type, Callback callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    // enhancer 配置調節代理對象的一些參數
    // 設定回調方法
    // 設定超類
    //判斷當傳入目标類型是否有writeReplace方法,沒有則配置一個有writeReplace方法的接口(序列化寫出)
	Enhancer enhancer = new Enhancer();
    enhancer.setCallback(callback);
    enhancer.setSuperclass(type);
    try {
      type.getDeclaredMethod(WRITE_REPLACE_METHOD);
      // ObjectOutputStream will call writeReplace of objects returned by writeReplace
      if (log.isDebugEnabled()) {
        log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this");
      }
    } catch (NoSuchMethodException e) {
	  //這個enhancer增加一個WriteReplaceInterface接口
      enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class});
    } catch (SecurityException e) {
      // nothing to do here
    }
	
    //根據構造函數建立一個對象
    //無參構造
    //有參構造	
    Object enhanced;
    if (constructorArgTypes.isEmpty()) {
      enhanced = enhancer.create();
    } else {
      Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
      Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
      enhanced = enhancer.create(typesArray, valuesArray);
    }
    return enhanced;
  }

  /**
   * 結果對象代理實作類,
   * 它實作方法攔截器的intercept方法
   */
  private static class EnhancedResultObjectProxyImpl implements MethodInterceptor {

    private final Class<?> type;
    private final ResultLoaderMap lazyLoader;
    private final boolean aggressive;
    private final Set<String> lazyLoadTriggerMethods;
    private final ObjectFactory objectFactory;
    private final List<Class<?>> constructorArgTypes;
    private final List<Object> constructorArgs;

    /**
     * 代理對象建立
     * @param type 目标class類型
     * @param lazyLoader 延遲加載器
     * @param configuration 配置資訊
     * @param objectFactory 對象工廠
     * @param constructorArgTypes 構造函數類型數組
     * @param constructorArgs 構造函數值數組
     */
    private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      this.type = type;
      this.lazyLoader = lazyLoader;
      this.aggressive = configuration.isAggressiveLazyLoading();
      this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
      this.objectFactory = objectFactory;
      this.constructorArgTypes = constructorArgTypes;
      this.constructorArgs = constructorArgs;
    }

    /**
     * 建立代理對象, 将源對象值指派給代理對象
     * @param target 目标對象
     * @param lazyLoader 延遲加載器
     * @param configuration 配置對象
     * @param objectFactory 對象工廠
     * @param constructorArgTypes 構造函數類型數組
     * @param constructorArgs 構造函數值數組
     * @return
     */
    public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      //擷取目标的類型
      //建立一個結果對象代理實作類(它實作cglib的MethodInterface接口,完成回調作用invoke方法)      
	  final Class<?> type = target.getClass();
      EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
      Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
      PropertyCopier.copyBeanProperties(type, target, enhanced);
      return enhanced;
    }

    /**
     * 回調方法
     * @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 {
        // 同步擷取延遲加載對象
        // 如果是執行writeReplace方法(序列化寫出)
        // 執行個體化一個目标對象的執行個體	  
        synchronized (lazyLoader) {
          if (WRITE_REPLACE_METHOD.equals(methodName)) {
            Object original;
            if (constructorArgTypes.isEmpty()) {
              original = objectFactory.create(type);
            } else {
              original = objectFactory.create(type, constructorArgTypes, constructorArgs);
            }
            // 将enhanced中的屬性複制到orignal對象中
            // 如果延遲加載數量>0,			
            PropertyCopier.copyBeanProperties(type, enhanced, original);
            if (lazyLoader.size() > 0) {
              return new CglibSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
            } else {
              return original;
            }
          } else {
            //不是writeReplace方法
            // 延遲加載長度大于0, 且不是finalize方法
            // configuration配置延遲加載參數,延遲加載觸發的方法包含這個方法
            // 延遲加載所有資料		  
            if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
              if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                lazyLoader.loadAll();
			  // setter方法,直接移除	
              } else if (PropertyNamer.isSetter(methodName)) {
                final String property = PropertyNamer.methodToProperty(methodName);
                lazyLoader.remove(property);
				
			  // getter方法, 加載該屬性	
              } else if (PropertyNamer.isGetter(methodName)) {
                final String property = PropertyNamer.methodToProperty(methodName);
                if (lazyLoader.hasLoader(property)) {
                  lazyLoader.load(property);
                }
              }
            }
          }
        }
        return methodProxy.invokeSuper(enhanced, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }

  /**
   * 他繼承抽象反序列化代理和實作了方法攔截
   */
  private static class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy implements MethodInterceptor {

    private EnhancedDeserializationProxyImpl(Class<?> type, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
    }

    /**
     * 建立代理對象
     * @param target
     * @param unloadedProperties
     * @param objectFactory
     * @param constructorArgTypes
     * @param constructorArgs
     * @return
     */
    public static Object createProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      final Class<?> type = target.getClass();
      EnhancedDeserializationProxyImpl callback = new EnhancedDeserializationProxyImpl(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
      Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
      PropertyCopier.copyBeanProperties(type, target, enhanced);
      return enhanced;
    }

    @Override
    public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
      final Object o = super.invoke(enhanced, method, args);
      return o instanceof AbstractSerialStateHolder ? o : methodProxy.invokeSuper(o, args);
    }

    @Override
    protected AbstractSerialStateHolder newSerialStateHolder(Object userBean, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      return new CglibSerialStateHolder(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
    }
  }
}
           

如上,代理方法首先會檢查 aggressive 是否為 true,如果不滿足,再去檢查 lazyLoadTriggerMethods 是否包含目前方法名。這裡兩個條件隻要一個為 true,目前實體類中所有需要延遲加載。aggressive 和 lazyLoadTriggerMethods 兩個變量的值取決于下面的配置。

<setting name="aggressiveLazyLoading" value="false"/>
<setting name="lazyLoadTriggerMethods" value="equals,hashCode"/>
           

然後代理邏輯會檢查使用者是不是調用了實體類的 setter 方法,如果調用了,就将該屬性對應的 LoadPair 從 loaderMap 中移除。為什麼要這麼做呢?答案是:使用者既然手動調用 setter 方法,說明使用者想自定義某個屬性的值。此時,延遲加載邏輯不應該再修改該屬性的值,是以這裡從 loaderMap 中移除屬性對于的 LoadPair。

最後如果使用者調用的是某個屬性的 getter 方法,且該屬性配置了延遲加載,此時延遲加載邏輯就會被觸發。那接下來,我們來看看延遲加載邏輯是怎樣實作的的。

public class ResultLoaderMap {

	private final Map<String, LoadPair> loaderMap = new HashMap<String, LoadPair>();
  
	public boolean load(String property) throws SQLException {
		// 從 loaderMap 中移除 property 所對應的 LoadPair
		LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
		if (pair != null) {
			// 加載結果
			pair.load();
			return true;
		}
		return false;
	}
	
    public void load() throws SQLException {
      /* These field should not be null unless the loadpair was serialized.
       * Yet in that case this method should not be called. */
      if (this.metaResultObject == null) {
        throw new IllegalArgumentException("metaResultObject is null");
      }
      if (this.resultLoader == null) {
        throw new IllegalArgumentException("resultLoader is null");
      }

      this.load(null);
    }
	
    public void load(final Object userObject) throws SQLException {
      if (this.metaResultObject == null || this.resultLoader == null) {
        if (this.mappedParameter == null) {
          throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
                  + "required parameter of mapped statement ["
                  + this.mappedStatement + "] is not serializable.");
        }

        final Configuration config = this.getConfiguration();
        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 + "]");
        }

        this.metaResultObject = config.newMetaObject(userObject);
        this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
                metaResultObject.getSetterType(this.property), null, null);
      }

      /* We are using a new executor because we may be (and likely are) on a new thread
       * and executors aren't thread safe. (Is this sufficient?)
       *
       * A better approach would be making executors thread safe. */
      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 的 loadResult 方法加載結果,
     * 并通過 metaResultObject 設定結果到實體類對象中
     */
      this.metaResultObject.setValue(property, this.resultLoader.loadResult());
    }
}

public class ResultLoader {

	protected final ResultExtractor resultExtractor;

	protected Object resultObject;

	public Object loadResult() throws SQLException {
		// 執行關聯查詢
		List<Object> list = selectList();
		// 抽取結果
		resultObject = resultExtractor.extractObjectFromList(list, targetType);
		return resultObject;
	}
	
	private <E> List<E> selectList() throws SQLException {
		Executor localExecutor = executor;
		if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
			localExecutor = newExecutor();
		}
		try {
		    // 通過 Executor 就行查詢,這個之前已經分析過了
			// 這裡的parameterObject和boundSql就是我們之前存放在LoadPair中的,現在直接拿來執行了
			return localExecutor.<E> query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
		} finally {
			if (localExecutor != executor) {
				localExecutor.close(false);
			}
		}
	}
}
           

好了,延遲加載我們基本已經講清楚了,我們介紹一下另外的一種代理方式

JavassistProxyFactory

/**JavassistProxy位元組碼生成代理
 * 1.建立一個代理對象然後将目标對象的值指派給代理對象,這個代理對象是可以實作其他的接口
 * 2. JavassistProxyFactory實作ProxyFactory接口createProxy(建立代理對象的方法)
 * @author Eduardo Macarron
 */
public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory {

  private static final Log log = LogFactory.getLog(JavassistProxyFactory.class);
  
  /**
   * finalize方法(垃圾回收)
   */
  private static final String FINALIZE_METHOD = "finalize";
  /**
   * writeReplace(序列化寫出方法)
   */  
  private static final String WRITE_REPLACE_METHOD = "writeReplace";

  /**
   * 加載ProxyFactory, 也就是JavassistProxy的入口
   */
  public JavassistProxyFactory() {
    try {
      Resources.classForName("javassist.util.proxy.ProxyFactory");
    } catch (Throwable e) {
      throw new IllegalStateException("Cannot enable lazy loading because Javassist is not available. Add Javassist to your classpath.", e);
    }
  }

  /**
   * 建立代理
   * @param target 目标對象
   * @param lazyLoader 延遲加載Map集合(那些屬性是需要延遲加載的)
   * @param configuration 配置類
   * @param objectFactory 對象工廠
   * @param constructorArgTypes 構造函數類型[]
   * @param constructorArgs  構造函數的值[]
   * @return
   */
  @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);
  }

  public Object createDeserializationProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    return EnhancedDeserializationProxyImpl.createProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
  }

  @Override
  public void setProperties(Properties properties) {
      // Not Implemented
  }

  /**
   * 擷取代理對象, 也就是說在執行方法之前首先調用MethodHanlder的invoke方法
   * @param type 目标類型
   * @param callback 回調對象
   * @param constructorArgTypes 構造函數類型數組
   * @param constructorArgs 構造函數值的數組
   * @return
   */
  static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    // 建立一個代理工廠類
    // 配置超類
    ProxyFactory enhancer = new ProxyFactory();
    enhancer.setSuperclass(type);
    //判斷是否有writeReplace方法,如果沒有将這個代理對象實作WriteReplaceInterface接口,這個接口隻有一個writeReplace方法
    try {
      type.getDeclaredMethod(WRITE_REPLACE_METHOD);
      // ObjectOutputStream will call writeReplace of objects returned by writeReplace
      if (log.isDebugEnabled()) {
        log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this");
      }
    } 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;
  }

  /**
   * 實作Javassist的MethodHandler接口, 相對于Cglib的MethodInterceptor
   * 他們接口的方法名也是不一樣的,Javassist的是invoke, 而cglib是intercept,叫法不同,實作功能是一樣的
   */
  private static class EnhancedResultObjectProxyImpl implements MethodHandler {
    /**
     * 目标類型
     */
    private final Class<?> type;
    /**
     * 延遲加載Map集合
     */	
    private final ResultLoaderMap lazyLoader;
    /**
     * 是否配置延遲加載
     */	
    private final boolean aggressive;
    /**
     * 延遲加載觸發的方法
     */	
    private final Set<String> lazyLoadTriggerMethods;
    /**
     * 對象工廠
     */	
    private final ObjectFactory objectFactory;
    /**
     * 構造函數類型數組
     */	
    private final List<Class<?>> constructorArgTypes;
    /**
     * 構造函數類型的值數組
     */	
    private final List<Object> constructorArgs;

    /**
     * 構造函數私有化了
     * @param type
     * @param lazyLoader
     * @param configuration
     * @param objectFactory
     * @param constructorArgTypes
     * @param constructorArgs
     */
    private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      this.type = type;
      this.lazyLoader = lazyLoader;
      this.aggressive = configuration.isAggressiveLazyLoading();
      this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
      this.objectFactory = objectFactory;
      this.constructorArgTypes = constructorArgTypes;
      this.constructorArgs = constructorArgs;
    }

    public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      // 擷取目标類型
      // 建立一個EnhancedResultObjectProxyImpl對象,回調對象
	  final Class<?> type = target.getClass();
      EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
      Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
      PropertyCopier.copyBeanProperties(type, target, enhanced);
      return enhanced;
    }

    /**
     * 回調方法
     * @param enhanced 代理對象
     * @param method 方法
     * @param methodProxy 代理方法
     * @param args 入參
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) 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) {
              return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
            } else {
              return original;
            }
          } else {
            //不是writeReplace方法
            // 延遲加載長度大于0, 且不是finalize方法
            // configuration配置延遲加載參數,延遲加載觸發的方法包含這個方法
            // 延遲加載所有資料		  
            if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
              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);
                }
              }
            }
          }
        }
        return methodProxy.invoke(enhanced, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }

  private static class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy implements MethodHandler {

    private EnhancedDeserializationProxyImpl(Class<?> type, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
    }

    public static Object createProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      final Class<?> type = target.getClass();
      EnhancedDeserializationProxyImpl callback = new EnhancedDeserializationProxyImpl(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
      Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
      PropertyCopier.copyBeanProperties(type, target, enhanced);
      return enhanced;
    }

    @Override
    public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
      final Object o = super.invoke(enhanced, method, args);
      return o instanceof AbstractSerialStateHolder ? o : methodProxy.invoke(o, args);
    }

    @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);
    }
  }
}