Mybatis 官网 以及 本系列文章地址:
书接上文 Mybatis 源码 ③ :SqlSession
。我们这里来看下 DefaultParameterHandler 和 DefaultResultSetHandler 的处理过程。
DefaultParameterHandler 类图如下,可以看到其实现了 ParameterHandler 接口,我们可以通过 Plugin 的方式对 ParameterHandler 进行增强。这里我们主要来看 DefaultParameterHandler 的具体作用。
在 SimpleExecutor 和 BaseExecutor doUpdate、doQuery、doQueryCursor 等方法中会调用 prepareStatement 方法,在其中会调用 StatementHandler#parameterize 来对参数做预处理,里面会调用 PreparedStatementHandler#parameterize,该方法如下:
@Override
public void parameterize(Statement statement) throws SQLException {
// 这里会调用 DefaultParameterHandler#setParameters
parameterHandler.setParameters((PreparedStatement) statement);
}
因此我们可以知道,在Sql 执行前,会调用 DefaultParameterHandler#setParameters 方法来对参数做处理,这也就给了 TypeHandler 的参数转换提供了条件。
DefaultParameterHandler#setParameters 实现如下:
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 获取当前Sql执行时的参数
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
// 对一些额外参数处理
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// 判断是否有合适的类型转换器,可以解析当前参数
// 这里个人理解是为了判断参数是否是单独参数,
value = parameterObject;
} else {
// 根据 参数名去获取参数传入的值。
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 如果当前参数指定了类型转换器, 则通过类型转换器进行转换。否则交由 UnknownTypeHandler
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 调用类型转换器进行处理, 默认情况下是 UnknownTypeHandler
// jdbcType 是我们通过 jdbcType 属性指定的类型,没有指定则为空
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
上面可以看到逻辑比较简单:遍历所有参数,并且参数值交由 typeHandler.setParameter
来处理。需要注意的是这里的 typeHandler 如果没有指定默认是 UnknownTypeHandler。在UnknownTypeHandler 中则会根据参数实际类型来从注册的 TypeHandler 中选择合适的处理器来处理。下面我们具体来看。
UnknownTypeHandler#setParameter 会调用 UnknownTypeHandler#setNonNullParameter, 我们以该方法为例,UnknownTypeHandler 的其他方法也类似。
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
// 根据参数类型来获取 类型处理器
// jdbcType 是我们通过 jdbcType 属性指定的类型,没有指定则为空
TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
// 调用类型处理器处理
handler.setParameter(ps, i, parameter, jdbcType);
}
private TypeHandler<?> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
TypeHandler<?> handler;
// 参数为空直接返回 ObjectTypeHandler
if (parameter == null) {
handler = OBJECT_TYPE_HANDLER;
} else {
// 从注册的 TypeHandler 中根据类型选择合适的处理器
handler = typeHandlerRegistrySupplier.get().getTypeHandler(parameter.getClass(), jdbcType);
// check if handler is null (issue #270)
// 如果没找到返回 ObjectTypeHandler
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
}
return handler;
}
这里可以看到, 在执行Sql前会通过 DefaultParameterHandler#setParameters 对参数做一次处理。
我们可以自定义 TypeHandler 来实现指定字段的特殊处理,如用户密码在数据库中不能明文展示,而在代码中我们明文处理,则就可以通过如下方式定义:
public class PwdTypeHandler extends BaseTypeHandler<String> {
// 定义加解密方式
private static final SymmetricCrypto AES = new SymmetricCrypto(
SymmetricAlgorithm.AES, "1234567890123456".getBytes());
// 赋值时加密
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, AES.encryptBase64(parameter));
}
// 取值时解密
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return AES.decryptStr(rs.getString(columnName));
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return AES.decryptStr(rs.getString(columnIndex));
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return AES.decryptStr(cs.getString(columnIndex));
}
}
<resultMap id="BaseResultMap" type="com.kingfish.entity.SysUser">
<result property="id" column="id" jdbcType="INTEGER"/>
<result property="userName" column="user_name" jdbcType="VARCHAR"/>
<result property="password" column="password" jdbcType="VARCHAR" typeHandler="com.kingfish.config.handler.PwdTypeHandler"/>
resultMap>
DefaultResultSetHandler实现了ResultSetHandler 接口,ResultSetHandler 见名知意,即为结果集合处理器。所以下面我们来看看该方法的具体逻辑 :
@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;
// 获取第一个结果集 ResultSet 并包装成 ResultSetWrapper
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
// ResultMap 的数量, 当使用存储过程时,可能会有多个,我们这里不考虑存储过程的多个场景。
int resultMapCount = resultMaps.size();
// ResultMap 数量校验 :rsw != null && resultMapCount < 1
validateResultMapsCount(rsw, resultMapCount);、
/**********************************************************************/
// 1.对 ResultMap 的处理
// 循环所有的 ResultMap
while (rsw != null && resultMapCount > resultSetCount) {
// 获取当前 ResultMap
ResultMap resultMap = resultMaps.get(resultSetCount);
// 1.1 根据ResultMap中定义的映射规则处理ResultSet,并将映射得到的Java对象添加到 multipleResults集合中保存
handleResultSet(rsw, resultMap, multipleResults, null);
// 1.2 获取下一个 ResultSet
rsw = getNextResultSet(stmt);
// 1.3 清理nestedResultObjects集合,这个集合是用来存储中间数据的
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
/**********************************************************************/
// 2. 对 ResultSets 的处理
// 对 resultSet 处理,
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
// 处理reusltSet
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);
}
// 获取下一个 ResultSet
rsw = getNextResultSet(stmt);
// 清理nestedResultObjects集合,这个集合是用来存储中间数据的
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
/**********************************************************************/
// 返回结果集
return collapseSingleResultList(multipleResults);
}
这里可以看到, DefaultResultSetHandler#handleResultSet 方法的逻辑分为对 ResultMap 的处理和 对 ResultSets 的处理,在涉及存储过程的情况下会返回 ResultSets ,该部分不在本文的讨论范围内,在 Mybatis 官方文档 中对该属性做了具体的描述 : 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。具体使用如下图:
业务使用方面可以详参: https://blog.csdn.net/qq_40233503/article/details/94436578
本文主要看对 ResultMap 的处理内容,而其中最主要的则是 DefaultResultSetHandler#handleResultSet 方法,具体实现如下:
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
// 父级 mapper 不为空的情况 :在处理 ResultSet 时会出现,不在本文讨论范围
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
// 1. 未指定 ResultHandler 情况 : 如果 resultHandler 为空则创建一个 DefaultResultHandler 作为默认处理器
// 这里的 resultHandler 是我们调用 Mapper Interface Method 方法时指定的。如果没指定则为空
if (resultHandler == null) {
// 如果没指定则使用默认的 DefaultResultHandler 来处理结果
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
// 2. 指定了 ResultHandler 情况 : 将 resultHandler 传入作为结果处理器
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
上面可以看到这里针对了 未指定 ResultHandler 情况 和 指定了 ResultHandler 情况做了判断:我们可以在 Mapper Interface Method 入参中传入 ResultHandler 来对返回结果集做处理。(也可传入 RowBounds 对返回结果集做逻辑分页,但是需要注意 RowBounds 仅是逻辑分页,数据已经查出,所以不建议使用),通过实现ResultHandler 接口来对该查询的结果进行定制化解析(需要注意方法不能有返回值,因为返回值已经交由 resultHandler 来处理了),当 Mybatis 将结果查询出后会交由 resultHandler#handleResult 方法来处理。在方法入参中传入 ResultHandler 实例,并且返回值为 void,如下指定了 selectByParam 方法查询的结果交由 ResultHandler 来处理:
void selectByParam(ResultHandler resultHandler);
而实际上无论 ResultHandler 指定与否,都会调用 DefaultResultSetHandler#handleRowValues 方法来解析行数据,所以我们来看看该方法的具体实现,如下:
// 处理行数据 : 该方法会获取并解析出来每一行的数据
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 1. 如果有嵌套的 ResultMap,即 ResultMap#hasNestedResultMaps = true
if (resultMap.hasNestedResultMaps()) {
// 嵌套前的判断1 :嵌套情况下,如果 safeRowBoundsEnabled 为true,则不能使用 RowBounds (确切的说只能使用 默认的 RowBounds )
// safeRowBoundsEnabled 可以通过 {mybatis.configuration.safe-row-bounds-enabled} 配置,代表 允许在嵌套语句中使用分页(RowBounds) , 默认 true
ensureNoRowBounds();
// 嵌套前的判断2 :嵌套情况下,如果 safeResultHandlerEnabled 为 true && 语句属性 resultOrdered 为 true 则抛出异常
// safeResultHandlerEnabled 可以通过 {mybatis.configuration.safe-result-handler-enabled} 配置,代表 允许在嵌套语句中使用分页(ResultHandler)。默认 true
checkResultHandler();
// 2. 处理嵌套 ResultMap
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 3. 无嵌套 ResultMap的 简单逻辑
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
这里可以看到,对于行数据的处理分为嵌套情况和非嵌套情况,如下 :
DefaultResultSetHandler#hasNestedResultMaps :通过 ResultMap#hasNestedResultMaps 属性判断当前是否是嵌套结果集,成立条件是
标签中使用了子标签
、
,并且标签没有指定 select 属性 或使用了
标签。(如果指定了select属性,则会保存在 ResultMapping#nestedQueryId 指定 查询id)。 该属性在于 ResultMap.Builder#build 中会初始化,如下:
DefaultResultSetHandler#handleRowValuesForNestedResultMap :用来处理嵌套结果集的情况,即如果上面的判断成立了,则执行该逻辑。
DefaultResultSetHandler#handleRowValuesForSimpleResultMap :用来处理简单查询的情况,无嵌结果集的情况。
下面我们详细来看上面的详细逻辑
DefaultResultSetHandler#hasNestedResultMaps 方法的作用是判断当前 ResultMap 是否是嵌套结果集,其判断依据是 ResultMap#hasNestedResultMaps = true
,如下:
public boolean hasNestedResultMaps() {
return hasNestedResultMaps;
}
而 ResultMap#hasNestedResultMaps 属性的初始化是在ResultMap.Builder#build 中完成,如下:
这里我们关注两个属性:ResultMap#hasNestedQueries (标记当前 ResultMap 是否有嵌套映射,判断依据是 ResultMapping#nestedQueryId != null
)和 ResultMap#hasNestedResultMaps (标记当前 ResultMap 是否有嵌套结果集,判断依据是 ResultMapping#nestedResultMapId != null || ResultMapping#resultSet != null
)
我们以 XML 解析为例,在 XMLMapperBuilder#buildResultMappingFromContext中,会通过如下逻辑来解析取 nestedSelect、nestedResultMap 属性 :
并且在 MapperBuilderAssistant#buildResultMapping 方法中根据 nestedSelect、nestedResultMap 来给 ResultMapping#nestedQueryId 和 ResultMapping#nestedResultMapId 赋值,如下:
综上,这里的嵌套判断成立的条件是 :
标签中使用了子标签
、
,并且标签没有指定 select 属性 或使用了
标签。(如果指定了select属性,则会保存在 ResultMapping#nestedQueryId 指定 查询id)。下面我们来简单介绍下这两种情况的区别。
对于嵌套映射,其存在两种实现方式:
我们以如下两个表为例:
CREATE TABLE `sys_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
`user_name` varchar(255) DEFAULT NULL COMMENT '用户名',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`role_id` bigint(20) DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
CREATE TABLE `sys_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
`role_name` varchar(255) DEFAULT NULL COMMENT '用户名',
`status` varchar(255) DEFAULT NULL COMMENT '状态',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
内部嵌套实现如下:
<resultMap id="UserBaseResultMap" type="com.kingfish.entity.SysUser">
<result property="id" column="id" jdbcType="INTEGER"/>
<result property="userName" column="user_name" jdbcType="VARCHAR"/>
<result property="password" column="password" jdbcType="VARCHAR" />
resultMap>
<resultMap id="BaseResultMap" type="com.kingfish.entity.SysRole">
<result property="id" column="id" jdbcType="INTEGER"/>
<result property="roleName" column="role_name" jdbcType="VARCHAR"/>
<result property="status" column="status" jdbcType="VARCHAR"/>
resultMap>
<resultMap id="InnerNestMap" type="com.kingfish.entity.dto.SysRoleDto" extends="BaseResultMap">
<collection property="sysUsers" columnPrefix="user_"
resultMap="UserBaseResultMap">collection>
resultMap>
<select id="selectRoleUser" resultMap="InnerNestMap">
select sr.*, su.id user_id, su.user_name user_user_name, su.password user_password
from sys_role sr
left join sys_user su on sr.id = su.role_id
select>
外部嵌套实现如下:
<resultMap id="UserBaseResultMap" type="com.kingfish.entity.SysUser">
<result property="id" column="id" jdbcType="INTEGER"/>
<result property="userName" column="user_name" jdbcType="VARCHAR"/>
<result property="password" column="password" jdbcType="VARCHAR" />
resultMap>
<resultMap id="OutNestMap" type="com.kingfish.entity.dto.SysRoleDto" extends="BaseResultMap">
<collection property="sysUsers" ofType="com.kingfish.entity.dto.SysUserDto"
select="selectUser" column="{roleId=id}" >collection>
resultMap>
<select id="selectUser" resultMap="UserBaseResultMap">
select
id, user_name, password
from sys_user
where role_id = #{roleId}
select>
<select id="selectRole" resultMap="OutNestMap">
select *
from sys_role
select>
关于该部分内容本文只做简单介绍,如有需要可详参:https://www.cnblogs.com/sanzao/p/11466496.html#_label1
上面我们介绍了嵌套条件成立的条件,当满足了上述条件后,说明了当前查询存在嵌套结果集,则调用 DefaultResultSetHandler#handleRowValuesForNestedResultMap 来处理,具体如下
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
// 跳过执行行数据,由 RowBounds.offset 属性决定
skipRows(resultSet, rowBounds);
Object rowValue = previousRowValue;
// 确定当前剩余数据满足条件,即此次拉取的数据量 < RowBounds.limmit 时 且 连接未关闭 且后续还有结果集,则再次获取
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 1. 解析 discriminator 属性,获取 discriminator 指定的 ResultMap
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 2. 创建当前行记录的 缓存 key
final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
// 尝试获取该行记录的缓存
Object partialObject = nestedResultObjects.get(rowKey);
// issue #577 && #542
// resultOrdered = true 时
if (mappedStatement.isResultOrdered()) {
// 如果未缓存安全数据
if (partialObject == null && rowValue != null) {
// 清空缓存
nestedResultObjects.clear();
// 存储数据
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
// 3. 获取行数据
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
} else {
// 3. 获取行数据
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
// 4. 存储数据 : partialObject == null 说明数据没有被缓存
if (partialObject == null) {
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
}
// 行数据不为空 && resultOrdered = true && 还需要查询更多行
if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
// 存储数据
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
previousRowValue = null;
} else if (rowValue != null) {
previousRowValue = rowValue;
}
}
这里我们可以看到 :
标签的解析,并将
解析后的ResultMap 作为最终的 ResultMap 处理。下面我们会详细讲。该方法的作用是为了解析
标签, 内容比较简单,这里不在过多赘述。关于
标签的用法,如有需要详参 Mybatis 源码 ∞ :杂七杂八
public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException {
Set<String> pastDiscriminators = new HashSet<>();
Discriminator discriminator = resultMap.getDiscriminator();
while (discriminator != null) {
// 获取 discriminator 指定的 column 的值
final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
// 根据 column 的值来判断执行哪个 case 分支 : 根据 value 获取到 discriminatedMapId ,如果获取到则说明有对应的 case 分支
final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
// 如果存在该 ResultMap
if (configuration.hasResultMap(discriminatedMapId)) {
// 用 discriminator 指定 ResultMap 替换现有的 resultMap
resultMap = configuration.getResultMap(discriminatedMapId);
Discriminator lastDiscriminator = discriminator;
discriminator = resultMap.getDiscriminator();
if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
break;
}
} else {
break;
}
}
return resultMap;
}
createRowKey 方法 作用是创建当前行的缓存Key。具体实现如下:
// 生成行数据的缓存Key,这里会将列名和列值都作为关键值创建Key
// 在嵌套映射中会作为唯一标志标识一个结果对象
private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
final CacheKey cacheKey = new CacheKey();
// 使用映射结果集的id 作为 CacheKey 的一部分
cacheKey.update(resultMap.getId());
// 获取 标签结果集
List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
// 为空则判断返回类型是是不是Map
if (resultMappings.isEmpty()) {
if (Map.class.isAssignableFrom(resultMap.getType())) {
// 由结果集中的所有列名以及当前记录行的所有列值一起构成CacheKey
createRowKeyForMap(rsw, cacheKey);
} else {
// 由结果集中未映射的列名以及它们在当前记录行中的对应列值一起构成CacheKey对象
createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
}
} else {
// 由ResultMapping集合中的列名以及它们在当前记录行中相应的列值一起构成CacheKey
createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
}
// 如果除了映射结果集的id 之外没有任何属性参与生成CacheKey 则返回NULL_CACHE_KEY
if (cacheKey.getUpdateCount() < 2) {
return CacheKey.NULL_CACHE_KEY;
}
return cacheKey;
}
这里我们不再具体分析具体的代码内容,直接总结具体的逻辑(下面内容来源 Mybatis源码阅读(三):结果集映射3.2 —— 嵌套映射):
额外需要注意的是 ,CacheKey 创建后,会尝试从 nestedResultObjects 中获取对象对数据。如下:
Object partialObject = nestedResultObjects.get(rowKey);
nestedResultObjects 的作用是缓存所有查询出的结果数据,但是这里会存在问题:在嵌套映射时如果存在两行完全一样的数据,则会被忽略。该问题我们在 Mybatis 源码 ∞ :杂七杂八 进行了详细说明
getRowValue 方法是处理每一行的值,需要注意的是这里的 handleRowValuesForNestedResultMap 中调用的 getRowValue 方法和 handleRowValuesForSimpleResultMap 中调用的 getRowValue 方法是重载方法。
下面我们来具体看 handleRowValuesForNestedResultMap 中调用的 getRowValue 如下:
// DefaultResultSetHandler#getRowValue(ResultSetWrapper, ResultMap, .CacheKey, String, Object)
// 将数据库查出来的数据转换为 Mapper Interface Method 返回的类型
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
// 获取 ResultMap 的唯一ID
final String resultMapId = resultMap.getId();
// 外层数据赋值给 rowValue
Object rowValue = partialObject;
// 如果缓存有值,则认为是嵌套映射
if (rowValue != null) {
// 用外层数据生成元数据 metaObject
final MetaObject metaObject = configuration.newMetaObject(rowValue);
// 外层数据保存到 ancestorObjects 中
putAncestor(rowValue, resultMapId);
// 处理嵌套逻辑
applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
// 从 ancestorObjects 中移除该数据
ancestorObjects.remove(resultMapId);
} else {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 1. 反射 Mapper Interface Method 返回的类型对象,这里尚未填充行数据
rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
// rowValue 不为空 && 没有针对 rowValue 类型的 TypeHandler
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 如果允许自动映射(可通过 标签的 autoMapping 属性指定)
if (shouldApplyAutomaticMappings(resultMap, true)) {
// 2. 根据自动映射规则尝试映射,看是行数据是否能映射到对应的属性 (忽略大小写的映射)
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 3. 根据属性映射
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
putAncestor(rowValue, resultMapId);
// 4. 对嵌套结果集进行映射
foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
ancestorObjects.remove(resultMapId);
foundValues = lazyLoader.size() > 0 || foundValues;
// 如果 映射到了属性值 或者 配置了空数据返回实体类 (mybatis.configuration.return-instance-for-empty-row 属性指定)则 返回 rowValue, 否则返回空
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
// 缓存外层对象
if (combinedKey != CacheKey.NULL_CACHE_KEY) {
nestedResultObjects.put(combinedKey, rowValue);
}
}
return rowValue;
}
上面我们我们主要看下面几个方法:
createResultObject : 这里会创建Mapper Interface Method 返回的类型对象,但是并没有对各个属性赋值。不过需要注意 createResultObject 方法创建返回对象时分为下面集中情况:
applyAutomaticMappings :如果开启了自动映射则会按照自动映射的规则(忽略属性大小写差异)进行属性映射
applyPropertyMappings :根据规则对剩余属性进行映射
applyNestedResultMappings : 处理嵌套映射的属性。
下面我们详细来看上面的几个方法的具体实现:
createResultObject 实现如下:
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<>();
final List<Object> constructorArgs = new ArrayList<>();
// 根据 ResultMap 的属性通过反射方式创建一个对象(如果通过 指定了构造参数 则注入构造参数)
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
// 对象不为空且没有对应的类型处理器
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
// 遍历所有属性
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149
// 如果是嵌套结果集 && 并且开启了懒加载,则这里创建一个代理对象,等实际调用时才会触发获取逻辑
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
// 标注是否使用了构造映射
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
如果开启了自动映射则会按照自动映射的规则(忽略属性大小写差异)进行属性映射
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
// 获取开启自动映射的结果集
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)) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
这里是对剩下的属性进行映射,在上面我们提到过嵌套映射存在内部嵌套和外部嵌套两种情况。这里则会对外部嵌套的情况做处理。具体如下:
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
// 获取使用 columnPrefix拼接后的列名
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
// 获取 ResultMap 的 reuslt 属性
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
// 遍历所有属性
for (ResultMapping propertyMapping : propertyMappings) {
// 获取拼接 columnPrefix 后 属性列名
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
// 如果当前属性存在嵌套的 ResultMap 则忽略该列,交由下面进行嵌套解析
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
// 如果当前查询有复合结果(嵌套映射时,可能出现一对一、一对多的情况) || 当前列匹配(property 与 column经过转换后一致) || 当前属性指定了 ResultSet
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 optional
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
// value 不为空 || (配置 {mybatis.configuration.call-setters-on-nulls} 为 true && set 方法不为私有)
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;
}
可以看到上面的关键方法在于下面 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 DEFERRED;
} else {
// 最基础的解析使用指定的 TypeHandler 解析数据并返回。如 Long 使用 LongTypeHandler等
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
// 拼接前缀:即 prefix + columnName
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
// 获取处理结果并返回
return typeHandler.getResult(rs, column);
}
}
上面我们可以看到,这里分成三种情况
下面我们来看看 getNestedQueryMappingValue 嵌套解析的过程:
// 获取嵌套查询的结果集映射
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
// 获取嵌套映射id 即 select属性指定的查询语句
final String nestedQueryId = propertyMapping.getNestedQueryId();
final String property = propertyMapping.getProperty();
// 获取嵌套映射指定的语句
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) {
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
final Class<?> targetType = propertyMapping.getJavaType();
// 如果结果已经被缓存
if (executor.isCached(nestedQuery, key)) {
// 延迟加载
executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
value = DEFERRED;
} else {
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
// 如果是懒加载则加载到 lazyLoader中并返回推迟加载对象
if (propertyMapping.isLazy()) {
lazyLoader.addLoader(property, metaResultObject, resultLoader);
value = DEFERRED;
} else {
// 加载结果
value = resultLoader.loadResult();
}
}
}
return value;
}
applyNestedResultMappings 则是针对内部嵌套进行处理,如下:
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 {
// 获取拼接 parentPrefix 后的列名 :我们可以通过 的 columnPrefix 属性指定前缀,这里会进行前缀拼接
final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
// 1. 获取嵌套映射对应的结果集
final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
// 如果列前缀为空:一般情况下如果使用嵌套映射则会声明前缀
if (resultMapping.getColumnPrefix() == null) {
// try to fill circular reference only when columnPrefix
// is not specified for the nested result map (issue #215)
// 尝试获取祖先对象
Object ancestorObject = ancestorObjects.get(nestedResultMapId);
if (ancestorObject != null) {
// 如果是新对象,则进行链接 : 当第一次处理当前嵌套映射时认为是新对象,可以简单认为没有放入 nestedResultObjects 缓存
if (newObject) {
// 链接对象
linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
}
continue;
}
}
// 创建行的key
final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
// 与父级 key 进行组合:
final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
// 从缓存中获取该行对象
Object rowValue = nestedResultObjects.get(combinedKey);
boolean knownValue = rowValue != null;
// 如果对象是集合类型,则判断是否需要初始化,需要则创建爱你
instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
// 据notNullColumn属性, 检测是否有非空属性,如果全为空则没必要解析
if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
// 获取映射结果
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;
}
// 链接对象
private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
// 必要的话初始化集合对象
final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
// 如果集合对象不为空,则添加到集合对象中
if (collectionProperty != null) {
final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
targetMetaObject.add(rowValue);
} else {
// 否则的话保存属性到元数据中
metaObject.setValue(resultMapping.getProperty(), rowValue);
}
}
这里需要注意的是由于 Mybatis 的 RowKey 是属性名 + 属性值拼接,在嵌套时如果两行数据完全一致,则第一行数据会被缓存,当处理第二行数据时,会被缓存命中从而不满足 rowValue != null && !knownValue
的判断条件,导致数据丢失。
storeObject 方法将数据保存起来, 具体实现如下:
private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
// 如果父 ResultMap 存在 (嵌套模式),则链接到 父 ResultMap 中
if (parentMapping != null) {
linkToParents(rs, parentMapping, rowValue);
} else {
// 回调 resultHandler 来处理结果
callResultHandler(resultHandler, resultContext, rowValue);
}
}
private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException {
// 获取到父ResultMapping 中该属性的缓存key
CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getForeignColumn());
// 获取缓存的对象
List<PendingRelation> parents = pendingRelations.get(parentKey);
if (parents != null) {
for (PendingRelation parent : parents) {
if (parent != null && rowValue != null) {
// 将当前对象注入到父级
linkObjects(parent.metaObject, parent.propertyMapping, rowValue);
}
}
}
}
private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
resultContext.nextResultObject(rowValue);
// 调用ResultHandler#handleResult来处理结果,默认情况是 DefaultResultHandler,将结果保存到 DefaultResultHandler#list 中
((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}
该方法用来解析非嵌套映射情况,具体实现如下:
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
// 跳过执行行数据,由 RowBounds.offset 属性决定
skipRows(resultSet, rowBounds);
// 确定当前剩余数据满足条件,即此次拉取的数据量 < RowBounds.limmit 时 且 连接未关闭 且后续还有结果集,则再次获取
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 1. 解析 discriminator 属性
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 2. 获取行数据
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// 3. 保存映射后的数据
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
// 跳过指定的行数
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
rs.absolute(rowBounds.getOffset());
}
} else {
for (int i = 0; i < rowBounds.getOffset(); i++) {
if (!rs.next()) {
break;
}
}
}
}
// 是否应该获取更多列
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}
这里我们可以看到 :
利用 RowBounds 是可以实现分页的功能的,但却是一个逻辑分页,因为所有数据都是已经加载到内存后再根据 RowBounds 的分页限制选择是否丢弃或继续获取,因此并不建议使用。
resolveDiscriminatedResultMap 方法实现了对
标签的解析,并将
解析后的ResultMap 作为最终的 ResultMap 处理,上面已经介绍,不再赘述。
getRowValue 方法会根据 resultMap 解析并获取当前的行数据, 这个跟上面不同是个重载方法,如下:
// 将数据库查出来的数据转换为 Mapper Interface Method 返回的类型
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 1. 反射 Mapper Interface Method 返回的类型对象,这里尚未填充行数据
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
// rowValue 不为空 && 没有针对 rowValue 类型的 TypeHandler
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 如果允许自动映射(可通过 标签的 autoMapping 属性指定)
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 2. 根据自动映射规则尝试映射,看是行数据是否能映射到对应的属性 (忽略大小写的映射)
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 3. 根据属性映射
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
// 如果 映射到了属性值 或者 配置了空数据返回实体类 (mybatis.configuration.return-instance-for-empty-row 属性指定)则 返回 rowValue, 否则返回空
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
// 返回映射后的实体类
return rowValue;
}
storeObject 方法会将处理后的行结果缓存起来。上面已经介绍,这里不再赘述。
至此整个解析过程已经结束。
以上:内容部分参考
https://www.jianshu.com/p/cdb309e2a209
https://zhuanlan.zhihu.com/p/526147349
https://blog.csdn.net/qq_40233503/article/details/94436578
https://blog.csdn.net/weixin_42893085/article/details/105105958
https://blog.csdn.net/weixin_40240756/article/details/108889127
https://www.cnblogs.com/hongshaozi/p/14160328.html
https://www.jianshu.com/p/05f643f27246
https://www.cnblogs.com/sanzao/p/11466496.html
https://juejin.cn/post/6844904127823085581
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正