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 映射優先級從上至下依次排列,而且需要注意的是,如果是通過有參構造器進行映射,則會按照構造器參數順序依次映射,如果結果集順序,和有參構造器的參數順序不一致,會導致映射失敗。