11_Mybatis 是如何进行DO类和数据库字段的映射的?
11_Mybatis 是如何进行DO类和数据库字段的映射的?
假设 VideoAbnormalContentMapper.xml
文件有如下方法:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//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_contentorder 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;} // 如果方法返回多个结果,比如返回 Listelse 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 {// 普通查询,不带分页,直接调用 selectListresult = 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 {// 普通查询,不带分页,直接调用 selectListresult = 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 idErrorContext.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);// 处理当前结果集,映射数据行为对象,结果加入 multipleResultsthis.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 ResultSetResultSet 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;// 如果未找到任何有效数据,且配置不允许返回空对象,则返回 nullrowValue = !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 映射优先级从上至下依次排列,而且需要注意的是,如果是通过有参构造器进行映射,则会按照构造器参数顺序依次映射,如果结果集顺序,和有参构造器的参数顺序不一致,会导致映射失败。