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.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;如灰度、…