title: ResultSetHandler
date:2017年11月26日16:58:04
categories: Mybatis
ResultSetHandler介绍
ResultSetHandler是处理结果集的接口,在我看来他的实现类DefaultResultSetHandler是最难理解的一个组件,之所以复杂是因为Mybatis中ResultMap的设计非常强大,他可以满足用户的很多需求。
MyBatis是基于“数据库结构不可控”的思想建立的,也就是我们希望数据库遵循第三范式或BCNF,但实际事与愿违,那么结果集映射就是MyBatis为我们提供这种理想与现实间转换的手段了,为了实现这一灵活的转化,给用户带来便利,Mybatis做了很多努力,这些努力的具体实现大多都体现在DefaultResultSetHandler这个类中。所以在后面我们会通过查询过程的Debug来好好分析这个类。
我们先看看ResultSetHandler接口的实现
public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
这个接口只定义了两个方法
handleResultSets就是用来处理结果集的方法,如何将数据库执行后返回的结果集转化为我们在Mapper文件中定义的ResultMap对应的返回,就是通过这个方法实现的。
handleOutputParameters这个方法目前还没有用到,因为目前还没有使用到存储过程。
handleResultSets
我们接着分析查询的过程
现在代码已经执行到了这个阶段
PreparedStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//执行数据库操作
ps.execute();
//调用handleResultSets处理结果集
return resultSetHandler.<E> handleResultSets(ps);
}
执行ps.execute();后
控制台如下
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2ed2d9cb]
==> Preparing: select * from t_user where t_id in ( ? , ? , ? )
==> Parameters: 1(Integer), 3(Integer), 25(Integer)
进入handleResultSets方法
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//先新建一个存放最终的结果的ArrayList
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
//得到ResultSet结果集, 然后对ResultSetMetaData元数据封装一下
ResultSetWrapper rsw = getFirstResultSet(stmt);
//得到该SqlMapper配置文件指定的ResultMap
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
//校验ResultMap的数量,不能为0或者不存在,如果是,则会抛出异常
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
//依次从List集合中按index取ResultMap
ResultMap resultMap = resultMaps.get(resultSetCount);
//然后加入针对ResultMap处理结果集的方法
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResulSets();
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(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
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;
}
这个方法就是为了得到ResultSet结果集,如果成功得到结果集,就将结果集封装一下,返回封装好的ResultSetWrapper对象
这个方法里面用到了一些原生JDBC的API方法
getResultSet
ResultSet getResultSet()
throws SQLException以 ResultSet 对象的形式获取当前结果。每个结果只应调用一次此方法。
返回:
以 ResultSet 对象的形式返回当前结果;如果结果是更新计数或没有更多的结果,则返回 null
抛出:
SQLException - 如果发生数据库访问错误,或者在已关闭的 Statement 上调用此方法
另请参见:
execute(java.lang.String)
getMoreResults
boolean getMoreResults(int current)
throws SQLException将此 Statement 对象移动到下一个结果,根据给定标志指定的指令处理所有当前 ResultSet 对象;如果下一个结果为 ResultSet 对象,则返回 true。
当以下表达式为 true 时没有更多结果:
// stmt is a Statement object
((stmt.getMoreResults(current) == false) && (stmt.getUpdateCount() == -1))
参数:
current - 下列 Statement 常量之一,这些常量指示将对使用 getResultSet 方法获取的当前 ResultSet 对象发生的操作:Statement.CLOSE_CURRENT_RESULT、Statement.KEEP_CURRENT_RESULT 或 Statement.CLOSE_ALL_RESULTS
返回:
如果下一个结果为 ResultSet 对象,则返回 true;如果其为更新计数或者不存在更多的结果,则返回 false
抛出:
SQLException - 如果发生数据库访问错误,在已关闭的 Statement 上调用此方法,或者提供的参数不是以下参数之一:Statement.CLOSE_CURRENT_RESULT、Statement.KEEP_CURRENT_RESULT 或 Statement.CLOSE_ALL_RESULTS
SQLFeatureNotSupportedException - 如果 DatabaseMetaData.supportsMultipleOpenResults 返回 false,并且 Statement.KEEP_CURRENT_RESULT 或 Statement.CLOSE_ALL_RESULTS 作为参数提供。
从以下版本开始:
1.4
--------------------------------------------------------------------------------
getUpdateCount
int getUpdateCount()
throws SQLException以更新计数的形式获取当前结果;如果结果为 ResultSet 对象或没有更多结果,则返回 -1。每个结果只应调用一次此方法。
返回:
以更新计数的形式返回当前结果;如果当前结果为 ResultSet 对象或没有更多结果,则返回 -1
抛出:
SQLException - 如果发生数据库访问错误,或者在已关闭的 Statement 上调用此方法
另请参见:
execute(java.lang.String)
我们也有必要了解这个封装的对象ResultSetWrapper
class ResultSetWrapper {
private final ResultSet resultSet;
private final TypeHandlerRegistry typeHandlerRegistry;
private final List<String> columnNames = new ArrayList<String>();
private final List<String> classNames = new ArrayList<String>();
private final List<JdbcType> jdbcTypes = new ArrayList<JdbcType>();
private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<String, Map<Class<?>, TypeHandler<?>>>();
private Map<String, List<String>> mappedColumnNamesMap = new HashMap<String, List<String>>();
private Map<String, List<String>> unMappedColumnNamesMap = new HashMap<String, List<String>>();
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
super();
//注入了TypeHandlerRegistry,TypeHandler的注册中心
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
//注入了ResultSet
this.resultSet = rs;
//得到ResultSet的元数据 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象。
final ResultSetMetaData metaData = rs.getMetaData();
final int columnCount = metaData.getColumnCount();
//对元数据的相关有用信息进行封装
for (int i = 1; i <= columnCount; i++) {
//将返回的元数据的ColumnName添加到一个String类型的ArrayList中,如果SQL中使用了AS ColumnLable就取ColumnLable,否则就取ColumnName.
/*
getColumnLabel
String getColumnLabel(int column)
throws SQLException获取用于打印输出和显示的指定列的建议标题。建议标题通常由 SQL AS 子句来指定。如果未指定 SQL AS,则从 getColumnLabel 返回的值将和 getColumnName 方法返回的值相同。
getColumnName
String getColumnName(int column)
throws SQLException获取指定列的名称。
*/
columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
/*
getColumnType
int getColumnType(int column)
throws SQLException获取指定列的 SQL 类型。
得到元数据指定Column的SQL 类型类型,将类型也存储到一个List集合中
*/
jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
/*
getColumnClassName
String getColumnClassName(int column)
throws SQLException如果调用方法 ResultSet.getObject 从列中获取值,则返回构造其实例的 Java 类的完全限定名称。ResultSet.getObject 可能返回此方法所返回的类的子类。
参数:
column - 第一列是 1,第二个列是 2,……
返回:
Java 编程语言中类的完全限定名称,方法 ResultSet.getObject 将使用该名称获取指定列中的值。此名称为用于自定义映射关系的类名称。
*/
//将列对应的类完全限定名也加入一个List中
classNames.add(metaData.getColumnClassName(i));
}
}
}
handleResultSet
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
//是否有父Mapper
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
}
//如果没有
else {
//且ResultHandler为空,即Method参数列表中没有ResultHandler
if (resultHandler == null) {
//新建一个DefaultResultHandler
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
//然后将新的resultHandler注入到handleRowValues中,对结果集进行处理
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
closeResultSet(rsw.getResultSet()); // issue #228 (close resultsets)
}
}
handleRowValues
private 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);
}
}
handleRowValuesForSimpleResultMap
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
//临时存储结果的上下文
DefaultResultContext resultContext = new DefaultResultContext();
//这个方法与RowBounds有关,目前没有理解这个方法,但是这个方法目前不是特别重要。
skipRows(rsw.getResultSet(), rowBounds);
//判断是否需要继续执行
//shouldProcessMoreRows会去校验上下文的标志位以及行数是否超过RowBounds的限制
// rsw.getResultSet().next()就很好理解了,看下API的解释就明了了
/*
next
boolean next()
throws SQLException将光标从当前位置向前移一行。ResultSet 光标最初位于第一行之前;第一次调用 next 方法使第一行成为当前行;第二次调用使第二行成为当前行,依此类推。
当调用 next 方法返回 false 时,光标位于最后一行的后面。任何要求当前行的 ResultSet 方法调用将导致抛出 SQLException。如果结果集的类型是 TYPE_FORWARD_ONLY,则其 JDBC 驱动程序实现对后续 next 调用是返回 false 还是抛出 SQLException 将由供应商指定。
如果对当前行开启了输入流,则调用 next 方法将隐式关闭它。读取新行时,将清除 ResultSet 对象的警告链。
返回:
如果新的当前行有效,则返回 true;如果不存在下一行,则返回 false
*/
//当执行了rsw.getResultSet().next()后,第一个结果集就出来了
//控制台新增输出:
/*
<== Columns: t_id, t_name, address
<== Row: 1, kobe, los
*/
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
//这个方法目前没有深究,但是看到Discriminated我想应该是校验是否在SqlMapper中用到了discriminate把,之后测试了这一情况再来细说
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
//这里终于到了正式处理结果集的时候了,
Object rowValue = getRowValue(rsw, discriminatedResultMap);
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
getRowValue(rsw, discriminatedResultMap)
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
//创建我们要返回的对象类型,这里是User
Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null);
// 创建的实例不为空而且也不是基本类型(泛指)
if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
//构造出一个Mybatis自己定义的MetaObject
final MetaObject metaObject = configuration.newMetaObject(resultObject);
//判断是否有指定构造方法
boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;
//判断是否有autoMapping,如果没有执行if下的语句,目前还没有弄清楚autoMapping是什么属性
if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
//处理没有显式用result节点声明的ColumnName
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
//处理显式使用result节点声明的ColumnName
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
resultObject = foundValues ? resultObject : null;
return resultObject;
}
createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>();
final List<Object> constructorArgs = new ArrayList<Object>();
final Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
//创建的实例不为空而且也不是基本类型(泛指)
if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
//就看下propertyMappings里面是不是带有查询,即select属性
//propertyMappings 很显然就对应ResultMap节点中的Result节点
for (ResultMapping propertyMapping : propertyMappings) {
//如果带有select属性而且指定了是懒加载
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { // issue gcode #109 && issue #149
//具体这种方式是如何创建的,目前还没有测试过,后续再细说
return configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
}
}
}
return resultObject;
}
createResultObject
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
throws SQLException {
//先得到ResultMap要返回的类型,也就是我们在Mapper文件中对ResultMap配置时的type属性
final Class<?> resultType = resultMap.getType();
//这个目前测试的都是为null的,看这个命名应该是ResultMap中有指定对应的返回类型的构造方法吧
final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
//如果对应的返回类型不是JavaBean或者集合类型等,而是基本类型或者是他的一些包装类和时间日期等类型
if (typeHandlerRegistry.hasTypeHandler(resultType)) {
//创建基本类型的返回类型的实例
return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
}
//如果指定了构造方法,使用构造方法来创建实例
else if (constructorMappings.size() > 0) {
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
} else {
//要不然使用objectFactory来创建实例
return objectFactory.create(resultType);
}
}
objectFactory.create(resultType)
DefaultObjectFactory
public <T> T create(Class<T> type) {
return create(type, null, null);
}
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
Class<?> classToCreate = resolveInterface(type);
@SuppressWarnings("unchecked")
// we know types are assignable
T created = (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
return created;
}
private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
try {
Constructor<T> constructor;
//如果没有在ResultMap中指定构造方法,Mybatis默认该类只有默认的无参构造方法
if (constructorArgTypes == null || constructorArgs == null) {
/*
getDeclaredConstructor
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
throws NoSuchMethodException,
SecurityException返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。parameterTypes 参数是 Class 对象的一个数组,它按声明顺序标识构造方法的形参类型。 如果此 Class 对象表示非静态上下文中声明的内部类,则形参类型作为第一个参数包括显示封闭的实例。
参数:
parameterTypes - 参数数组
返回:
带有指定参数列表的构造方法的 Constructor 对象
*/
//这里调用该方法时没有指定参数,意思即试图返回该类的无参构造方法,如果没有无参构造方法,就会抛出异常,然后再后面的catch住,后抛出给用户
constructor = type.getDeclaredConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
//如果存在无参构造方法,就直接创建一个实例且返回
return constructor.newInstance();
}
//如果有指定构造方法,试图返回相应的有参构造方法,如果失败,抛出异常后catch住重新抛出给用户
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) {
for (Class<?> argType : constructorArgTypes) {
argTypes.append(argType.getSimpleName());
argTypes.append(",");
}
}
StringBuilder argValues = new StringBuilder();
if (constructorArgs != null) {
for (Object argValue : constructorArgs) {
argValues.append(String.valueOf(argValue));
argValues.append(",");
}
}
throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
}
}
当我们分析完这段代码的时候,我们就可以很清楚的知道为什么当我们没有在ResultMap中显式的指定构造方法时,如果我们对我们的JavaBean创建了一个有参构造方法而此时没有显式的给出无参构造方法,这时运行程序,就会抛出Error instantiating异常。
有些同学可能会觉得看源码的用处不大,甚至是浪费时间,但是我不这样觉得,看源码还是有很多好处的。
1、看源码我们可以学习框架的设计思想,学习设计模式。
2、看源码可以加深对JDK API的认识,再复杂的代码都是基于JDK API写出来的,对JDK API的熟悉度会一定程度决定你的编码质量。
3、看源码虽然确实很花时间,但是看了源码之后我们才真正的做到了知其然,也知其所以然,这是一个热爱的代码,热爱编程了必须要有的专研精神,看了源码,我们就知道了,这个框架到底为什么要这样用,为什么那样配置就不对,为什么会报这个错误,这样我们才能利用用框架工具,而不是被框架牵着走。
啰嗦完了,继续往下看。
configuration.newMetaObject(resultObject)
public MetaObject newMetaObject(Object object) {
return MetaObject.forObject(object, objectFactory, objectWrapperFactory);
}
public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory) {
if (object == null) {
return SystemMetaObject.NULL_META_OBJECT;
} else {
return new MetaObject(object, objectFactory, objectWrapperFactory);
}
}
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory) {
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
//如果object已经是封装过的ObjectWarpper,就不需要再封装了,直接转化为ObjectWarpper类型就好了
if (object instanceof ObjectWrapper) {
this.objectWrapper = (ObjectWrapper) object;
}
//如果是已经封装了,去Factory里取
else if (objectWrapperFactory.hasWrapperFor(object)) {
this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
}
//如果是Map类型,创建一个MapWrapper
else if (object instanceof Map) {
this.objectWrapper = new MapWrapper(this, (Map) object);
}
//如果是Collection类型,一定要注意哦,Collection和Map是平级的哦,Map可不属于Collection
//Collection下面有List Set等
else if (object instanceof Collection) {
this.objectWrapper = new CollectionWrapper(this, (Collection) object);
} else {
//否则创建一个JavaBean 普通Java对象的包装类
this.objectWrapper = new BeanWrapper(this, object);
}
}
BeanWarpper的创建
public BeanWrapper(MetaObject metaObject, Object object) {
super(metaObject);
this.object = object;
this.metaClass = MetaClass.forClass(object.getClass());
}
public static MetaClass forClass(Class<?> type) {
return new MetaClass(type);
}
private MetaClass(Class<?> type) {
this.reflector = Reflector.forClass(type);
}
this.reflector = Reflector.forClass(type);
这里的reflector是MetaClass的一个属性对应的类是Reflector,是Mybatis处理反射最重要的类。
Reflector
public static Reflector forClass(Class<?> clazz) {
if (classCacheEnabled) {
// synchronized (clazz) removed see issue #461
Reflector cached = REFLECTOR_MAP.get(clazz);
if (cached == null) {
cached = new Reflector(clazz);
REFLECTOR_MAP.put(clazz, cached);
}
return cached;
} else {
return new Reflector(clazz);
}
}
private Reflector(Class<?> clazz) {
type = clazz;
//添加默认的无参构造方法
addDefaultConstructor(clazz);
//添加Get方法
addGetMethods(clazz);
//添加Set方法
addSetMethods(clazz);
//添加Field属性
addFields(clazz);
readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
//下面的两个put元素的操作是很重要的,我们看到他们把对应的PropertyName的大写作为Key,
//把PropertyName作为Value存入这个HashMap中
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writeablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
applyAutomaticMappings
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
//得到没有显式声明的ColumnName 的 List
final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
//后续的赋值操作比较简单,就不详细分析了
for (String columnName : unmappedColumnNames) {
String propertyName = columnName;
if (columnPrefix != null && columnPrefix.length() > 0) {
// When columnPrefix is specified,
// ignore columns without the prefix.
if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
propertyName = columnName.substring(columnPrefix.length());
} else {
continue;
}
}
//对这句代码还是有必要分析一下的,因为这里就是驼峰命名可以不显式声明ColumnName的原因所在
final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
if (property != null && metaObject.hasSetter(property)) {
final Class<?> propertyType = metaObject.getSetterType(property);
if (typeHandlerRegistry.hasTypeHandler(propertyType)) {
final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
final Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
if (value != null || configuration.isCallSettersOnNulls()) { // issue #377, call setter on nulls
if (value != null || !propertyType.isPrimitive()) {
metaObject.setValue(property, value);
}
foundValues = true;
}
}
}
}
return foundValues;
}
getUnmappedColumnNames
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);
final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
for (String columnName : columnNames) {
final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
if (mappedColumns.contains(upperColumnName)) {
mappedColumnNames.add(upperColumnName);
} else {
unmappedColumnNames.add(columnName);
}
}
mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}
loadMappedAndUnmappedColumnNames这个方法将没有显式的在ResultMap节点中的Result节点声明的property和显式声明的property分别存到unmappedColumnNames和unmappedColumnNames中。
findProperty(propertyName, configuration.isMapUnderscoreToCamelCase())
final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
//如果设置了数据库列名使用驼峰命名可以不用显式声明result,会把列名的下划线去掉,使其与JavaBean 属性名相同
public String findProperty(String name, boolean useCamelCaseMapping) {
if (useCamelCaseMapping) {
name = name.replace("_", "");
}
return findProperty(name);
}
如果设置了
<setting name="mapUnderscoreToCamelCase" value="true"/>
上述的useCamelCaseMapping就是true了
applyPropertyMappings
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;
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
final 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);
final String property = propertyMapping.getProperty(); // issue #541 make property optional
if (value != NO_VALUE && property != null && (value != null || configuration.isCallSettersOnNulls())) { // issue #377, call setter on nulls
if (value != null || !metaObject.getSetterType(property).isPrimitive()) {
metaObject.setValue(property, value);
}
foundValues = true;
}
}
}
return foundValues;
}
getPropertyMappingValue
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
//如果result节点中select属性存在
if (propertyMapping.getNestedQueryId() != null) {
//在本次查询中是不会用到这个方法的,因为result节点中并没有
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
}
//这一分支应该是这个select属性对应的查询结果ResultMap中的result节点依然有select属性,当然目前还没有验证
else if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping);
return NO_VALUE;
} else if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
return NO_VALUE;
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}
如果我们的ResultMap是这样的
。。。待补充
那么就会用到这个方法
getNestedQueryMappingValue
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final String nestedQueryId = propertyMapping.getNestedQueryId();
final String property = propertyMapping.getProperty();
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
//得到该result节点对应column的值,这个值将作为select对应的select Mapper的参数传入
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
Object value = NO_VALUE;
if (nestedQueryParameterObject != null) {
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
//ofType 如果没有指定ofType,应该会取selct Mapper中对应的ResultMap Type把,这个做实验就知道会不会这样取了
final Class<?> targetType = propertyMapping.getJavaType();
if (executor.isCached(nestedQuery, key)) {
executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
} else {
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
//如果是懒加载,不要立即查出结果,先缓存
if (propertyMapping.isLazy()) {
lazyLoader.addLoader(property, metaResultObject, resultLoader);
} else {
//执行查询,得到结果
value = resultLoader.loadResult();
}
}
}
return value;
}
loadResult
public Object loadResult() throws SQLException {
//selectList并不难理解
List<Object> list = selectList();
//extractObjectFromList就是转化下返回的类型
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);
}
}
}
extractObjectFromList
public Object extractObjectFromList(List<Object> list, Class<?> targetType) {
Object value = null;
if (targetType != null && targetType.isAssignableFrom(list.getClass())) {
value = list;
} else if (targetType != null && objectFactory.isCollection(targetType)) {
value = objectFactory.create(targetType);
MetaObject metaObject = configuration.newMetaObject(value);
metaObject.addAll(list);
} else if (targetType != null && targetType.isArray()) {
Class<?> arrayComponentType = targetType.getComponentType();
Object array = Array.newInstance(arrayComponentType, list.size());
if (arrayComponentType.isPrimitive()) {
for (int i = 0; i < list.size(); i++) {
Array.set(array, i, list.get(i));
}
value = array;
} else {
value = list.toArray((Object[])array);
}
} else {
if (list != null && list.size() > 1) {
throw new ExecutorException("Statement returned more than one row, where no more than one was expected.");
} else if (list != null && list.size() == 1) {
value = list.get(0);
}
}
return value;
}
总结
到这里对ResultSetHandler的讲解就稍微告一段落了
目前还有MapKey(差实验),鉴别器(),缓存,懒加载还没有讲到。
ResultSetHandler是Mybatis最复杂的一部分,它对应着ResultMap的处理,而ResultMap的配置也是配置文件中最难的。
在这里再说下我之前的一个疑问,也是自己对泛型没有很好的理解到位。
当我第一次看到这段代码时
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
我对这行
return resultSetHandler.<E> handleResultSets(ps);
是蛮疑惑的,resultSetHandler.\ handleResultSets(ps);
这种在调用方法前使用泛型的,代表这个方法肯定是泛型方法,否则肯定是不能这样用的。
但是我们发现在DefaultResultSetHandler中这个方法的返回类型是List\,并不是一个泛型方法呀?
public List<Object> handleResultSets(Statement stmt) throws SQLException {...}
但是依然在返回给我们结果的时候进行了强制的类型转化,也就是实现了泛型方法的效果。
这是为什么呢?在一开始我是十分想不通的。
当然在ResultSetHandler接口中,这是一个泛型方法
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
这说明实现了这个接口的方法,即使在实现类指定了某个具体的类型,还是具有泛型方法的特性的,还是会被当做泛型方法的。
至于为什么会这样,我的理解是,只要实现的方法是泛型方法,即使实现类里明确指定了特定的返回类型,依然会在编译时期,完成类型的强制转化,依然是具有泛型方法特性的。
最后,我们举个例子来验证一下:
package com.wangcc.MyJavaSE.Generic;
import java.util.List;
public interface ResultSetHandler {
public <E> List<E> handleResultSets();
}
public class DefaultResultSetHandler implements ResultSetHandler {
public List<Object> handleResultSets() {
// TODO Auto-generated method stub
List<Object> list = new ArrayList<Object>();
list.add("Kobe");
list.add("james");
return list;
}
}
public class GenericTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
ResultSetHandler resultSetHandler = new DefaultResultSetHandler();
List<String> list = resultSetHandler.<String>handleResultSets();
for (String str : list) {
System.out.println(str);
}
}
}
这个测试类是可以正常运行的。