Mybatis源码-级联映射和懒加载
目录
MyBatis其中一个比较强大的功能是支持查询结果级联映射。使用MyBatis级联映射,我们可以很轻松地实现一对多、一对一或者多对多关联查询,甚至可以利用MyBatis提供的级联映射实现懒加载。
所谓的懒加载,就是当我们在一个实体对象中关联其他实体对象时,如果不需要获取被关联的实体对象,则不需要为被关联的实体执行额外的查询操作,仅当调用当前实体的Getter方法获取被关联实体对象时,才会执行一次额外的查询操作。 通过这种方式在一定程度上能够减轻数据库的压力。
级联映射 #
一对多 #
<resultMap id="detailMap" type="com.blog4java.mybatis.example.entity.User">
<collection property="orders" ofType="com.blog4java.mybatis.example.entity.Order"
select="com.blog4java.mybatis.example.mapper.OrderMapper.listOrdersByUserId"
javaType="java.util.ArrayList"
column="id">
</collection>
</resultMap>
一对一 #
<resultMap id="detailNestMap" type="com.blog4java.mybatis.example.entity.Order">
<id column="id" property="id"></id>
<result column="createTime" property="createTime"></result>
<result column="userId" property="userId"></result>
<result column="amount" property="amount"></result>
<result column="orderNo" property="orderNo"></result>
<result column="address" property="address"></result>
<association property="user" javaType="com.blog4java.mybatis.example.entity.User" >
<id column="userId" property="id"></id>
<result column="name" property="name"></result>
<result column="createTime" property="createTime"></result>
<result column="password" property="password"></result>
<result column="phone" property="phone"></result>
<result column="nickName" property="nickName"></result>
</association>
</resultMap>
鉴别器 #
<resultMap id="detailMapForDiscriminator" type="com.blog4java.mybatis.example.entity.User">
<discriminator javaType="String" column="gender">
<!--当查询用户信息时,如果用户性别为女性,则获取用户对应的订单信息,如果用户性别为男性,则不获取订单信息。-->
<case value="female" resultType="com.blog4java.mybatis.example.entity.User">
<collection property="orders" ofType="com.blog4java.mybatis.example.entity.Order"
select="com.blog4java.mybatis.example.mapper.OrderMapper.listOrdersByUserId"
javaType="java.util.ArrayList"
column="id">
</collection>
</case>
</discriminator>
</resultMap>
懒加载机制 #
MyBatis的级联映射可以通过两种方式实现:
- 为实体的属性关联一个外部的查询Mapper,这种情况下,MyBatis实际上为实体的属性执行一次额外的查询操作;
- 通过JOIN查询来实现,这种方式需要为实体关联的其他实体对应的属性配置映射关系,通过JOIN查询方式只需要一次查询即可。
在一些情况下,我们需要按需加载,即当我们查询用户信息时,如果不需要获取用户订单信息,则不需要执时订单查询对应的Mapper,仅当调用Getter方法获取订单数据时,才执行一次额外的查询操作。 这种方式能够在一定程度上能够减少数据库IO次数,提升系统性能。懒加载机制的作用。
<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 将积极加载改为懒加载即按需加载 -->
<setting name="aggressiveLazyLoading" value="false" />
<!-- toString,hashCode等方法不触发懒加载 -->
<setting name="lazyLoadTriggerMethods" value=""/>
</settings>
public void testLazyQuery() {
Order order = orderMapper.getOrderByNo("order_2314234");
System.out.println("完成Order数据查询");
// 调用getUser()方法时执行懒加载
order.getUser();
}
级联映射实现原理 #
生成ResultMap类 #
MyBatis通过ResultMap类描述<resultMap>标签的配置信息,ResultMap类的所有属性如下:
public class ResultMap {
private Configuration configuration;
// <resultMap>标签id属性
private String id;
// <resultMap>标签type属性
private Class<?> type;
// <result>标签配置的映射信息
private List<ResultMapping> resultMappings;
// <id>标签配置的主键映射信息
private List<ResultMapping> idResultMappings;
// <constructor>标签配置的构造器映射信息
private List<ResultMapping> constructorResultMappings;
// <result>标签配置的结果集映射信息
private List<ResultMapping> propertyResultMappings;
// 存放所有映射的数据库字段信息,当使用columnPrefix配置字段前缀时,所有字段都会追加前缀
private Set<String> mappedColumns;
// 存放所有映射的属性信息
private Set<String> mappedProperties;
// <discriminator>标签配置的鉴别器信息
private Discriminator discriminator;
// 是否有嵌套的<resultMap>
private boolean hasNestedResultMaps;
// 是否有存在嵌套查询
private boolean hasNestedQueries;
// autoMapping属性值,是否自动映射
private Boolean autoMapping;
// ...
}
MyBatis中的Mapper配置信息解析都是通过XMLMapperBuilder类完成的,该类提供了一个parse()方法,用于解析Mapper中的所有配置信息:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 调用XPathParser的evalNode()方法获取根节点对应的XNode对象
configurationElement(parser.evalNode("/mapper"));
// 將资源路径添加到Configuration对象中
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
// 继续解析之前解析出现异常的ResultMap对象
parsePendingResultMaps();
// 继续解析之前解析出现异常的CacheRef对象
parsePendingCacheRefs();
// 继续解析之前解析出现异常<select|update|delete|insert>标签配置
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
// 获取命名空间
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置当前正在解析的Mapper配置的命名空间
builderAssistant.setCurrentNamespace(namespace);
// 解析<cache-ref>标签
cacheRefElement(context.evalNode("cache-ref"));
// 解析<cache>标签
cacheElement(context.evalNode("cache"));
// 解析所有的<parameterMap>标签
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析所有的<resultMap>标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析所有的<sql>标签
sqlElement(context.evalNodes("/mapper/sql"));
// 解析所有的<select|insert|update|delete>标签
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// 属性优先级按照type->ofType->resultType->javaType顺序,type属性为空或者无type属性,则使用ofType属性,依此类推
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 是否继承ResultMap
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
// 参数映射列表
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
// 解析<constructor>标签
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
// 解析<discriminator>标签
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
// 通过ResultMapResolver对象构建ResultMap对象
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
public ResultMap resolve() {
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
如上面的代码所示,在XMLMapperBuilder 类的resultMapElement()方法中,首先获取<resultMap>标签的所有属性信息,然后对<id>、<constructor>、<discriminator>子标签进行解析,接着创建一个ResultMapResolver对象,调用ResultMapResolver 对象的resolve()方法返回一个ResultMap对象。
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
id = applyCurrentNamespace(id, false);
extend = applyCurrentNamespace(extend, true);
if (extend != null) {
// 如果继承了其他ResultMap
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
// 获取继承的父ResultMap对象
ResultMap resultMap = configuration.getResultMap(extend);
List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
extendedResultMappings.removeAll(resultMappings);
// 如果父ResultMap定义了构造器映射,则移除构造器映射.
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
extendedResultMappingsIter.remove();
}
}
}
// 將父ResultMap配置的映射信息添加到当前ResultMap
resultMappings.addAll(extendedResultMappings);
}
// 通过建造者模式创建ResultMap对象
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
configuration.addResultMap(resultMap);
return resultMap;
}
处理结果集 #
在DefaultResultSetHandler 类的handleResultSets()方法中,为了简化对JDBC 中 ResultSet 对象的操作,将ResultSet 对象包装成ResultSetWrapper 对象,然后获取MappedStatement对象对应的ResultMap对象,接着调用重载的handleResultSet()方法进行处理。该方法代码实现如下:
// org.apache.ibatis.executor.statement.PreparedStatementHandler#query
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 调用PreparedStatement对象的execute()方法,执行SQL语句
ps.execute();
// 调用ResultSetHandler的handleResultSets()方法处理结果集
return resultSetHandler.<E> handleResultSets(ps);
}
// org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
@Override
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;
// 1、获取ResultSet对象,將ResultSet对象包装为ResultSetWrapper
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 2、获取ResultMap信息,一般只有一个ResultMap
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
// 校验ResultMap,如果该ResultMap名称没有配置,则抛出异常
validateResultMapsCount(rsw, resultMapCount);
// 如果指定了多个ResultMap,则对每个ResultMap进行处理
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 3、调用handleResultSet方法处理结果集
handleResultSet(rsw, resultMap, multipleResults, null);
// 获取下一个结果集对象,需要JDBC驱动支持多结果集
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 如果JDBC驱动支持多结果集,可以通过<select>标签resultSets属性指定多个ResultMap
// 处理<select>标签resultSets属性,该属性一般情况不会指定
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
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方法处理结果集
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
// 对multipleResults进行处理,如果只有一个结果集,则返回结果集中的元素,否则返回多个结果集
return collapseSingleResultList(multipleResults);
}
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
// 仅当指定了<select>标签resultSets属性,parentMapping值才不为null
if (parentMapping != null) {
// 调用handleRowValues()方法处理
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
// 如果未指定ResultHandler,则创建默认的ResultHandler实现
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
// 调用handleRowValues()方法处理
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
// 获取处理后的结果
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
closeResultSet(rsw.getResultSet());
}
}
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 是否有嵌套ResultMap
if (resultMap.hasNestedResultMaps()) {
// 嵌套查询校验RowBounds,可以通过设置safeRowBoundsEnabled=false参数绕过校验
ensureNoRowBounds();
// 校验ResultHandler,可以设置safeResultHandlerEnabled=false参数绕过校验
checkResultHandler();
// 如果有嵌套的ResultMap,调用handleRowValuesForNestedResultMap处理嵌套ResultMap
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 如果无嵌套的ResultMap,调用handleRowValuesForSimpleResultMap处理简单非嵌套ResultMap
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
如上面的代码所示,在DefaultResultSetHandler类的handleRowValues()方法中判断ResultMap中是否有嵌套的ResultMap,当使用<association>或<collection>标签通过JOIN查询方式进行级联映射时,hasNestedResultMaps()方法的返回值为true。如果有嵌套的ResultMap,则调用handleRowValuesForNestedResultMap()方法进行处理,否则调用handleRowValuesForSimpleResultMap()方法。
我们先来看一下有嵌套ResultMap时的处理逻辑。handleRowValuesForNestedResultMap()方法的代码如下:
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
final DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
// 调用skipRows()方法將ResultSet对象定位到rowBounds对象指定的偏移量
skipRows(rsw.getResultSet(), rowBounds);
// previousRowValue为上一个结果对象
Object rowValue = previousRowValue;
// 遍历处理每一行记录
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
// 处理<discriminator>标签配置的鉴别器
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
// 获取缓存的嵌套实体
Object partialObject = nestedResultObjects.get(rowKey);
if (mappedStatement.isResultOrdered()) {
// 缓存的嵌套实体对象不为空
if (partialObject == null && rowValue != null) {
nestedResultObjects.clear();
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
// 调用getRowValue
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
} else {
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
if (partialObject == null) {
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
}
if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
previousRowValue = null;
} else if (rowValue != null) {
previousRowValue = rowValue;
}
}
上面的代码中,对结果集对象进行遍历,处理每一行数据。首先调用resolveDiscriminatedResultMap()方法处理<resultMap>标签中通过<discriminator>标签配置的鉴别器信息,根据字段值获取对应的ResultMap对象,然后调用DefaultResultSetHandler类的getRowValue()方法将结果集中的一行数据转换为Java实体对象。下面是getRowValue()方法的代码:
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
final String resultMapId = resultMap.getId();
// 如果缓存了嵌套ResultMap对应的实体对象,则调用applyNestedResultMappings()方法处理
Object rowValue = partialObject;
if (rowValue != null) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
putAncestor(rowValue, resultMapId);
// 调用applyNestedResultMappings()方法处理嵌套的映射
applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
ancestorObjects.remove(resultMapId);
} else {
// ResultLoaderMap用于存放懒加载ResultMap信息
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 处理通过<constructor>标签配置的构造器映射
rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
// 判断结果对象是否注册对应的TypeHandler
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
// 是否使用构造器映射
boolean foundValues = this.useConstructorMappings;
// 是否指定了自动映射
if (shouldApplyAutomaticMappings(resultMap, true)) {
// 调用applyAutomaticMappings()方法处理自动映射
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 处理非<id>,<constructor>指定的映射
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
putAncestor(rowValue, resultMapId);
// 处理嵌套的映射
foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
ancestorObjects.remove(resultMapId);
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
if (combinedKey != CacheKey.NULL_CACHE_KEY) {
nestedResultObjects.put(combinedKey, rowValue);
}
}
return rowValue;
}
在getRowValue()方法中,主要做了以下几件事情:
调用createResultObject()方法处理通过<constructor>标签配置的构造器映射,根据配置信息找到对应的构造方法,然后通过MyBatis中的ObjectFactory创建ResultMap关联的实体对象。
调用applyAutomaticMappings()方法处理自动映射,对未通过<result>标签配置映射的数据库字段进行与Java实体属性的映射处理。该方法的具体实现代码如下:
首先获取未指定映射的所有数据库字段和对应的Java属性,然后获取对应的字段值,通过反射机制为Java实体对应的属性值赋值。
private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) { boolean foundValues = false; for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) { final String nestedResultMapId = resultMapping.getNestedResultMapId(); if (nestedResultMapId != null && resultMapping.getResultSet() == null) { try { final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping); // 对所有ResultMap映射信息进行遍历,获取嵌套的ResultMap,然后为嵌套ResultMap对应的实体属性设置值 final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix); if (resultMapping.getColumnPrefix() == null) { // 当未指定columnPrefix属性时,从缓存中获取嵌套的ResultMap对应的Java实体对象,避免循环引用问题 Object ancestorObject = ancestorObjects.get(nestedResultMapId); if (ancestorObject != null) { if (newObject) { // 调用linkObjects方法將將外层实体对象和嵌套对象进行关联 linkObjects(metaObject, resultMapping, ancestorObject); } continue; } } final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix); final CacheKey combinedKey = combineKeys(rowKey, parentRowKey); Object rowValue = nestedResultObjects.get(combinedKey); boolean knownValue = rowValue != null; instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) { // 调用getRowValue()方法,根据嵌套结果集映射信息创建Java实体 rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue); if (rowValue != null && !knownValue) { linkObjects(metaObject, resultMapping, rowValue); foundValues = true; } } } catch (SQLException e) { throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e); } } } return foundValues; }
用applyPropertyMappings()方法处理<result>标签配置的映射信息。该方法处理逻辑相对简单,对所有<result>标签配置的映射信息进行遍历,然后找到数据库字段对应的值,为Java实体属性赋值。applyPropertyMappings()方法的实现代码如下:
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { // 获取通过<result>标签指定映射的字段名称 final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix); // foundValues变量用于标识是否获取到数据库字段对应的值 boolean foundValues = false; final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); // 对所有通过<result>标签配置了映射的字段进行赋值 for (ResultMapping propertyMapping : propertyMappings) { // 获取数据库字段名称 String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); if (propertyMapping.getNestedResultMapId() != null) { 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); // 获取Java实体对应的属性名称 final String property = propertyMapping.getProperty(); if (property == null) { continue; } else if (value == DEFERED) { foundValues = true; continue; } if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) { // 调用MetaObject对象的setValue()方法为返回的实体对象设置属性值 metaObject.setValue(property, value); } } } return foundValues; }
如上面的代码所示,在applyNestedResultMappings()方法中,首先获取嵌套ResultMap对象,然后根据嵌套ResultMap的Id从缓存中获取嵌套ResultMap对应的Java实体对象,如果能获取到,则调用 linkObjects()方法将嵌套Java 实体与外部Java 实体进行关联。如果缓存中没有,则调用getRowValue()方法创建嵌套ResultMap对应的Java实体对象并进行属性映射,然后调用linkObjects()方法与外部的Java实体对象进行关联。
懒加载实现原理 #
MyBatis支持通过<collection>或者<association>标签关联一个外部的查询Mapper,当通过MyBatis配置开启懒加载机制时,执行查询操作不会触发关联的查询Mapper,而通过Getter方法访问实体属性时才会执行一次关联的查询Mapper,然后为实体属性赋值。
在DefaultResultSetHandler类的handleRowValues()方法中处理结果集时,对嵌套的ResultMap和非嵌套ResultMap做了不同处理,代码如下:
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 是否有嵌套ResultMap
if (resultMap.hasNestedResultMaps()) {
// 嵌套查询校验RowBounds,可以通过设置safeRowBoundsEnabled=false参数绕过校验
ensureNoRowBounds();
// 校验ResultHandler,可以设置safeResultHandlerEnabled=false参数绕过校验
checkResultHandler();
// 如果有嵌套的ResultMap,调用handleRowValuesForNestedResultMap处理嵌套ResultMap
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 如果无嵌套的ResultMap,调用handleRowValuesForSimpleResultMap处理简单非嵌套ResultMap
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
skipRows(rsw.getResultSet(), rowBounds);
// 遍历处理每一行记录
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
// 对<discriminator>标签配置的鉴别器进行处理,获取实际映射的ResultMap对象
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
// 调用getRowValue()把一行数据转换为Java实体对象
Object rowValue = getRowValue(rsw, discriminatedResultMap);
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
// 处理非嵌套ResultMap
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
// 创建ResultLoaderMap对象,用于存放懒加载属性信息
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 创建ResultMap指定的类型实例,通常为<resultMap>标签的type属性指定的类型
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
// 判断该类型是否注册了TypeHandler
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 判断是否需要处理自动映射
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 调用applyAutomaticMappings()方法处理自动映射的字段
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
// 处理<result>标签配置映射的字段
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
如上面的代码所示,在getRowValue()方法中主要做了下面几件事情:
- 创建ResultLoaderMap对象,该对象用于存放懒加载的属性及对应的ResultLoader对象,MyBatis中的ResultLoader用于执行一个查询Mapper,然后将执行结果赋值给某个实体对象的属性。
- 调用createResultObject()方法创建ResultMap对应的Java实体对象,我们需要重点关注该方法的实现,代码如下:
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>(); // 调用createResultObject()方法创建结果对象 Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { // 获取<result>结果集映射信息 final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // 如果映射中配置了懒加载,则创建代理对象 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { // 调用ProxyFactory实例的createProxy()方法创建代理对象 resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result return resultObject; }
- 用applyAutomaticMappings()方法处理自动映射。
- 调用applyPropertyMappings()方法处理<result>标签配置的映射字段,该方法中除了为Java实体属性设置值外,还将指定了懒加载的属性添加到ResultLoaderMap对象中。当我们开启懒加载时,执行查询Mapper返回的实际上是通过Cglib或Javassist创建的动态代理对象。假设我们指定了使用Cglig创建动态代理对象,调用动态代理对象的Getter方法时会执行MyBatis中定义的拦截逻辑,代码如下:在EnhancedResultObjectProxyImpl类的intercept()方法中,获取Getter方法对应的属性名称,然后调用ResultLoaderMap对象的hasLoader()方法判断该属性是否是懒加载属性,如果是,则调用ResultLoaderMap对象的load()方法加载该属性,ResultLoaderMap对象的load()方法最终会调用LoadPair.load()
// org.apache.ibatis.executor.loader.cglib.CglibProxyFactory.EnhancedResultObjectProxyImpl#intercept 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)) { 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.invokeSuper(enhanced, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } }
该方法中创建了一个ResultLoader 对象,然后ResultLoader 对象的loadResult()方法执行查询操作,将查询结果赋值给对应的Java 实体属性。ResultLoader类的loadResult()方法实现如下: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); } this.metaResultObject.setValue(property, this.resultLoader.loadResult()); }
如上面的代码所示,在ResultLoader类的loadResult()方法中调用selectList()方法完成查询操作,selectList()方法中最终会调用Executor对象的query()方法完成查询操作。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 { return localExecutor.<E> query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql); } finally { if (localExecutor != executor) { localExecutor.close(false); } } }