文章目录
前言
上一节【MyBatis3源码深度解析(二十四)级联映射与关联查询(一)级联映射的使用】通过编写一个测试案例,学习了如何使用<resultMap>标签实现MyBatis的一对多、一对一级联映射。
本节来研究一下MyBatis级联映射的实现原理。先说明一下本节使用的测试案例代码:
<!--UserMapper.xml-->
<resultMap id="fullUser" type="User">
<id column="user_id" property="userId"/>
<result column="name" property="name"/>
<!--<result column="age" property="age"/>-->
<!--<result column="phone" property="phone"/>-->
<result column="birthday" property="birthday"/>
<collection property="orderList"
select="com.star.mybatis.mapper.OrderMapper.listOrderByUserId"
ofType="Order"
javaType="List"
column="user_id">
</collection>
</resultMap>
age和phone属性被注释掉了,是故意的,下面分析会用到。
@Test
public void testOne2ManyQuery() {
User user = userMapper.getFullUserById(1);
System.out.println(user.toString());
}
10.2 MyBatis级联映射的实现原理
10.2.1 ResultMap详解
MyBatis是一个半自动化的ORM框架,可以将数据库中的记录转换为Java实体对象,但是Java实体属性通常采用驼峰命名法,而数据库表字段习惯采用下划线分割命名法,因此需要用户指定Java实体属性与数据库表字段之间的映射关系。
Mapper配置中的<resultMap>标签,就用于建立Java实体属性与数据库表字段之间的映射关系,例如本节的测试案例代码。
测试案例的配置中,每个ResultMap有一个全局唯一的ID,即<resultMap>标签的id属性;还会通过type属性指定与哪一个Java实体进行映射。
在<resultMap>标签中,需要使用<id>或<result>标签配置具体的某个表字段与Java实体属性之间的映射关系。数据库主键通常使用<id>标签建立映射关系,普通数据库字段则使用<result>标签。
除了属性映射,ResultMap还支持构造器映射,即<constructor>标签,例如:
<!--UserMapper.xml-->
<resultMap id="UserMap" type="User">
<constructor>
<idArg column="user_id" javaType="Integer"/>
<arg column="name" javaType="String"/>
</constructor>
<result column="age" property="age"/>
<result column="phone" property="phone"/>
<result column="birthday" property="birthday"/>
</resultMap>
使用构造器映射的前提是,Java实体中存在与之相对应的构造方法。<idArg>标签用于配置数据库主键的映射,<arg>标签用于配置数据库普通字段的映射。
最后,总结一下<resultMap>标签的各个子标签的作用:
- <id>:用于配置数据库主键的映射,标记出主键,提高整体性能。
- <result>:用于配置数据库普通字段的映射。
- <collection>:用于配置一对多关联映射,可以关联一个外部的查询Mapper或者配置一个嵌套的ResultMap。
- <association>:用于配置一对一关联映射,可以关联一个外部的查询Mapper或者配置一个嵌套的ResultMap。
- <discriminator>:用于配置根据字段值使用不同的ResultMap。该标签有一个子标签<case>,用于枚举字段值对应的ResultMap,类似于Java中的switch语法。
- <constructor>:用于建立构造器映射。它有两个子标签,<idArg>标签用于配置数据库主键的映射,标记出主键,提高整体性能;<arg>标签用于配置数据库普通字段的映射。
10.2.2 ResultMap解析过程
MyBatis在启动时,所有配置信息都会被转换为Java对象,通过<resultMap>标签配置的结果集映射信息将会被转换为ResultMap对象。 其定义如下:
源码1:org.apache.ibatis.mapping.ResultMap
public class ResultMap {
private Configuration configuration;
// <resultMap>标签的id属性
private String id;
// <resultMap>标签的type属性,指定与数据库表建立映射关系的Java实体
private Class<?> type;
// <result>标签配置的映射信息
private List<ResultMapping> resultMappings;
// <id>标签配置的主键映射信息
private List<ResultMapping> idResultMappings;
// <constructor>标签配置的构造器映射信息
private List<ResultMapping> constructorResultMappings;
// <result>标签配置的结果集映射信息
private List<ResultMapping> propertyResultMappings;
// 存放所有映射的数据库字段信息
private Set<String> mappedColumns;
// 存放所有映射的属性信息
private Set<String> mappedProperties;
// <discriminator>标签配置的鉴别器信息
private Discriminator discriminator;
// 是否有嵌套的<resultMap>
private boolean hasNestedResultMaps;
// 是否存在嵌套查询
private boolean hasNestedQueries;
// <resultMap>标签的autoMapping属性,是否自动映射
private Boolean autoMapping;
// ......
}
ResultMap类中定义的属性的含义如 源码1 中的注释所示。其中有几个属性需要单独解释下:
- mappedColumns:用于存放所有映射的数据库字段信息。当使用columnPrefix属性配置了前缀时,MyBatis会对mappedColumns属性进行遍历,为所有数据库字段追加columnPrefix属性配置的前缀。
- hasNestedResultMaps:该属性用于标识是否有嵌套的ResultMap。当使用<association>或<collection>标签,并以JOIN子句的方式配置一对一或一对多级联映射时,<association>或<collection>标签就相当于一个嵌套的ResultMap,此时hasNestedResultMaps属性为true。
- hasNestedQueries:该属性用于标识是否有嵌套查询。当使用<association>或<collection>标签,并以外部Mapper的方式配置一对一或一对多级联映射时,<association>或<collection>标签存在嵌套查询,此时hasNestedResultMaps属性为true。
- autoMapping:该标签用于标识是否开启自动映射。为true时,即使未使用<id>或<result>标签配置映射字段,MyBatis也会自动对这些字段进行映射。
MyBatis的Mapper配置信息的解析都是通过XMLMapperBuilder类完成的,该类提供了一个parse()
方法,用于解析Mapper中的所有配置信息。
源码2:org.apache.ibatis.builder.xml.XMLMapperBuilder
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 调用XPathParser的evalNode()方法获取根节点对应的XNode对象
// 在调用configurationElement()方法解析该XNode对象
configurationElement(parser.evalNode("/mapper"));
// ......
}
由 源码2 可知,在XMLMapperBuilder类的parse()
方法中,会调用configurationElement()
方法解析<mapper>标签对应的XNode对象。
源码3:org.apache.ibatis.builder.xml.XMLMapperBuilder
private void configurationElement(XNode context) {
try {
// 获取和配置命名空间
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
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"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} // catch ...
}
由 源码3 可知,在XMLMapperBuilder类的configurationElement()
方法中,会逐个解析各种标签,其中就有调用resultMapElements()
方法解析<resultMap>标签。
源码4:org.apache.ibatis.builder.xml.XMLMapperBuilder
private void resultMapElements(List<XNode> list) {
// 对全部<resultMap>标签进行遍历
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) {
return resultMapElement(resultMapNode, Collections.emptyList(), null);
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings,
Class<?> enclosingType) {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 获取<resultMap>标签的属性,按照type→ofType→resultType→javaType的顺序获取
// 如果type属性为空,则获取ofType属性,以此类推
String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
// 获取<resultMap>标签的子标签
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<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 将各标签转换为ResultMapping对象并添加到resultMappings集合中
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
// 获取id、extend、autoMapping属性
String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// 构造ResultMapResolver对象
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator,
resultMappings, autoMapping);
try {
// 调用ResultMapResolver对象的resolve()方法
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
由 源码4 可知,在XMLMapperBuilder类的resultMapElements()
方法中,会使用for循环语句对全部<resultMap>标签进行遍历,每一个<resultMap>标签均调用resultMapElement()
方法。
在resultMapElement()
方法中,会获取<resultMap>标签的所有属性信息,对<id>、<constructor>、<discriminator>等标签进行解析,接着创建一个ResultMapResolver对象,调用ResultMapResolver对象的resolve()
方法返回一个ResultMap对象。
源码5:org.apache.ibatis.builder.ResultMapResolver
public class ResultMapResolver {
private final MapperBuilderAssistant assistant;
private final String id;
private final Class<?> type;
private final String extend;
private final Discriminator discriminator;
private final List<ResultMapping> resultMappings;
private final Boolean autoMapping;
public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, String extend,
Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
this.assistant = assistant;
this.id = id;
this.type = type;
this.extend = extend;
this.discriminator = discriminator;
this.resultMappings = resultMappings;
this.autoMapping = autoMapping;
}
public ResultMap resolve() {
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings,
this.autoMapping);
}
}
由 源码5 可知,在ResultMapResolver的resolve()
方法中,会调用MapperBuilderAssistant对象的addResultMap()
方法创建ResultMap对象。
源码6:org.apache.ibatis.builder.MapperBuilderAssistant
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的情况 ......
}
// 通过建造者模式创建ResultMap对象
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator).build();
// 将ResultMap对象设置到Configuration对象中
configuration.addResultMap(resultMap);
return resultMap;
}
由 源码6 可知,在MapperBuilderAssistant对象的addResultMap()
方法中,会通过建造者模式创建ResultMap对象,并将ResultMap对象设置到Configuration对象中。
借助Debug工具,可以查看测试案例的<resultMap>标签的解析结果:
由图可知,数据库字段与Java实体属性的映射关系封装在一个ResultMapping对象中。重点关注一下orderList属性,其对应的ResultMapping对象信息如下,其中<collection>标签的select属性被封装在ResultMapping对象nestedQueryId属性中:
10.2.3 级联映射的实现原理
默认情况下,MyBatis会选择PreparedStatement操作数据库,因此在执行测试案例中的getFullUserById()
查询操作时,会调用PreparedStatementHandler对象的query()
方法。
源码7:org.apache.ibatis.executor.statement.PreparedStatementHandler
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 执行查询操作
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// 处理结果集
return resultSetHandler.handleResultSets(ps);
}
通过Debug工具,可以发现query()
方法被调用了两次,第一次的查询语句是select * from user where user_id = 1
,第二次的查询语句是select * from `order` where user_id = 1
。
这和上一节的分析结果是一致的,在接下来的分析中也证实了这种结果。
由 源码7 可知,在PreparedStatementHandler对象的query()
方法中,会在完成查询操作后,调用ResultSetHandler对象的handleResultSets()
方法处理结果集。
ResultSetHandler接口只有一个实现类,即DefaultResultSetHandler类。
源码8:org.apache.ibatis.executor.resultset.DefaultResultSetHandler
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 获取第一个结果集,并将其包装成ResultSetWrapper
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++;
}
// ......
}
由 源码8 可知,在DefaultResultSetHandler对象的handleResultSets()
方法中,结果集会被包装成ResultSetWrapper对象,真正处理结果集的方法是handleResultSet()
,其参数包括ResultSetWrapper对象,以及从MappedStatement中取出来的ResultMap对象。
源码9:org.apache.ibatis.executor.resultset.DefaultResultSetHandler
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,
ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
// 当配置了<select>标签的resultSets属性时,parentMapping的值不为null
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else if (resultHandler == null) {
// 当没有指定ResultHandler时,创建默认的DefaultResultHandler
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
// 最终都会调用handleRowValues()方法
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
} finally {
closeResultSet(rsw.getResultSet());
}
}
由 源码9 可知,在DefaultResultSetHandler对象的handleResultSet()
方法中,针对不同的情况做了一些逻辑判断,但最终都会调用handleRowValues()
方法进行处理。
源码10:org.apache.ibatis.executor.resultset.DefaultResultSetHandler
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 判断是否有嵌套ResultMap
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
由 源码10 可知,在DefaultResultSetHandler对象的handleRowValues()
方法中,会判断是否有嵌套ResultMap。如果有,则调用handleRowValuesForNestedResultMap()
方法进行处理,否则调用handleRowValuesForSimpleResultMap()
方法进行处理。
10.2.3.1 handleRowValuesForSimpleResultMap()
在【10.2.2 ResultMap解析过程】中指出,当使用<association>或<collection>标签,并以JOIN子句的方式配置一对一或一对多级联映射时,<association>或<collection>标签就相当于一个嵌套的ResultMap,此时hasNestedResultMaps属性为true。当使用外部Mapper的方式时,hasNestedResultMaps属性为false。
下面分析一下hasNestedResultMaps属性为false时的情况:
源码11:org.apache.ibatis.executor.resultset.DefaultResultSetHandler
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
skipRows(resultSet, rowBounds);
// 遍历结果集对象,处理每一行数据
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 将结果集中的一行数据转换为Java实体对象
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
由 源码11 可知,在DefaultResultSetHandler对象的handleRowValuesForSimpleResultMap()
方法中,会遍历结果集对象,处理每一行数据,调用getRowValue()
方法将结果集中的每一行数据转换为Java实体对象。
源码12:org.apache.ibatis.executor.resultset.DefaultResultSetHandler
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// (1)创建结果对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
// (2)处理自动映射,对未通过<result>等标签配置的映射进行处理
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// (3)处理<result>等标签配置的映射信息
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
由 源码12 可知,在DefaultResultSetHandler对象的getRowValue()
方法做了三件事情:
(1)getRowValue()
方法的第一步,调用createResultObject()
方法创建结果对象。
源码13:org.apache.ibatis.executor.resultset.DefaultResultSetHandler
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
List<Object> constructorArgs, String columnPrefix) throws SQLException {
// ......
if (!constructorMappings.isEmpty()) {
// 根据<constructor>标签配置的构造器映射找到构造方法
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs,
columnPrefix);
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
// 使用默认构造方法
return objectFactory.create(resultType);
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
// 使用标注了@AutomapConstructor注解的构造方法
return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes,
constructorArgs);
}
// throw ...
}
由 源码13 可知,createResultObject()
方法会在按照【<constructor>标签配置的构造器映射→默认构造方法→标注了@AutomapConstructor注解的构造方法】的顺序找到结果对象的构造方法,通过构造方法创建一个结果对象。
(2)getRowValue()
方法的第二步,调用applyAutomaticMappings()
方法处理自动映射,对未通过<result>等标签配置的数据库字段与Java实体属性的映射进行处理。
源码14:org.apache.ibatis.executor.resultset.DefaultResultSetHandler
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
String columnPrefix) throws SQLException {
// 获取需要进行自动映射的UnMappedColumnAutoMapping对象
// UnMappedColumnAutoMapping封装了数据库字段与Java实体属性的关联关系
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
// 遍历
for (UnMappedColumnAutoMapping mapping : autoMapping) {
// 获取数据库记录中该字段的值
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) {
// 调用实体对象对应的MetaObject对象的setValue()方法进行赋值
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
由 源码14 可知,applyAutomaticMappings()
方法首先会找到需要进行自动映射的UnMappedColumnAutoMapping对象集合,UnMappedColumnAutoMapping对象封装了没有通过<result>等标签配置的数据库字段与Java实体属性的对应关系。
接着对UnMappedColumnAutoMapping对象集合进行遍历,获取数据库字段的值,并调用Java实体对象对应的MetaObject对象的setValue()
方法进行赋值。
借助Dubug工具,可以查看测试案例执行到这一步的结果:
由图可知,createAutomaticMappings()
找到了2个需要进行自动映射的属性,恰好就是测试案例中注释掉的age属性和phone属性。整个方法执行完后,可以发现User实体的age属性和phone属性已被成功赋值。
(3)getRowValue()
方法的第三步,调用applyPropertyMappings()
方法处理通过<result>等标签配置的数据库字段与Java实体属性的映射。
源码15:org.apache.ibatis.executor.resultset.DefaultResultSetHandler
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
final Set<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
// 获取通过标签配置的映射集合
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
// 遍历映射集合
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;
}
if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null
|| configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()) {
// 为Java实体对象的属性赋值
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
由 源码15 可知,applyPropertyMappings()
方法的逻辑很清晰,即获取所有通过标签指定的映射信息并遍历,然后找到数据库字段对应的值,最后为Java实体赋值。
借助Dubug工具,可以查看测试案例执行时获取的映射集合:
由图可知,映射集合共有4个ResultMapping对象,分别对应Mapper配置文件中配置的userId、name、birthday、orderList属性。要注意的是,orderList属性是一个<collection>标签,它的select属性被记录在ResultMapping对象的nestedQueryId属性上。
测试案例执行时applyPropertyMappings()
方法的执行结果:
由图可知,applyPropertyMappings()
方法执行完后,用户信息及其关联的订单信息均被查询出来了。
实际上,也是在applyPropertyMappings()
方法中,<collection>标签中配置的外部Mapper被执行了。
(4)深入applyPropertyMappings()
方法中的getPropertyMappingValue()
方法,执行外部Mapper。
源码16:org.apache.ibatis.executor.resultset.DefaultResultSetHandler
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
// 如果ResultMapping对象的nestedQueryId属性不为空,则进行嵌套查询
if (propertyMapping.getNestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
}
// 正常获取数据库字段值的逻辑
if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}
由 源码16 可知,getPropertyMappingValue()
方法会判断ResultMapping对象的nestedQueryId属性是否不为空,如果不为空则进行嵌套查询,否则执行正常获取数据库字段值的逻辑。
在测试案例中,orderList属性是一个<collection>标签,它的select属性被记录在ResultMapping对象的nestedQueryId属性上,因此在获取该属性对应的值时,会进行嵌套查询。
源码17:org.apache.ibatis.executor.resultset.DefaultResultSetHandler
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
// 获取外部Mapper的ID
final String nestedQueryId = propertyMapping.getNestedQueryId();
final String property = propertyMapping.getProperty();
// 根据Mapper的ID获取对应的MappedStatement对象,并准备参数
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping,
nestedQueryParameterType, columnPrefix);
Object value = null;
if (nestedQueryParameterObject != null) {
// ......
} else {
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
nestedQueryParameterObject, targetType, key, nestedBoundSql);
if (propertyMapping.isLazy()) {
lazyLoader.addLoader(property, metaResultObject, resultLoader);
value = DEFERRED;
} else {
// 获取结果
value = resultLoader.loadResult();
}
}
}
return value;
}
由 源码17 可知,getNestedQueryMappingValue()
方法会根据外部Mapper的ID从Configuration对象中获取对应的MappedStatement对象;然后利用MappedStatement对象创建一个ResultLoader对象,在调用ResultLoader对象的loadResult()
方法获取结果。
源码18:org.apache.ibatis.executor.loader.ResultLoader
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对象的query()方法执行查询操作
return localExecutor.query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER,
cacheKey, boundSql);
} // finally ......
}
由 源码18 可知,ResultLoader对象的loadResult()
方法会转调selectList()
方法,而该方法会调用Executor对象的query()方法执行查询操作。
在测试案例中,通过这一次额外查询,orderList属性的值也被填充好了:
至此,getRowValue()
方法(源码12)全部执行完毕,handleRowValuesForSimpleResultMap()
方法(源码11)得到了最终的查询结果:
10.2.3.2 handleRowValuesForNestedResultMap()
下面再来看看 源码10 中,hasNestedResultMaps属性为true时的情况,即存在嵌套ResultMap,此时会调用handleRowValuesForNestedResultMap()
方法。
这个方法与上面分析的逻辑不同的地方在于:applyPropertyMappings()
方法中不会对orderList属性进行赋值,而是在这后面加了一个applyNestedResultMappings()
方法来赋值。
具体源码不再展开,分析方法和上面的分析逻辑类似。
······
本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析