- 三哥
内容来自【自学星球】
欢迎大家来了解我的星球,和星主(也就是我)一起学习 Java ,深入 Java 体系中的所有技术。我给自己定的时间是一年,无论结果如何,必定能给星球中的各位带来点东西。
想要了解更多,欢迎访问👉:自学星球
--------------SSM系列源码文章及视频导航--------------
创作不易,望三连支持!
SSM源码解析视频
👉点我
Spring
SpringMVC
MyBatis
---------------------【End】--------------------
回到下面代码:
return resultSetHandler. handleResultSets(ps);
//进行resultSet自动映射
return resultSetHandler. handleResultSets(ps);
在分析代码之前,我们先来看看 ResultSetHandler 和 ResultSetWrapper 两个类。
public interface ResultSetHandler {// 这里对结果集进行处理,返回的是集合 List handleResultSets(Statement stmt) throws SQLException;// 这里对结果集进行处理,返回的是游标 Cursor handleCursorResultSets(Statement stmt) throws SQLException;// 对存储过程的输出参数进行处理void handleOutputParameters(CallableStatement cs) throws SQLException;
}
public class ResultSetWrapper {// jdbc返回的结果集private final ResultSet resultSet;// 类型转换器注册中心private final TypeHandlerRegistry typeHandlerRegistry;// 查询结果中每列的名称private final List columnNames = new ArrayList<>();// 查询结果中每列的java类型private final List classNames = new ArrayList<>();// 查询结果中每列的jdbc类型private final List jdbcTypes = new ArrayList<>();// 每列对应的typeHandlerprivate final Map, TypeHandler>>> typeHandlerMap = new HashMap<>();// 记录了总的映射列名,key是ResultMap的id,value是该ResultMap的列名集合private final Map> mappedColumnNamesMap = new HashMap<>();// 和上面的属性相反,记录了未映射的列名,key是ResultMap的id,value是该ResultMap未映射的列名集合private final Map> unMappedColumnNamesMap = new HashMap<>();public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {super();this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();this.resultSet = rs;final ResultSetMetaData metaData = rs.getMetaData();final int columnCount = metaData.getColumnCount();for (int i = 1; i <= columnCount; i++) {// columnLable代表原始的列名,columnName代表别名columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));classNames.add(metaData.getColumnClassName(i));}}
}
接着我们来看映射结果集方法源码:
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
public List
多结果集只会在调用存储的时候出现,所以这里只介绍下单个结果集的情况,即 handleResultSet(rsw, resultMap, multipleResults, null) 方法。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#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) {// 如果用户没有指定对结果的处理器ResultHandler,那么默认会使用DefaultResultHandlerDefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);// 对结果集进行转换handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);// 将解析后的结果添加到multiResults中// 如果没有指定ResultHandler,那么默认会将解析之后的结果添加到multipleResults中multipleResults.add(defaultResultHandler.getResultList());} else {// 用户定义了对结果集的处理方法,即ResultHandler// 那么使用ResultSetHandler处理之后,会将结果再交给ResultHandler进行处理handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);}}} finally {// issue #228 (close resultsets)closeResultSet(rsw.getResultSet());}
}
该方法通过 handleRowValues 方法来映射 ResultSet 结果,并将并将映射的结果从 defaultResultHandler 的 ResultList 方法中取出存入 multipleResults 中,完成映射。
下面我们来看核心方法 handleRowValues 。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValues
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {// 判断是否有嵌套的映射if (resultMap.hasNestedResultMaps()) {ensureNoRowBounds();checkResultHandler();// 处理嵌套映射handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);} else {// 处理简单映射handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);}
}
我们可以通过 resultMap.hasNestedResultMaps() 知道查询语句是否是嵌套查询,如果 resultMap 中包含 和 且其 select 属性不为空,则为嵌套查询。
这里我们之分析简单查询。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForSimpleResultMap
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)throws SQLException {// ResultContext用来存放结果对象DefaultResultContext resultContext = new DefaultResultContext();// 根据 RowBounds 定位到指定行记录(取出rowbounds中的offset,跳过结果集中的前面offset行)skipRows(rsw.getResultSet(), rowBounds);// ResultSet是一个集合,很有可能我们查询的就是一个List,这就就每条数据遍历处理while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);// 从 resultSet 中获取结果Object rowValue = getRowValue(rsw, discriminatedResultMap);// 存储结果到resultHandler的ResultList,最后ResultList加入multipleResults中返回storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());}
}
该方法通过遍历结果集挨个调用 getRowValue 方法来进行结果集的映射,这里遍历映射是因为结果集可能不止一个。
RowBounds 是默认的分页工具(内存分页)。
下面我们来看看 getRowValue 方法的具体映射源码。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap)
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {// 这个Map是用来存储延迟加载的BountSql的,我们下面来看final ResultLoaderMap lazyLoader = new ResultLoaderMap();// 创建实体类对象,比如 User 对象Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);// 判断结果值是否为空,并且没有对应当前结果java类型的typehandlerif (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {final MetaObject metaObject = configuration.newMetaObject(rowValue);boolean foundValues = this.useConstructorMappings;if (shouldApplyAutomaticMappings(resultMap, false)) {//自动映射,结果集中有的column,但resultMap中并没有配置 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;}// 根据 节点中配置的映射关系进行映射foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;foundValues = lazyLoader.size() > 0 || foundValues;rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}return rowValue;
}
该方法的主要逻辑分为如下几个步骤:
下面我们来看看结果集映射对象的创建。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap)
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, org.apache.ibatis.executor.loader.ResultLoaderMap, java.lang.String)
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {// 用来判断是否使用到了构造方法参数映射this.useConstructorMappings = false; // reset previous mapping resultfinal List> constructorArgTypes = new ArrayList>();final List constructorArgs = new ArrayList();// 调用重载方法创建实体类对象Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);// 当前结果不为空,并且不存在可以直接将ResultSet转换为指定java类型的typeHandlerif (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {final List propertyMappings = resultMap.getPropertyResultMappings();for (ResultMapping propertyMapping : propertyMappings) {// issue gcode #109 && issue #149// 如果开启了延迟加载,则为 resultObject 生成代理类,如果仅仅是配置的关联查询,没有开启延迟加载,是不会创建代理类if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {/** 创建代理类,默认使用 Javassist 框架生成代理类。* 由于实体类通常不会实现接口,所以不能使用 JDK 动态代理 API 为实体类生成代理。* 并且将lazyLoader传进去了*/resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);break;}}}// 如果结果对象不为空,并且构造方法使用到了构造参数映射,那么将useConstructorMappings设置为truethis.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping resultreturn resultObject;
}
下面重载方法 createResultObject 的实现逻辑
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, java.util.List
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);// 取出构造函数参数的映射,就是FLAG为CONSTRUCTOR的映射final List constructorMappings = resultMap.getConstructorResultMappings();if (hasTypeHandlerForResultObject(rsw, resultType)) {// 如果符合当前java结果类型的TypeHandler,那么会使用typehandler来对结果集进行处理return createPrimitiveResultObject(rsw, resultMap, columnPrefix);} else if (!constructorMappings.isEmpty()) {// 如果指定了构造参数映射,使用构造参数映射来进行构造return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {// 如果是结果或者有默认构造函数,那么直接通过ObjectFactory来创建return objectFactory.create(resultType);} else if (shouldApplyAutomaticMappings(resultMap, false)) {// 判断是否开启了自动映射return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);}throw new ExecutorException("Do not know how to create an instance of " + resultType);
}
通常我们的映射实体都是通过默认构造函数来进行创建的,也即 objectFactory.create(resultType) 方法逻辑。
objectFactory.create 的创建逻辑就是通过反射进行创建,就不看源码了。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap)
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#applyAutomaticMappings
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {// 获取查询结果集中出现但是没有定义resultMapping的列// ResultSetWrapper会通过ResultSet来解析出当前查询结果返回的所有列// 从ResultMap中可以获取到当前已经指定了映射的列// 然后就可以得出有哪些查询结果中的列没有指定映射List autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);boolean foundValues = false;if (!autoMapping.isEmpty()) {// 遍历这些没有指定映射的结果集中的列for (UnMappedColumnAutoMapping mapping : autoMapping) {// 使用typeHandler进行转换,转换成Java类型final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);if (value != null) {foundValues = true;}if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {// 通过MetaObject将值设置到结果对象中// gcode issue #377, call setter on nulls (value is not 'found')metaObject.setValue(mapping.property, value);}}}return foundValues;
}
该方法比较简单,先获取未配置 resultMap 映射节点的信息,即 List 对象,然后遍历该集合,获取属性值并设置到对象属性中,完成映射。
那我们来看看如何获取没有定义 resultMap 映射信息的集合,即 createAutomaticMappings 方法。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createAutomaticMappings
private List createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {final String mapKey = resultMap.getId() + ":" + columnPrefix;// 从缓存中获取 UnMappedColumnAutoMapping 列表List autoMapping = autoMappingsCache.get(mapKey);// 缓存未命中if (autoMapping == null) {autoMapping = new ArrayList();// 从 ResultSetWrapper 中获取未配置在 中的列名final List unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);for (String columnName : unmappedColumnNames) {String propertyName = columnName;if (columnPrefix != null && !columnPrefix.isEmpty()) {// When columnPrefix is specified,// ignore columns without the prefix.if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {propertyName = columnName.substring(columnPrefix.length());} else {continue;}}// 将下划线形式的列名转成驼峰式,比如 AUTHOR_NAME -> authorNamefinal String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());if (property != null && metaObject.hasSetter(property)) {// 检测当前属性是否存在于 resultMap 中if (resultMap.getMappedProperties().contains(property)) {continue;}// 获取属性对于的类型final Class> propertyType = metaObject.getSetterType(property);if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {final TypeHandler> typeHandler = rsw.getTypeHandler(propertyType, columnName);// 封装上面获取到的信息到 UnMappedColumnAutoMapping 对象中autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));} else {configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property, propertyType);}} else {configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);}}// 写入缓存autoMappingsCache.put(mapKey, autoMapping);}return autoMapping;
}
原来该方法是从 ResultSetWrapper 中获取未配置 中的列名啊!然后处理属性的命名即类型,最后封装成 UnMappedColumnAutoMapping 对象。
那我们来看看 getUnmappedColumnNames 方法源码。
org.apache.ibatis.executor.resultset.ResultSetWrapper#getUnmappedColumnNames
public List getUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {List unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));if (unMappedColumnNames == null) {// 加载已映射与未映射列名loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);// 获取未映射列名unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));}return unMappedColumnNames;
}
org.apache.ibatis.executor.resultset.ResultSetWrapper#loadMappedAndUnmappedColumnNames
private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {List mappedColumnNames = new ArrayList();List unmappedColumnNames = new ArrayList();final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);// 获取 中配置的所有列名final Set mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);/** 遍历 columnNames,columnNames 是 ResultSetWrapper 的成员变量,保存了当前结果集中的所有列名* 这里是通过ResultSet中的所有列名来获取没有在resultMap中配置的列名* 意思是后面进行自动赋值时,只赋值查出来的列名*/for (String columnName : columnNames) {final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);// 检测已映射列名集合中是否包含当前列名if (mappedColumns.contains(upperColumnName)) {mappedColumnNames.add(upperColumnName);} else {// 将列名存入 unmappedColumnNames 中unmappedColumnNames.add(columnName);}}// 缓存列名集合mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}
获取未配置 resultMap 的映射流程很简单,就是先获取 resultMap 配置的映射信息,然后循环需要映射结果集对象的所有属性,如果属性在 resultMap 中则表示配置了映射信息放入 mappedColumnNamesMap 中,反正放入 unMappedColumnNamesMap ,最后将 unMappedColumnNamesMap 返回就是我们要的未配置 resultMap 映射信息的集合了。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap)
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#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;// 获取 ResultMapping集合final List propertyMappings = resultMap.getPropertyResultMappings();// 所有的ResultMapping遍历进行映射for (ResultMapping propertyMapping : propertyMappings) {String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);if (propertyMapping.getNestedResultMapId() != null) {// the user added a column attribute to a nested result map, ignore itcolumn = null;}if (propertyMapping.isCompositeResult()|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))|| propertyMapping.getResultSet() != null) {// 从结果集中获取指定列的数据Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);// issue #541 make property optionalfinal String property = propertyMapping.getProperty();if (property == null) {continue;// 若获取到的值为 DEFERED,则延迟加载该值} else if (value == DEFERED) {foundValues = true;continue;}if (value != null) {foundValues = true;}if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {// gcode issue #377, call setter on nulls (value is not 'found')// 将获取到的值设置到实体类对象中metaObject.setValue(property, value);}}}return foundValues;
}
该方法的处理就是遍历 resultMap 配置的映射信息,挨个获取其对应的值,该值可能是关联查询结果也可能是普通结果,最后将获取到的值设置到映射结果对象中。
那接下来看看,这个 value 是如何获取的,方法源码。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getPropertyMappingValue
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)throws SQLException {if (propertyMapping.getNestedQueryId() != null) {// 获取关联查询结果return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);} else if (propertyMapping.getResultSet() != null) {addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?return DEFERED;} else {final TypeHandler> typeHandler = propertyMapping.getTypeHandler();final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);// 从 ResultSet 中获取指定列的值return typeHandler.getResult(rs, column);}
}
这里和自动映射有一点不同,自动映射是从直接从ResultSet 中获取指定列的值,但是通过 ResultMap 多了一种情况,那就是关联查询,也可以说是延迟查询,此关联查询如果没有配置延迟加载,那么就要获取关联查询的值,如果配置了延迟加载,则返回DEFERED。
至此,我们的结果集映射就已经分析完成了。
好了,今天的内容到这里就结束了,我是 【J3】关注我,我们下期见
。
由于博主才疏学浅,难免会有纰漏,假如你发现了错误或偏见的地方,还望留言给我指出来,我会对其加以修正。
如果你觉得文章还不错,你的转发、分享、点赞、留言就是对我最大的鼓励。
感谢您的阅读,十分欢迎并感谢您的关注。