当前位置: 首页 > news >正文

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;}

数据库表如下:

字段名类型是否可空默认值注释
idbigintNOAUTO_INCREMENT主键ID
file_idbigintNO文件id
video_namevarchar(255)NO视频名
video_typevarchar(64)NO视频类型
model_typevarchar(255)NO模型类型
task_statustinyint(1)NO0任务状态(0-初始化,1-上传成功,2-上传失败,3-排队中,4-处理中,5-处理完成,6-处理失败,7-已过期,8-已取消)
task_resultvarchar(255)NO审核结果
is_breachtinyint(1)NO0是否违规
task_result_descriptionvarchar(255)NO结果描述
create_timedatetimeNOCURRENT_TIMESTAMP创建时间
update_timedatetimeNOCURRENT_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);
} 

然后进入 MapperMethodexecuteForMany 方法:

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);
}

接着进入 DefaultSqlSessionselectList 方法:

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);

接着进入 BaseExecutorquery 方法:

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);
}

接着进入 BaseExecutorqueryFromDatabase 方法:

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);

接着进入 SimpleExecutordoQuery 方法:

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);

接着进入 PreparedStatementHandlerquery 方法:

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);

接着进入 DefaultResultSetHandlerhandleResultSets 方法:

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);

接着进入 DefaultResultSetHandlerhandleResultSet 方法:

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());
}

接着进入 DefaultResultSetHandlerhandleRowValues 方法:

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);
}

接着进入 DefaultResultSetHandlerhandleRowValuesForSimpleResultMap 方法:

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);

接着进入 DefaultResultSetHandlergetRowValue 方法:

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);

接着进入 DefaultResultSetHandlercreateResultObject 方法:

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);

接着进入 DefaultResultSetHandlercreateResultObject 方法:

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,直接从结果集取对应列映射成简单类型返回例如:IntegerStringLongDate等简单类型
2resultMap 中定义了构造函数映射(constructorMappings 非空)调用 createParameterizedResultObject,通过带参数构造函数创建对象构造函数参数和数据库列显式绑定,通常通过 XML <constructor> 标签配置
3目标类型不是接口,且没有无参构造函数如果允许自动映射,则调用 createByConstructorSignature,根据构造函数参数自动匹配列值创建对象自动匹配可以基于参数名称或参数顺序匹配数据库列(需要 JDK 8+ 参数名支持)
4目标类型不是接口,且没有无参构造函数,且不允许自动映射抛出异常 ExecutorException,提示无法实例化表示开发者需要补充无参构造函数或者配置构造函数映射
5目标类型是接口或者有无参构造函数调用 objectFactory.create(resultType),通过无参构造函数创建实例最常用情况,MyBatis 创建实例后通过 setter 或反射设置属性

其实,逻辑走到这里就一目了然了,mybatis 映射优先级从上至下依次排列,而且需要注意的是,如果是通过有参构造器进行映射,则会按照构造器参数顺序依次映射,如果结果集顺序,和有参构造器的参数顺序不一致,会导致映射失败。

http://www.dtcms.com/a/322099.html

相关文章:

  • HTTP/HTTPS代理,支持RSA和SM2算法
  • 消防通道占用识别误报率↓79%:陌讯动态区域感知算法实战解析
  • 自签名证书实现HTTPS协议
  • 17.14 CogVLM-17B多模态模型爆肝部署:4-bit量化+1120px高清输入,A100实战避坑指南
  • 登上Nature子刊,深度学习正逐渐接管基础模型
  • NY128NY133美光固态闪存NY139NY143
  • 智驭全球波动:跨境量化交易系统2025解决方案
  • Linux系统:Ext系列文件系统(硬件篇)
  • 专题二_滑动窗口_将x减到0的最小操作数
  • Dart 单例模式:工厂构造、静态变量与懒加载
  • 频谱图学习笔记
  • python 通过Serper API联网搜索并大模型整理内容
  • 软件测试面试常见问题【含答案】
  • EtherCAT WatchDog
  • V4L2摄像头采集 + WiFi实时传输实战全流程
  • 深圳市天正达电子股份有限公司参展AUTO TECH China 2025 广州国际汽车技术展览会
  • std::transform
  • AI大模型专题:LLM大模型(Prompt提示词工程)
  • C语言实现经典扫雷游戏全解析
  • 使用观测云打造企业级监控告警中心
  • cudagraph 本质详解
  • Vue框架进阶
  • 宠智灵打造宠物AI开放平台:精准识别、灵活部署、生态共建
  • C++入门(上) -- 讲解超详细
  • 【狂神说java学习笔记】四:java流程控制(用户交互Scanner、顺序结构、if选择结构、switch选择结构)
  • isulad + harbor私有仓库登录
  • 大模型性能测试实战指南:从原理到落地的全链路解析
  • Claude使用报错 Error: Cannot find module ‘./yoga.wasm‘
  • 鸿蒙中使用tree
  • 系统集成项目管理工程师【第十一章 规划过程组】规划成本管理、成本估算、制定预算和规划质量管理篇