我们已经知道了 Mapper 方法执行的前因,即:获取语句+参数映射(MyBatis 源码分析篇 6:Mapper 方法执行的“前因”)。现在就让我们来看看其“后果”:结果集映射。
参数映射是通过 TypeHandler 实现的,那么同理,结果集映射也应该是通过 TypeHandler 实现。
我们还是按照之前的方式,使用 debug 在入口代码上打断点,步入源码。入口代码为:
List list = mapper.selectByName("Sylvia");
对应的 SQL:
我们接着 MyBatis 源码分析篇 5:Mapper 方法执行之 Executor 一文最后我们找到的 JDBC 代码位置 org.apache.ibatis.executor.statement.PreparedStatementHandler 的 query(...) 方法来看:
@Override
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler. handleResultSets(ps);
}
在执行完 PreparedStatement 的 execute() 后,自然就要进行结果的处理,我们现在就进入最后一行结果处理的代码看看:
org.apache.ibatis.executor.resultset.DefaultResultSetHandler:
@Override
public List
这个方法就是在获取结果集并处理成我们最终想要的 List
- 第一步:获取 PreparedStatement 执行数据库操作后返回的 ResultSet。
ResultSetWrapper rsw = getFirstResultSet(stmt);(方法的第 4 行代码) - 第二步:获取 ResultMap。
ListresultMaps = mappedStatement.getResultMaps();(方法的第 5 行代码)
ResultMap resultMap = resultMaps.get(resultSetCount);(方法的第 9 行代码) - 第三步:通过前两步获得的 ResultSet 和 ResultMap 处理获得最终的 List
类型的结果。
handleResultSet(rsw, resultMap, multipleResults, null);(方法的第 10 行代码)
现在,我们就来对每一步进行跟踪分析。
1 获取 ResultSet
ResultSetWrapper rsw = getFirstResultSet(stmt);
步入这行代码,进入本类的 getFirstResultSet(...) 方法:
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
ResultSet rs = stmt.getResultSet();
while (rs == null) {
// move forward to get the first resultset in case the driver
// doesn't return the resultset as the first result (HSQLDB 2.1)
if (stmt.getMoreResults()) {
rs = stmt.getResultSet();
} else {
if (stmt.getUpdateCount() == -1) {
// no more results. Must be no resultset
break;
}
}
}
return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}
在上一篇关于获取语句+参数映射的分析我们知道,这个方法传入的参数 stmt 是通过动态代理方法生成的,那么上面这个方法中的第一行代码:ResultSet rs = stmt.getResultSet(); 自然就会进入动态代理类 PreparedStatementLogger 中的 invoke(...) 方法来获取 ResultSet:
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if (EXECUTE_METHODS.contains(method.getName())) {
//这里省略了几个 else if
} else if ("getResultSet".equals(method.getName())) {
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
}
//这里还有其他代码,继续省略...
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
我们主要来看 if ("getResultSet".equals(method.getName() 这个判断后的代码:如果现在执行的方法是 getResultSet(...),那么首先会获取一个 ResultSet 的实例,接着会通过 ResultSetLogger.newInstance() 获取的 ResultSet 覆盖掉这个 ResultSet。同获取 PreparedStatement 实例非常相似,这里采用同样的方式:动态代理来通过 ResultSetLogger 代理类获取 ResultSet 实例。此处便不再赘述。
分析到这里我们就拿到了新出炉的 ResultSet,并将它封装到了 ResultSetWrapper 类中以供接下来的使用。
2 获取 ResultMap
List
ResultMap resultMap = resultMaps.get(resultSetCount);
关于获取 ResultMap,它是直接从 MappedStatement 中拿到的,而这个 MappedStatement 实例的内容是在获取 SqlSessionFactory 时填充的,关于这一点我们在 MyBatis 源码分析篇 6:Mapper 方法执行的“前因” 中已经详细分析过,此处便不再赘述,感兴趣的同学可以查看之前的篇章并结合自己实践跟踪源码来回忆一下。
这个时候获得的 ResultMap 是这个样子的:
可以看到 ResultMap 已经包含了每个属性的结果映射 Map、要映射的列和属性,用于接下来的结果映射。
3 获取 List
handleResultSet(rsw, resultMap, multipleResults, null);
通过前两步,我们已经拿到了 ResultSet 和 ResultMap 了,现在我们就要来通过它们处理得到最终我们想要的 List
首先要说明的一点是,这行代码是在 while 循环中,即通过遍历我们拿到的 ResultSetWrapper (封装了 ResultSet),来循环处理每一条结果集。现在就让我们步入这行代码,进入本类的 handleResultSet(...) 方法:
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List 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.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
对于上面这个方法,其中第 6 、7 行代码为我们要跟踪的核心功能。其中,第 6 行代码通过 ObjectFactory 生成 DefaultResultHandler,该 DefaultResultHandler 持有生成的 List 实例。第 7 行代码则是给第 6 行代码执行生成的 List 填充值。而第 8 行代码则是将分页处理过后的 List
现在我们就来步入第 6 行核心代码看看:
public DefaultResultHandler(ObjectFactory objectFactory) {
list = objectFactory.create(List.class);
}
在 DefaultResultHandler 类的构造方法中,调用了 ObjectFactory 的 create(...) 方法。想必大家对 ObjectFactory 已经不再陌生,在前面文档篇关于 ObjectFactory 的学习中我们知道:MyBatis 在获取到结果集后会通过 ObjectFactory 来创建结果对象的实例。它也是在 MyBatis 读取 Configuration XML 的时候存入的。也就是说,如果我们没有在 mybatis-config.xml 中配置自定义的 objectFactory,它会使用默认的 DefaultObjectFactory,默认只是实例化一个目标类。
为了印证这一点,我们跟踪 create(...) 方法来看一下 DefaultObjectFactory 中的实现:
@Override
public T create(Class type, List> constructorArgTypes, List constructorArgs) {
Class> classToCreate = resolveInterface(type);
// we know types are assignable
return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
}
上面这个方法第 1 行代码得到了 ArrayList 类型,继续步入:
private T instantiateClass(Class type, List> constructorArgTypes, List constructorArgs) {
try {
Constructor 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);
}
}
显而易见,原来 MyBatis 使用 ObjectFactory 生成目标类的实例,是通过反射实现的!对于反射,我想大家已经很熟悉了,这里就不展开分析了。
现在我们拿到了 ArrayList 实例,接下来就剩下往里面塞值了。我们接着看前面提到的第 7 行代码:handleResultSet(...) 方法的 handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);。
进入 org.apache.ibatis.executor.resultset.DefaultResultSetHandler 类的 handleRowValuesForSimpleResultMap(...) 方法:
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext resultContext = new DefaultResultContext();
skipRows(rsw.getResultSet(), rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap);
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
其中,方法的第 2 行代码是要进行逻辑分页的,这一点我们已经在 MyBatis 最佳实践篇 1:分页 一文中分析过,这里便不再赘述。我们重点来看 while 循环中的代码,该 while 循环遍历 ResultSet。
循环中的第 1 行代码的作用是对 ResultMap 进行进一步的处理:判断你是否定义了 Discriminator,如果定义了,它会去取你在 Discriminator 中指定的 ResultMap。
循环中的第 2 行代码的作用是获取 Bean 对象的实例并为该 Bean 对象赋值。
循环中的第 3 行代码的作用很简单,就是将获取到的 Bean 对象加入到 List 集合中。
我们想要知道 MyBatis 是如何将数据库获取到的每一条记录变成我们想要的集合中的 Java 对象的,所以第 2 行代码是我们要关注的重点。
步入这行代码,进入 getRowValue() 方法:
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
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)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
我们来分析一下这个方法的主要功能:
- 第一步:获取 Bean 对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);(方法中第 2 行) - 第二步:为 Bean 对象赋值,填充字段
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;(方法中第 9 行)
现在我们就来依次看一下具体是如何实现每一步的。
首先我们跟入第一步的代码,直到进入 org.apache.ibatis.executor.resultset.DefaultResultSetHandler 类的 createResultObject() 方法:
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List> constructorArgTypes, List constructorArgs, String columnPrefix)
throws SQLException {
final Class> resultType = resultMap.getType();
final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
final List 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()) {
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);
}
这个方法首先要通过 ResultMap 获取要创建对象的类型,然后根据该类型判断要如何实例化。因为在该测试中,我们要获取一个普通 Bean 对象 Author 的实例,它有默认的构造方法,所以会进入方法的第 9 行代码:return objectFactory.create(resultType); 这行代码我想大家已经不陌生了,在获取 ArrayList 对象的时候也是通过 ObjectFactory 的 create() 实现的。那么也就是说,对于结果集 List 的 Bean 对象的实例化是通过 ObjectFactory 实现的,也就是通过反射实现的!
现在我们拿到了 Bean 对象,剩下的就是为这个 Bean 对象的每个字段根据我们查询的结果赋值了。我们进入 applyPropertyMappings 方法看一下:
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final List mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
final List propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
//略...
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
//略...
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;
}
上面这个方法首先通过 ResultMap 获取到要映射的列名列表 mappedColumnNames 和属性结果映射列表
propertyMappings。然后遍历 propertyMappings,从 ResultSet 中获取每个列对应的值并将属性值存储到我们之前获得的 Author 对象中。其中,方法的第 10 行:Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); 为获取值,我们跟踪一下:
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
if (propertyMapping.getNestedQueryId() != null) {
//略...
} else {
final TypeHandler> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}
该方法首先获取该映射的 typeHandler 和列名,用 typeHandler 来获取:
@Override
public Integer getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getInt(columnName);
}
这是我们熟悉的 JDBC 代码,但是需要提醒一下,我们前面分析过,这里的 ResultSet 是通过动态代理生成的,所以调用其方法不是直接从我们熟知的 ResultSet 获取,而是通过代理类 ResultSetLogger 来实现,那么接着它会进入到 ResultSetLogger 的 invoke():
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
Object o = method.invoke(rs, params);
if ("next".equals(method.getName())) {
if (((Boolean) o)) {
rows++;
if (isTraceEnabled()) {
ResultSetMetaData rsmd = rs.getMetaData();
final int columnCount = rsmd.getColumnCount();
if (first) {
first = false;
printColumnHeaders(rsmd, columnCount);
}
printColumnValues(columnCount);
}
} else {
debug(" Total: " + rows, false);
}
}
clearColumnInfo();
return o;
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
在该方法的第 5 行代码我们就得到了最终的值。好,现在我们拿到了字段的值,就差把值塞进 Author 里了。我们回到 DefaultResultSetHandler 的 applyPropertyMappings(...) 方法中,跟入 metaObject.setValue(property, value); 这行代码,直到进入 BeanWrapper 类的 setBeanProperty(...):
private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
try {
Invoker method = metaClass.getSetInvoker(prop.getName());
Object[] params = {value};
try {
method.invoke(object, params);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} catch (Throwable t) {
throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
}
}
很明显,这里也用的是反射,try 中的第一行就是在获取要调用的方法。MyBatis 中用到了大量的反射,源码中专门有一个 reflection 模块,就是封装 Java 反射的。我们先跟进去看一下:
public Invoker getSetInvoker(String propertyName) {
Invoker method = setMethods.get(propertyName);
if (method == null) {
throw new ReflectionException("There is no setter for property named '" + propertyName + "' in '" + type + "'");
}
return method;
}
我们看方法的第 1 行代码:Invoker method = setMethods.get(propertyName);,传入的参数是属性名,setMethods 是个 HashMap
那么现在我们就可以判断出来,第 1 行代码通过属性名获取到对应的包含了该属性的 set 方法的 MethodInvoker。然后返回到 BeanWrapper 的 setBeanProperty(...) 方法中,我们继续跟入 try 中的代码:method.invoke(object, params);:
@Override
public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
return method.invoke(target, args);
}
其中,这时的 method 为 public void com.zhaoxueer.learn.entity.Author.setName(java.lang.String),target 为 Author 对象,args 即为属性对应的值。那么接下来它就会进入我们定义的 Bean,即 Author 中的 setName(...) 方法:
public void setName(String name) {
this.name = name;
}
这样,循环完所有的属性结果映射后,我们就会获得一个完美的已经被赋了数据库查询结果的值的 Author 对象。当我们执行完所有的循环后,我们就会最终拿到我们想要的 List
最后,简单总结一下 Mapper 方法执行。我们分了四篇分别来分析了 Mapper 方法是如何执行的,以及 Mapper 方法执行的前因(获取语句+参数映射)后果(结果映射)。其中,从获取 Connection、获取 PreparedStatement 到获取 ResultSet 都使用动态代理的方式来实现,分步打印日志的同时获取对象。对于参数和结果映射都使用了 TypeHandler,我们不需要为每个要映射的属性或字段显式配置 TypeHandler,MyBatis 会根据参数值和结果字段值实际的类型来自动推算 TypeHandler。对于结果映射,当我们从数据库获得 ResultSet 之后,通过使用 ObjectFactory 来反射来实例化对应的 Java 对象,并通过反射的方式将字段值存储到 Java 对象中。
附:
当前版本:mybatis-3.5.0
官网文档:MyBatis
项目实践:MyBatis Learn
手写源码:MyBatis 简易实现