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.pswp.cn/pingmian/92267.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/92267.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/92267.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年渗透测试面试题总结-06(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 逻辑漏洞 一、三大高危业务逻辑漏洞及修复方案 1. 订单金额篡改&#xff08;参数操纵&#xff09; 2. 重…

SpringBoot激活指定profile的方式

题目详细答案在 Spring Boot 中&#xff0c;可以通过多种方式激活指定的 Profile&#xff0c;以便在不同的环境中使用不同的配置。在application.properties文件中激活可以在默认的application.properties文件中通过spring.profiles.active属性激活某个 Profile。# application…

Pytest项目_day10(接口的参数传递)

接口的参数传递 如果我们需要在一个测试用例中使用另一个测试用例中获得的数据&#xff0c;应该怎么办&#xff1f; 解决方案一&#xff1a;使用函数返回值 - 我们可以在另一个测试用例中使用return来返回所需的数据&#xff0c;并在其他的测试用例中调用该测试用例&#xff08…

深信服GO面试题及参考答案(上)

Go 和 Java 的特点和区别是什么? Go 和 Java 都是静态类型、编译型语言,但在设计理念、语法特性、并发模型等方面存在显著差异,具体如下: 从语言设计目标来看,Go 由 Google 开发,旨在解决大型系统开发中的复杂性,强调“简单、高效、并发”,语法简洁,摒弃了许多传统面向…

BGP笔记及综合实验

BGP基础一、BGP产生背景 - BGP定义&#xff1a;边界网关协议&#xff08;BGP&#xff09;是自治系统间的动态路由协议&#xff0c;属于外部网关协议&#xff08;EGP&#xff09;。 - 自治系统&#xff08;AS&#xff09;&#xff1a;由统一管理、运行同一IGP协议的路由器组成&a…

全栈:如何判断自己应该下载哪个版本的Tomcat

版本兼容性矩阵 https://tomcat.apache.org/whichversion.html https://tomcat.apache.org/download-11.cgi 介绍一下这些版本的不同点&#xff1a; 一、按系统选&#xff08;优先看这个&#xff09; 1.Windows 系统&#xff08;普通使用&#xff0c;非服务自启&#xff09…

Redis的Linux安装

可以直接命令下载 wget http://download.redis.io/releases/redis-5.0.4.tar.gz下载好之后解压缩&#xff0c;并且重命名为redis 由于redis是c语言编写的&#xff0c;所以我们需要先安装gcc&#xff0c;安装的命令如下&#xff1a;yum -y install gcc 安装成功后输入 : gcc -v…

14-netty基础-手写rpc-提供方(服务端)-06

netty系列文章&#xff1a; 01-netty基础-socket02-netty基础-java四种IO模型03-netty基础-多路复用select、poll、epoll04-netty基础-Reactor三种模型05-netty基础-ByteBuf数据结构06-netty基础-编码解码07-netty基础-自定义编解码器08-netty基础-自定义序列化和反序列化09-n…

连续时间和数字之间频率的偏差以及相位补偿

接下来需要讲解在连续时间域下的角频率以及在离散化后的数字角频率。上面可以知道模拟角频率和数字的区别 接下来介绍相位 相位单位是弧度无频偏&#xff1a; 对于数字来说是对连续信号采样后的结果&#xff0c;数字的角频率 &#xff0c;就是相位的递增量&#xff0c;表示每个…

《Git从入门到精通:告别版本管理混乱》

坚持用 清晰易懂的图解 代码语言&#xff0c;让每个知识点变得简单&#xff01; &#x1f680;呆头个人主页详情 &#x1f331; 呆头个人Gitee代码仓库 &#x1f4cc; 呆头详细专栏系列 座右铭&#xff1a; “不患无位&#xff0c;患所以立。” 《Git从入门到精通&#xff1a…

小红书开源多模态视觉语言模型DOTS-VLM1

项目简介与模型基本介绍 DOTS-VLM1 是由小红书希实验室(Rednote HiLab)开源的多模态视觉语言模型(Vision-Language Model, VLM),旨在推动视觉与语言理解的融合研究。DOTS-VLM1 采用主流的编码-融合-解码架构,支持图片与文本的联合理解与生成,适用于图文问答、图片描述、…

【Git】企业级使用

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【中间件】企业级中间件剖析 基本概念 Git 有三个核心区域&#xff0c;分别是工作区、暂存区和版本库&#xff0c;理解这三个区域是掌握 Git 的基础。​ ​ 工作区就是我们电脑里能看到的文件目录&…

Druid学习笔记 02、快速使用Druid的SqlParser解析

文章目录前言本章节源码描述认识作者官方文档快速入门demo案例引入依赖获取到SQL的AST(抽象语法树)使用visitor完成表、字段、表达式解析汇总总结一、简介1.1、和Antlr生成Parser的区别1.2、Druid SQL Parser的使用场景二、各种语法支持三、性能四、Druid SQL Parser的代码结构…

时间复杂度计算(以for循环为例)

本文理论内容来自严蔚敏版《数据结构(C语言版 第2版)》 *本文仅为复习时的总结&#xff0c;描述不准确、过程不严谨之处&#xff0c;还请理解 一、算法的相关概念 首先复习一下算法的定义及5个重要特性 其次是算法的评价标准 可以看到 时间复杂度 属于算法评价标准中的高效性…

图论(1):图数据结构

目录 一、图的定义 1.1 图的基本概念 1.2 图的分类 &#xff08;1&#xff09;按边的方向&#xff1a; &#xff08;2&#xff09;按边的权值&#xff1a; &#xff08;3&#xff09;按边的数量和类型&#xff1a; &#xff08;4&#xff09;按连通性&#xff1a; 1.3 图…

等保测评-Nginx中间件

Nginx *排查有无Nginx中间件&#xff0c;可使用以下命令&#xff1a; ps -ef | grep nginx、netstat -nutlp *确认Nginx中间件有运行&#xff0c;查看其目录&#xff1a; find / -name nginx.conf、ps -ef | grep Nginx *确认好目录后&#xff0c;查看版本&#xff1a; …

Milvus向量数据库版本升级

创建时间&#xff1a;2025-3-11 更新时间&#xff1a;2025-8-8 作者&#xff1a;薄刀刀、散装DBA 联系方式&#xff1a;bulkdba&#xff0c;1511777 背景&#xff1a;当前版本无法使用分组搜索功能&#xff0c;通过升级版本解决&#xff0c;计划将milvus升级到2.4.15&#xf…

若依前后端分离版学习笔记(六)——JWT

在上一节已经提到了传统Session认证和JWT认证内容&#xff0c;这一节对JWT进行更加详细的了解。 一 JWT介绍 1、传统的session认证 1.1 传统session认证流程 1.用户向服务器发送用户名和密码 2.服务器通过验证后&#xff0c;在当前对话&#xff08;session&#xff09;中保存相…

如何永久删除三星手机中的照片?

如果你计划出售你的三星 Galaxy 手机&#xff0c;或者整理其接近满容量的存储空间&#xff0c;你可能会担心如何从设备中移除照片和其他文件。这对于确保你的个人信息保持安全至关重要&#xff0c;即使你选择通过各种平台捐赠或出售旧手机也是如此。在本文中&#xff0c;我们介…

【数字图像处理系列笔记】Ch06:图像压缩

一、基础知识信源编码器&#xff1a;减少或消除输入图像中的编码冗余、像素 间冗余以及心理视觉冗余。 数据的冗余 一、空间冗余&#xff08;Spatial Redundancy&#xff09;1. 定义图像中相邻像素间的强相关性导致的冗余 —— 同一区域内相邻像素的像素值&#xff08;如灰度、…