11_Mybatis 是如何进行DO类和数据库字段的映射的?
假设 VideoAbnormalContentMapper.xml
文件有如下方法:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxx.springboot.videotask.domain.mapper.VideoAbnormalContentMapper">
<resultMap id="BaseResultMap" type="com.xxx.springboot.videotask.domain.dataobject.VideoAbnormalContentDO">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="file_id" jdbcType="BIGINT" property="fileId"/>
<result column="video_name" jdbcType="VARCHAR" property="videoName"/>
<result column="video_type" jdbcType="VARCHAR" property="videoType"/>
<result column="model_type" jdbcType="VARCHAR" property="modelType"/>
<result column="task_status" jdbcType="INTEGER" property="taskStatus"/>
<result column="task_result" jdbcType="VARCHAR" property="taskResult"/>
<result column="is_breach" jdbcType="TINYINT" property="isBreach"/>
<result column="task_result_description" jdbcType="VARCHAR" property="taskResultDescription"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
</resultMap>
<select id="selectPageList" resultMap="BaseResultMap" parameterType="map">
select *
from video_abnormal_content
order by id desc limit #{offset}, #{limit}
</select>
</mapper>
VideoAbnormalContentMapper.java
有如下方法:
List<VideoAbnormalContentDO> selectPageList(@Param("offset") long offset,
@Param("limit") long limit);
VideoAbnormalContentDO
类如下:
public class VideoAbnormalContentDO extends BaseDO{
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private Long fileId;
private String videoName;
private String videoType;
private String modelType;
private Integer taskStatus;
@Schema(description = "是否违规")
private Boolean isBreach;
private String taskResult;
private String taskResultDescription;
}
数据库表如下:
字段名 | 类型 | 是否可空 | 默认值 | 注释 |
---|---|---|---|---|
id | bigint | NO | AUTO_INCREMENT | 主键ID |
file_id | bigint | NO | 文件id | |
video_name | varchar(255) | NO | 视频名 | |
video_type | varchar(64) | NO | 视频类型 | |
model_type | varchar(255) | NO | 模型类型 | |
task_status | tinyint(1) | NO | 0 | 任务状态(0-初始化,1-上传成功,2-上传失败,3-排队中,4-处理中,5-处理完成,6-处理失败,7-已过期,8-已取消) |
task_result | varchar(255) | NO | 审核结果 | |
is_breach | tinyint(1) | NO | 0 | 是否违规 |
task_result_description | varchar(255) | NO | 结果描述 | |
create_time | datetime | NO | CURRENT_TIMESTAMP | 创建时间 |
update_time | datetime | NO | CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
当我们调用
List<VideoAbnormalContent> list = videoAbnormalContentMapper.selectPageList(offset, limit)时,
MyBatis 会生成一个代理对象,最终会调用到 MapperMethod.execute()
。(无论是 Mybatis 自带方法,还是我们手写的方法,Mybatis都会生成代理对象)
这里会根据 SQL 类型(SELECT
/INSERT
/UPDATE
/DELETE
)调用对应的 SqlSession 方法:
public class MapperMethod {
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch (this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
// 如果 Mapper 方法返回类型是 void,且带有 ResultHandler,执行带结果处理器的查询
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
}
// 如果方法返回多个结果,比如返回 List
else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
}
// 如果方法返回 Map 类型,比如 @MapKey 注解标记的结果
else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
}
// 如果方法返回 Cursor 类型,支持游标逐条遍历
else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
}
// 默认情况,返回单条记录
else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
// 如果方法返回 Optional,包装结果
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + "' attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
显然,这里会调用
// 如果方法返回多个结果,比如返回 List
else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
}
然后进入 MapperMethod
的 executeForMany
方法:
public class MapperMethod {
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
// 将方法参数数组转换为 SQL 执行参数对象,可能是单个参数或多个参数封装的对象
Object param = this.method.convertArgsToSqlCommandParam(args);
List result;
// 判断 Mapper 方法是否声明了分页或偏移信息(RowBounds)
if (this.method.hasRowBounds()) {
// 从参数中提取 RowBounds 对象(offset + limit)
RowBounds rowBounds = this.method.extractRowBounds(args);
// 使用带 RowBounds 的 selectList 执行分页查询
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
} else {
// 普通查询,不带分页,直接调用 selectList
result = sqlSession.selectList(this.command.getName(), param);
}
// 判断查询结果 List 是否能直接赋值给 Mapper 方法的返回类型
// 例如:方法返回 List,查询结果是 ArrayList,直接返回即可
if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
// 如果方法返回的是数组,则转换 List 为数组
return this.method.getReturnType().isArray()
? this.convertToArray(result)
// 否则将 List 转为方法声明的具体集合类型(比如 Set、LinkedList)
: this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
} else {
// 如果类型兼容,直接返回 List 结果
return result;
}
}
}
这里会执行
else {
// 普通查询,不带分页,直接调用 selectList
result = sqlSession.selectList(this.command.getName(), param);
}
接着进入 DefaultSqlSession
的 selectList
方法:
public class DefaultSqlSession implements SqlSession {
/**
* 执行查询,返回多条记录,默认不分页(RowBounds.DEFAULT)
* @param statement Mapper 映射语句的唯一标识(namespace.id)
* @param parameter 查询参数,可以是单个对象或 Map
* @param <E> 返回结果类型
* @return 查询结果列表
*/
public <E> List<E> selectList(String statement, Object parameter) {
// 调用带 RowBounds 参数的重载方法,使用默认分页参数
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
/**
* 执行查询,返回多条记录,可以指定分页参数
* @param statement Mapper 映射语句的唯一标识
* @param parameter 查询参数
* @param rowBounds 分页参数,封装 offset 和 limit
* @param <E> 返回结果类型
* @return 查询结果列表
*/
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
// 调用带结果处理器的私有方法,默认不使用结果处理器
return this.selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
/**
* 真正执行查询的方法
* @param statement Mapper 映射语句的唯一标识
* @param parameter 查询参数
* @param rowBounds 分页参数
* @param handler 结果处理器,一般为 null 或 NO_RESULT_HANDLER
* @param <E> 返回结果类型
* @return 查询结果列表
*/
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
List<E> result;
try {
// 根据 statement 获取映射的 MappedStatement 对象,封装了 SQL、参数、映射等信息
MappedStatement ms = this.configuration.getMappedStatement(statement);
// 如果是“脏查询”,设置 dirty 标记(针对缓存相关)
this.dirty |= ms.isDirtySelect();
// 调用底层 Executor 执行查询
// wrapCollection 用于处理参数为集合或数组的情况
result = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
// 捕获异常,封装并抛出 MyBatis 异常
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
// 清理错误上下文,避免影响后续操作
ErrorContext.instance().reset();
}
return result;
}
}
这里会依次调用至
var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
接着进入 BaseExecutor
的 query
方法:
public abstract class BaseExecutor implements Executor {
/**
* 执行查询,外层入口方法
* @param ms MappedStatement,封装了SQL、参数映射等信息
* @param parameter 查询参数对象
* @param rowBounds 分页参数(offset + limit)
* @param resultHandler 结果处理器,通常为null
* @param <E> 返回结果类型
* @return 查询结果列表
* @throws SQLException SQL异常
*/
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取绑定的SQL语句和参数映射信息
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存键,基于MappedStatement、参数、分页、SQL语句
CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
// 传递缓存键和BoundSql进入真正的查询执行方法
return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
/**
* 真正执行查询的方法(带缓存和异常处理)
* @param ms MappedStatement
* @param parameter 查询参数
* @param rowBounds 分页参数
* @param resultHandler 结果处理器
* @param key 缓存键
* @param boundSql 绑定的SQL信息
* @param <E> 返回结果类型
* @return 查询结果列表
* @throws SQLException SQL异常
*/
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 记录错误上下文信息,方便异常时定位资源和SQL id
ErrorContext.instance()
.resource(ms.getResource())
.activity("executing a query")
.object(ms.getId());
// 检查执行器是否已经关闭,防止非法操作
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
// 如果是顶层调用且需要刷新缓存,清除本地缓存
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List<E> list;
try {
// 查询栈加1,防止递归时重复清理缓存
++this.queryStack;
// 从本地缓存中查找缓存的查询结果(如果没有结果处理器)
list = resultHandler == null ? (List<E>) this.localCache.getObject(key) : null;
// 如果缓存命中,处理输出参数(如存储过程的OUT参数)
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
}
// 缓存未命中,从数据库查询
else {
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 查询栈减1
--this.queryStack;
}
// 如果是最外层查询调用,执行延迟加载的属性加载,清空延迟加载队列
if (this.queryStack == 0) {
Iterator<DeferredLoad> iterator = this.deferredLoads.iterator();
while (iterator.hasNext()) {
DeferredLoad deferredLoad = iterator.next();
deferredLoad.load();
}
this.deferredLoads.clear();
// 如果本地缓存作用域是 STATEMENT,执行完毕后清除本地缓存
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
// 返回查询结果
return list;
}
}
}
这里会依次调用至
// 从本地缓存中查找缓存的查询结果(如果没有结果处理器)
list = resultHandler == null ? (List<E>) this.localCache.getObject(key) : null;
// 如果缓存命中,处理输出参数(如存储过程的OUT参数)
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
}
// 缓存未命中,从数据库查询
else {
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
先判断缓存中有没有,如果没有会调用:
// 缓存未命中,从数据库查询
else {
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
接着进入 BaseExecutor
的 queryFromDatabase
方法:
public abstract class BaseExecutor implements Executor {
/**
* 从数据库执行查询操作
* @param ms MappedStatement,包含SQL及映射信息
* @param parameter 查询参数对象
* @param rowBounds 分页参数
* @param resultHandler 结果处理器,通常为null
* @param key 缓存键
* @param boundSql 绑定的SQL信息
* @param <E> 返回结果类型
* @return 查询结果列表
* @throws SQLException SQL异常
*/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 在本地缓存中先放入一个执行占位符,防止循环引用或递归查询时重复执行
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List<E> list;
try {
// 执行真正的数据库查询(由子类实现),返回结果列表
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 查询结束后移除占位符,避免缓存污染
this.localCache.removeObject(key);
}
// 查询结果放入本地缓存,供后续相同查询复用
this.localCache.putObject(key, list);
// 如果是存储过程调用,缓存输出参数
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
// 返回查询结果
return list;
}
}
然后调用:
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
接着进入 SimpleExecutor
的 doQuery
方法:
public class SimpleExecutor extends BaseExecutor {
/**
* 通过 StatementHandler 执行数据库查询,获取结果列表
* @param ms MappedStatement,封装SQL及映射信息
* @param parameter 查询参数
* @param rowBounds 分页参数
* @param resultHandler 结果处理器,通常为 null
* @param boundSql 绑定的SQL信息
* @param <E> 返回结果类型
* @return 查询结果列表
* @throws SQLException SQL 异常
*/
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List<E> resultList;
try {
// 获取配置对象
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler(核心执行器,负责准备、执行SQL,封装参数和结果映射)
StatementHandler handler = configuration.newStatementHandler(
this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 准备 JDBC Statement 对象(包括设置参数、执行前准备等)
stmt = this.prepareStatement(handler, ms.getStatementLog());
// 执行查询,交给 StatementHandler 处理,并返回结果列表
resultList = handler.query(stmt, resultHandler);
} finally {
// 关闭 Statement,释放资源
this.closeStatement(stmt);
}
// 返回查询结果
return resultList;
}
}
然后调用:
var9 = handler.query(stmt, resultHandler);
接着进入 PreparedStatementHandler
的 query
方法:
public class PreparedStatementHandler extends BaseStatementHandler {
/**
* 使用 PreparedStatement 执行查询
* @param statement JDBC Statement 对象,实际是 PreparedStatement
* @param resultHandler 自定义结果处理器,通常为 null
* @param <E> 返回结果类型
* @return 查询结果列表
* @throws SQLException SQL 执行异常
*/
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 强制类型转换为 PreparedStatement,方便调用其特有方法
PreparedStatement ps = (PreparedStatement) statement;
// 执行 SQL 查询,返回结果集
ps.execute();
// 使用 ResultSetHandler 处理查询结果,转换成结果对象列表并返回
return this.resultSetHandler.handleResultSets(ps);
}
}
调用:
return this.resultSetHandler.handleResultSets(ps);
接着进入 DefaultResultSetHandler
的 handleResultSets
方法:
public class DefaultResultSetHandler implements ResultSetHandler {
/**
* 处理 JDBC Statement 返回的所有结果集,映射成 Java 对象列表。
* @param stmt 执行后的 JDBC Statement
* @return 多个结果集的映射结果列表,通常只有一个结果集则返回该集合
* @throws SQLException SQL 异常
*/
public List<Object> handleResultSets(Statement stmt) throws SQLException {
// 设置错误上下文信息,方便定位问题
ErrorContext.instance()
.activity("handling results")
.object(this.mappedStatement.getId());
// 存储所有结果集的映射结果
List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0; // 当前处理的结果集序号
// 获取第一个结果集的封装对象(ResultSetWrapper)
ResultSetWrapper rsw = this.getFirstResultSet(stmt);
// 获取当前 MappedStatement 定义的所有 ResultMap(可能有多个结果集对应多个 ResultMap)
List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size(); // ResultMap 数量
// 校验结果集数量与 ResultMap 数量是否匹配,不匹配会抛异常
this.validateResultMapsCount(rsw, resultMapCount);
// 遍历每个结果集,逐个映射成对象
while (rsw != null && resultMapCount > resultSetCount) {
// 取对应结果集的 ResultMap 映射配置
ResultMap resultMap = resultMaps.get(resultSetCount);
// 处理当前结果集,映射数据行为对象,结果加入 multipleResults
this.handleResultSet(rsw, resultMap, multipleResults, null);
// 获取下一个结果集(有些 SQL 语句可能返回多个结果集)
rsw = this.getNextResultSet(stmt);
// 清理本次结果集处理时的临时状态
this.cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 如果定义了命名的结果集(nestedResultMaps 支持),需要额外处理嵌套结果集
String[] resultSets = this.mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
// 获取当前结果集对应的父级映射关系
ResultMapping parentMapping = this.nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
// 根据嵌套 ResultMap ID 获取嵌套映射配置
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
// 处理嵌套结果集
this.handleResultSet(rsw, resultMap, null, parentMapping);
}
// 继续获取下一个结果集
rsw = this.getNextResultSet(stmt);
// 清理处理状态
this.cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
// 如果只有一个结果集,则返回该结果集的对象列表,否则返回多结果集的集合
return this.collapseSingleResultList(multipleResults);
}
}
这里调用:
this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
接着进入 DefaultResultSetHandler
的 handleResultSet
方法:
public class DefaultResultSetHandler implements ResultSetHandler {
/**
* 处理单个 ResultSet 的结果映射
*
* @param rsw 封装了 ResultSet 和元信息的包装类
* @param resultMap 映射规则,定义了如何将列映射到对象属性
* @param multipleResults 多结果集合,通常用于处理多结果集的情况
* @param parentMapping 父级映射,用于嵌套结果映射,平时为 null
* @throws SQLException SQL 异常
*/
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
// 1. 有父映射时,走嵌套结果映射逻辑
this.handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else if (this.resultHandler == null) {
// 2. resultHandler 为 null,使用默认的 DefaultResultHandler 收集结果
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(this.objectFactory);
this.handleRowValues(rsw, resultMap, defaultResultHandler, this.rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
// 3. 有外部传入的自定义 resultHandler,则用它处理结果
this.handleRowValues(rsw, resultMap, this.resultHandler, this.rowBounds, null);
}
} finally {
// 关闭当前 ResultSet,释放数据库资源
this.closeResultSet(rsw.getResultSet());
}
}
}
调用:
else if (this.resultHandler == null) {
// 2. resultHandler 为 null,使用默认的 DefaultResultHandler 收集结果
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(this.objectFactory);
this.handleRowValues(rsw, resultMap, defaultResultHandler, this.rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
}
接着进入 DefaultResultSetHandler
的 handleRowValues
方法:
public class DefaultResultSetHandler implements ResultSetHandler {
/**
* 处理 ResultSet 中的多行数据,将其映射成 Java 对象集合
*
* @param rsw ResultSet 的封装类,包含了 ResultSet 及其元信息
* @param resultMap 定义映射规则的 ResultMap,说明如何映射数据库列到对象属性
* @param resultHandler 结果处理器,用于收集或处理映射后的对象,通常为 DefaultResultHandler
* @param rowBounds 分页参数,控制跳过多少行及最大行数
* @param parentMapping 父级 ResultMapping,用于多级嵌套映射时传递关联关系,普通查询一般为 null
* @throws SQLException SQL 异常
*/
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) {
// 如果 ResultMap 包含嵌套映射(比如一对多、嵌套对象等)
// 不支持 RowBounds 分页,需抛异常或者跳过分页限制
this.ensureNoRowBounds();
// 检查自定义结果处理器是否支持嵌套映射
this.checkResultHandler();
// 进入嵌套结果集处理逻辑,递归解析嵌套的结果映射
this.handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 简单映射,即普通的单表字段映射,逐行将结果集映射成对象
this.handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
}
调用:
else {
// 简单映射,即普通的单表字段映射,逐行将结果集映射成对象
this.handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
接着进入 DefaultResultSetHandler
的 handleRowValuesForSimpleResultMap
方法:
public class DefaultResultSetHandler implements ResultSetHandler {
/**
* 处理简单的 ResultMap 映射,即不包含嵌套映射的单表映射。
* 逐行从 ResultSet 中读取数据,将每一行映射成对应的 Java 对象,
* 并通过 ResultHandler 进行结果收集或处理。
*
* @param rsw ResultSet 的包装类,包含了原始 ResultSet 和元数据信息
* @param resultMap 映射规则,定义了列与对象属性的对应关系
* @param resultHandler 结果处理器,负责收集映射后的结果对象
* @param rowBounds 分页参数,控制跳过的行数和最大处理的行数
* @param parentMapping 父级 ResultMapping,用于多级嵌套映射时传递上下文,简单映射时为 null
* @throws SQLException SQL 异常
*/
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 用于保存处理状态的上下文对象,记录当前处理到第几条记录等信息
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
// 获取底层 JDBC ResultSet
ResultSet resultSet = rsw.getResultSet();
// 跳过指定数量的行,支持分页功能
this.skipRows(resultSet, rowBounds);
// 遍历 ResultSet,逐行处理映射
while (this.shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 根据当前行判断是否使用某个子 ResultMap(支持 discriminator 判别器)
ResultMap discriminatedResultMap = this.resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 映射当前行数据为对应的 Java 对象
Object rowValue = this.getRowValue(rsw, discriminatedResultMap, null);
// 将映射得到的对象交给 ResultHandler 处理(通常是收集到结果列表中)
this.storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
}
调用:
Object rowValue = this.getRowValue(rsw, discriminatedResultMap, (String)null);
接着进入 DefaultResultSetHandler
的 getRowValue
方法:
public class DefaultResultSetHandler implements ResultSetHandler {
/**
* 将 ResultSet 中当前行的数据映射成对应的 Java 对象实例。
* 具体流程:
* 1. 先通过 createResultObject 创建目标对象(可能通过构造函数或无参构造创建)
* 2. 判断是否有类型处理器直接处理该对象类型
* 3. 如果没有类型处理器,则通过 MetaObject 反射对象属性,进行属性赋值映射
* 4. 支持自动映射未显式配置的列(applyAutomaticMappings)
* 5. 映射配置中显式的属性(applyPropertyMappings)
* 6. 支持延迟加载(lazyLoader),如果有延迟加载属性,会保存加载器信息
* 7. 如果整行数据都为空且配置不允许空实例返回,则返回 null
*
* @param rsw ResultSet 的包装类,包含元信息和原始 ResultSet
* @param resultMap 映射规则,列到属性的映射
* @param columnPrefix 列名前缀,用于处理嵌套结果集的列名映射,普通映射传 null
* @return 映射后的 Java 对象实例,或 null(当数据为空且配置不返回空实例时)
* @throws SQLException SQL 异常
*/
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
// 用于延迟加载的映射存储
ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 创建目标对象实例(调用构造函数或无参构造)
Object rowValue = this.createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
// 如果对象创建成功,且该对象类型没有对应的 TypeHandler(简单类型处理器)
if (rowValue != null && !this.hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 创建 MetaObject,方便反射操作对象属性
MetaObject metaObject = this.configuration.newMetaObject(rowValue);
// 标记是否找到有效的列数据
boolean foundValues = this.useConstructorMappings;
// 是否开启自动映射功能(自动映射数据库列和对象属性)
if (this.shouldApplyAutomaticMappings(resultMap, false)) {
// 自动映射未明确配置的列,返回是否找到数据
foundValues = this.applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 映射所有显式配置的属性映射(对应<result>等标签)
foundValues = this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
// 如果有延迟加载属性,则视为找到数据
foundValues = lazyLoader.size() > 0 || foundValues;
// 如果未找到任何有效数据,且配置不允许返回空对象,则返回 null
rowValue = !foundValues && !this.configuration.isReturnInstanceForEmptyRow() ? null : rowValue;
}
return rowValue;
}
}
调用:
Object rowValue = this.createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
接着进入 DefaultResultSetHandler
的 createResultObject
方法:
public class DefaultResultSetHandler implements ResultSetHandler {
/**
* 创建结果对象(映射结果行对应的 Java 实例)
*
* 1. 调用重载的 createResultObject 方法,尝试通过构造函数或无参构造创建对象
* 2. 如果对象创建成功且该类型没有对应的 TypeHandler(即不是简单类型)
* - 遍历映射的属性映射列表
* - 检查是否存在延迟加载的嵌套查询属性(nestedQueryId 不为空且懒加载开启)
* - 如果存在延迟加载,则用代理方式包装该对象,支持延迟加载
* 3. 标记是否使用了构造函数注入(useConstructorMappings)
* 4. 返回创建好的对象
*
* @param rsw ResultSet 包装对象,包含元数据和结果集
* @param resultMap 映射规则,定义如何映射列到对象属性
* @param lazyLoader 延迟加载的映射集合,用于支持延迟加载属性
* @param columnPrefix 列名前缀,处理嵌套映射时使用,普通映射传 null
* @return 映射后的结果对象实例,可能是代理对象
* @throws SQLException SQL 异常
*/
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
// 初始化构造函数参数类型列表和参数值列表,用于后续构造函数注入
this.useConstructorMappings = false;
List<Class<?>> constructorArgTypes = new ArrayList<>();
List<Object> constructorArgs = new ArrayList<>();
// 调用重载方法实际创建对象(通过构造函数或无参构造)
Object resultObject = this.createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
// 如果对象创建成功且不是简单类型
if (resultObject != null && !this.hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 获取所有属性的映射信息
List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
// 遍历所有属性映射,查找是否存在延迟加载的嵌套查询
for (ResultMapping propertyMapping : propertyMappings) {
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
// 生成代理对象支持延迟加载,代理会拦截对懒加载属性的访问
resultObject = this.configuration.getProxyFactory().createProxy(
resultObject, lazyLoader, this.configuration, this.objectFactory,
constructorArgTypes, constructorArgs);
break; // 找到一个即可,停止遍历
}
}
}
// 标记是否启用了构造函数注入(构造函数参数列表不为空即为true)
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty();
// 返回创建好的对象实例(可能是代理对象)
return resultObject;
}
}
这里会调用:
Object resultObject = this.createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
接着进入 DefaultResultSetHandler
的 createResultObject
方法:
public class DefaultResultSetHandler implements ResultSetHandler {
/**
* 创建结果对象实例
*
* 根据 resultMap 中的映射规则和目标类型,创建对象实例,具体逻辑如下:
*
* 1. 如果目标类型有对应的 TypeHandler(简单类型,如基本类型、包装类、String 等)
* - 调用 createPrimitiveResultObject,直接从结果集取对应列映射成简单类型返回
*
* 2. 如果 resultMap 定义了构造函数映射(constructorMappings 非空)
* - 调用 createParameterizedResultObject,通过带参数构造函数创建对象
*
* 3. 如果目标类型不是接口,且没有无参构造函数
* - 如果允许自动映射,则尝试根据构造函数参数名称和类型自动匹配列值,调用 createByConstructorSignature 创建对象
* - 否则抛出异常,无法实例化
*
* 4. 其他情况(存在无参构造函数或接口类型)
* - 通过 objectFactory 调用无参构造函数创建实例
*
* @param rsw ResultSet 封装类,包含当前结果集和元数据信息
* @param resultMap 映射规则,定义如何映射数据库列到对象属性
* @param constructorArgTypes 输出参数,记录构造函数参数类型,用于后续映射
* @param constructorArgs 输出参数,记录构造函数参数值
* @param columnPrefix 列名前缀,用于嵌套结果映射的列过滤,普通映射时为 null
* @return 创建好的对象实例,或基本类型映射值
* @throws SQLException SQL 异常
*/
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap,
List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
String columnPrefix) throws SQLException {
// 目标映射类型
Class<?> resultType = resultMap.getType();
// 元信息工具,用于获取类的构造函数、方法等反射信息
MetaClass metaType = MetaClass.forClass(resultType, this.reflectorFactory);
// 构造函数映射列表(通过 <constructor> 标签定义的映射)
List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
// 1. 如果类型有对应的 TypeHandler,则直接调用 createPrimitiveResultObject 创建简单类型对象
if (this.hasTypeHandlerForResultObject(rsw, resultType)) {
return this.createPrimitiveResultObject(rsw, resultMap, columnPrefix);
}
// 2. 有构造函数映射,则调用带参构造函数创建对象
else if (!constructorMappings.isEmpty()) {
return this.createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
}
// 3. 没有无参构造函数,且不是接口,尝试自动根据构造函数参数映射创建对象
else if (!resultType.isInterface() && !metaType.hasDefaultConstructor()) {
if (this.shouldApplyAutomaticMappings(resultMap, false)) {
return this.createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs);
} else {
// 无法创建实例,抛异常
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}
}
// 4. 其他情况,调用无参构造函数创建实例
else {
return this.objectFactory.create(resultType);
}
}
}
注意:
createResultObject
可能存在的几种情况及其处理方式
情况序号 | 条件描述 | 处理方式 | 备注 |
---|---|---|---|
1 | 目标类型有对应的 TypeHandler | 调用 createPrimitiveResultObject ,直接从结果集取对应列映射成简单类型返回 |
例如:Integer 、String 、Long 、Date 等简单类型 |
2 | resultMap 中定义了构造函数映射(constructorMappings 非空) |
调用 createParameterizedResultObject ,通过带参数构造函数创建对象 |
构造函数参数和数据库列显式绑定,通常通过 XML <constructor> 标签配置 |
3 | 目标类型不是接口,且没有无参构造函数 | 如果允许自动映射,则调用 createByConstructorSignature ,根据构造函数参数自动匹配列值创建对象 |
自动匹配可以基于参数名称或参数顺序匹配数据库列(需要 JDK 8+ 参数名支持) |
4 | 目标类型不是接口,且没有无参构造函数,且不允许自动映射 | 抛出异常 ExecutorException ,提示无法实例化 |
表示开发者需要补充无参构造函数或者配置构造函数映射 |
5 | 目标类型是接口或者有无参构造函数 | 调用 objectFactory.create(resultType) ,通过无参构造函数创建实例 |
最常用情况,MyBatis 创建实例后通过 setter 或反射设置属性 |
其实,逻辑走到这里就一目了然了,mybatis 映射优先级从上至下依次排列,而且需要注意的是,如果是通过有参构造器进行映射,则会按照构造器参数顺序依次映射,如果结果集顺序,和有参构造器的参数顺序不一致,会导致映射失败。