參考 知識星球 中 芋道源碼 星球的源碼解析,一個活躍度非常高的 Java 技術社群,感興趣的小伙伴可以加入 芋道源碼 星球,一起學習😄
該系列文檔是本人在學習 Mybatis 的源碼過程中總結下來的,可能對讀者不太友好,請結合我的源碼注釋(Mybatis源碼分析 GitHub 地址、Mybatis-Spring 源碼分析 GitHub 地址、Spring-Boot-Starter 源碼分析 GitHub 地址)進行閱讀
MyBatis 版本:3.5.2
MyBatis-Spring 版本:2.0.3
MyBatis-Spring-Boot-Starter 版本:2.1.4
該系列其他文檔請查看:《精盡 MyBatis 源碼分析 - 文章導讀》
MyBatis的SQL執行過程
在前面一系列的文檔中,我已經分析了 MyBatis 的基礎支持層以及整個的初始化過程,此時 MyBatis 已經處於就緒狀態了,等待使用者發號施令了
那么接下來我們來看看它執行SQL的整個過程,該過程比較復雜,涉及到二級緩存,將返回結果轉換成 Java 對象以及延遲加載等等處理過程,這里將一步一步地進行分析:
MyBatis中SQL執行的整體過程如下圖所示:

在 SqlSession 中,會將執行 SQL 的過程交由Executor
執行器去執行,過程大致如下:
- 通過
DefaultSqlSessionFactory
創建與數據庫交互的SqlSession
“會話”,其內部會創建一個Executor
執行器對象 - 然后
Executor
執行器通過StatementHandler
創建對應的java.sql.Statement
對象,並通過ParameterHandler
設置參數,然后執行數據庫相關操作 - 如果是數據庫更新操作,則可能需要通過
KeyGenerator
先設置自增鍵,然后返回受影響的行數 - 如果是數據庫查詢操作,則需要將數據庫返回的
ResultSet
結果集對象包裝成ResultSetWrapper
,然后通過DefaultResultSetHandler
對結果集進行映射,最后返回 Java 對象
上面還涉及到一級緩存、二級緩存和延遲加載等其他處理過程
SQL執行過程(二)之StatementHandler
在上一篇文檔中,已經詳細地分析了在MyBatis的SQL執行過程中,SqlSession會話將數據庫操作交由Executor執行器去完成,實際上需要通過StatementHandler
創建相應的Statement
對象,並做一些准備工作,然后通過Statement
執行數據庫操作,查詢結果則需要通過ResultSetHandler
對結果集進行映射轉換成Java對象,那么接下來我們先來看看StatementHandler
到底做哪些操作
StatementHandler接口的實現類如下圖所示:

-
org.apache.ibatis.executor.statement.RoutingStatementHandler
:實現StatementHandler接口,裝飾器模式,根據Statement類型創建對應的StatementHandler對象,所有的方法執行交由該對象執行 -
org.apache.ibatis.executor.statement.BaseStatementHandler
:實現StatementHandler接口,提供骨架方法,指定的幾個抽象方法交由不同的子類去實現 -
org.apache.ibatis.executor.statement.SimpleStatementHandler
:繼承BaseStatementHandler抽象類,創建java.sql.Statement
進行數據庫操作 -
org.apache.ibatis.executor.statement.PreparedStatementHandler
:繼承BaseStatementHandler抽象類,創建java.sql.PreparedStatement
進行數據庫操作(默認) -
org.apache.ibatis.executor.statement.CallableStatementHandler
:繼承BaseStatementHandler抽象類,創建java.sql.CallableStatement
進行數據庫操作,用於存儲過程
我們先回顧一下StatementHandler是在哪里被創建的,可以在《SQL執行過程(一)之Executor》的SimpleExecutor小節中有講到,創建的是RoutingStatementHandler
對象
StatementHandler
org.apache.ibatis.executor.statement.StatementHandler
:Statement處理器接口,代碼如下:
public interface StatementHandler {
/**
* 准備操作,可以理解成創建 Statement 對象
*
* @param connection Connection 對象
* @param transactionTimeout 事務超時時間
* @return Statement 對象
*/
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
/**
* 設置 Statement 對象的參數
*
* @param statement Statement 對象
*/
void parameterize(Statement statement) throws SQLException;
/**
* 添加 Statement 對象的批量操作
*
* @param statement Statement 對象
*/
void batch(Statement statement) throws SQLException;
/**
* 執行寫操作
*
* @param statement Statement 對象
* @return 影響的條數
*/
int update(Statement statement) throws SQLException;
/**
* 執行讀操作
*
* @param statement Statement 對象
* @param resultHandler ResultHandler 對象,處理結果
* @param <E> 泛型
* @return 讀取的結果
*/
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
/**
* 執行讀操作,返回 Cursor 對象
*
* @param statement Statement 對象
* @param <E> 泛型
* @return Cursor 對象
*/
<E> Cursor<E> queryCursor(Statement statement) throws SQLException;
/**
* @return BoundSql 對象
*/
BoundSql getBoundSql();
/**
* @return ParameterHandler 對象
*/
ParameterHandler getParameterHandler();
}
每個方法可以根據注釋先理解它的作用,在實現類中的會講到
RoutingStatementHandler
org.apache.ibatis.executor.statement.RoutingStatementHandler
:實現StatementHandler接口,采用裝飾器模式,在初始化的時候根據Statement類型,創建對應的StatementHandler對象,代碼如下:
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
// 根據不同的類型,創建對應的 StatementHandler 實現類
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
}
-
在構造函數中初始化
delegate
委托對象,根據MappedStatement
(每個SQL對應的對象)的statementType
類型,創建對應的StatementHandler實現類 -
其余所有的方法都是直接交由
delegate
去執行的,這里就不列出來了,就是實現StatementHandler接口的方法
回顧到《MyBatis初始化(二)之加載Mapper接口與XML映射文件》中的XMLStatementBuilder小節,在parseStatementNode
方法中的第10
步如下:
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
所以說statementType
的默認值為PREPARED
,委托對象也就是PreparedStatementHandler
類型
BaseStatementHandler
org.apache.ibatis.executor.statement.BaseStatementHandler
:實現StatementHandler接口,提供骨架方法,指定的幾個抽象方法交由不同的子類去實現
構造方法
public abstract class BaseStatementHandler implements StatementHandler {
/**
* 全局配置
*/
protected final Configuration configuration;
/**
* 實例工廠
*/
protected final ObjectFactory objectFactory;
/**
* 類型處理器注冊表
*/
protected final TypeHandlerRegistry typeHandlerRegistry;
/**
* 執行結果處理器
*/
protected final ResultSetHandler resultSetHandler;
/**
* 參數處理器,默認 DefaultParameterHandler
*/
protected final ParameterHandler parameterHandler;
/**
* 執行器
*/
protected final Executor executor;
/**
* SQL 相關信息
*/
protected final MappedStatement mappedStatement;
/**
* 分頁條件
*/
protected final RowBounds rowBounds;
/**
* SQL 語句
*/
protected BoundSql boundSql;
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
// <1> 如果 boundSql 為空,更新數據庫的操作這里傳入的對象會為 null
if (boundSql == null) { // issue #435, get the key before calculating the statement
// <1.1> 生成 key,定義了 <selectKey /> 且配置了 order="BEFORE",則在 SQL 執行之前執行
generateKeys(parameterObject);
// <1.2> 創建 BoundSql 對象
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
// <2> 創建 ParameterHandler 對象,默認為 DefaultParameterHandler
// PreparedStatementHandler 實現的 parameterize 方法中需要對參數進行預處理,進行參數化時需要用到
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// <3> 創建 DefaultResultSetHandler 對象
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
}
關於它的屬性可以根據注釋進行理解
-
如果入參中的
boundSql
為null
,則需要進行初始化,可以會看到SimpleExecutor
中執行數據庫的更新操作時,傳入的boundSql
為null
,數據庫的查詢操作才會傳入該對象的值-
調用
generateKeys(Object parameter)
方法,根據配置的KeyGenerator
對象,在SQL執行之前執行查詢操作獲取值,設置到入參對象對應屬性中,代碼如下:protected void generateKeys(Object parameter) { /* * 獲得 KeyGenerator 對象 * 1. 配置了 <selectKey /> 則會生成 SelectKeyGenerator 對象 * 2. 配置了 useGeneratedKeys="true" 則會生成 Jdbc3KeyGenerator 對象 * 否則為 NoKeyGenerator 對象 */ KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); ErrorContext.instance().store(); // 前置處理,創建自增編號到 parameter 中 keyGenerator.processBefore(executor, mappedStatement, null, parameter); ErrorContext.instance().recall(); }
只有配置的
<selectKey />
標簽才有前置處理,這就是為什么數據庫的更新操作傳入的boundSql
為null
的原因,因為入參中有的屬性值可能需要提前生成一個值(執行配置的SQL語句),KeyGenerator
會在后續講到😈 -
通過
MappedStatement
對象根據入參獲取BoundSql
對象,在《MyBatis初始化(四)之SQL初始化(下)》中的SqlSource小節中有講到這個方法,如果是動態SQL則需要進行解析,獲取到最終的SQL,替換成?
占位符
-
-
創建
ParameterHandler
對象,用於對參數進行預處理,默認為DefaultParameterHandler
,這個也在《MyBatis初始化(四)之SQL初始化(下)》中有講過可以看到Configuration的
newParameterHandler
方法:public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { // 創建 ParameterHandler 對象 ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); // 應用插件 parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; }
-
創建
ResultSetHandler
,用於返回結果的映射,默認為DefaultResultSetHandler
,這個映射過程非常復雜,會有單獨一篇文檔進行分析😈可以看到Configuration的
newResultSetHandler
方法:public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { // 創建 DefaultResultSetHandler 對象 ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); // 應用插件 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; }
prepare方法
創建Statement對象,做一些初始化工作,代碼如下:
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// <1> 創建 Statement 對象
statement = instantiateStatement(connection);
// <2> 設置執行和事務的超時時間
setStatementTimeout(statement, transactionTimeout);
// <3> 設置 fetchSize,為驅動的結果集獲取數量(fetchSize)設置一個建議值
setFetchSize(statement);
return statement;
} catch (SQLException e) {
// 發生異常,進行關閉
closeStatement(statement);
throw e;
} catch (Exception e) {
// 發生異常,進行關閉
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
-
創建 Statement 對象,調用
instantiateStatement(Connection connection)
抽象方法,交由不同的子類去實現 -
設置執行和事務的超時時間,調用
setStatementTimeout(Statement stmt, Integer transactionTimeout)
方法,如下:protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException { Integer queryTimeout = null; // 獲得 queryTimeout if (mappedStatement.getTimeout() != null) { queryTimeout = mappedStatement.getTimeout(); } else if (configuration.getDefaultStatementTimeout() != null) { queryTimeout = configuration.getDefaultStatementTimeout(); } // 設置執行的超時時間 if (queryTimeout != null) { stmt.setQueryTimeout(queryTimeout); } // 設置事務超時時間 StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout); }
-
設置 fetchSize,為驅動的結果集獲取數量(fetchSize)設置一個建議值(無默認值),調用
setFetchSize(Statement stmt)
方法,如下:protected void setFetchSize(Statement stmt) throws SQLException { // 獲得 fetchSize 配置 Integer fetchSize = mappedStatement.getFetchSize(); if (fetchSize != null) { stmt.setFetchSize(fetchSize); return; } // 獲得 fetchSize 的默認配置 Integer defaultFetchSize = configuration.getDefaultFetchSize(); if (defaultFetchSize != null) { stmt.setFetchSize(defaultFetchSize); } }
-
發生任何異常都會關閉 Statement 對象
SimpleStatementHandler
org.apache.ibatis.executor.statement.SimpleStatementHandler
:繼承BaseStatementHandler抽象類,創建java.sql.Statement
進行數據庫操作,部分代碼如下:
public class SimpleStatementHandler extends BaseStatementHandler {
public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public void batch(Statement statement) throws SQLException {
String sql = boundSql.getSql();
// 添加到批處理
statement.addBatch(sql);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
// <1> 執行查詢
statement.execute(sql);
// <2> 處理返回結果
return resultSetHandler.handleResultSets(statement);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.handleCursorResultSets(statement);
}
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
// 創建Statement對象
return connection.createStatement();
} else {
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
@Override
public void parameterize(Statement statement) {
// N/A
}
}
-
上面的方法都很簡單,都是直接通過
Statement
對象執行數據庫操作 -
在查詢方法中,需要通過
resultSetHandler
對結果集進行映射,返回對應的Java對象
update方法
執行數據庫更新操作,方法如下:
@Override
public int update(Statement statement) throws SQLException {
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
/*
* 獲得 KeyGenerator 對象
* 1. 配置了 <selectKey /> 則會生成 SelectKeyGenerator 對象
* 2. 配置了 useGeneratedKeys="true" 則會生成 Jdbc3KeyGenerator 對象
* 否則為 NoKeyGenerator 對象
*/
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
if (keyGenerator instanceof Jdbc3KeyGenerator) { // 如果是 Jdbc3KeyGenerator 類型
// <1.1> 執行寫操作,設置返回自增鍵,可通過 getGeneratedKeys() 方法獲取
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
// <1.2> 獲得更新數量
rows = statement.getUpdateCount();
// <1.3> 執行 keyGenerator 的后置處理邏輯,也就是對我們配置的自增鍵進行賦值
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) { // 如果是 SelectKeyGenerator 類型
// <2.1> 執行寫操作
statement.execute(sql);
// <2.2> 獲得更新數量
rows = statement.getUpdateCount();
// <2.3>執行 keyGenerator 的后置處理邏輯,也就是對我們配置的自增鍵進行賦值
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
// <3.1> 執行寫操作
statement.execute(sql);
// <3.2> 獲得更新數量
rows = statement.getUpdateCount();
}
return rows;
}
為什么數據庫更新操作的方法需要做這么多處理,其實目的就一個,支持用戶配置的自增鍵,設置到入參中
在BaseStatementHandler
的構造方法已經有過KeyGenerator
的前置處理了,那里是在SQL執行之前,執行查詢操作獲取值,設置到入參對象對應屬性中
而這里需要做的就是在SQL執行的后置處理了,在SQL執行之后,執行查詢操作獲取值或者設置需要返回哪些自增鍵,設置到入參對象對應屬性中
-
如果
KeyGenerator
是Jdbc3KeyGenerator
類型,也就是配置useGeneratedKeys="true"
- 執行寫操作,設置需要返回自增鍵,可通過
getGeneratedKeys()
方法獲取 - 獲得受影響的行數
- 執行后置處理,調用其
processAfter
方法,也就是將我們配置的自增鍵設置到入參對象中
- 執行寫操作,設置需要返回自增鍵,可通過
-
如果
KeyGenerator
是SelectKeyGenerator
類型,也就是添加了<selectKey />
標簽- 執行寫操作
- 獲得受影響的行數
- 執行后置處理,調用其
processAfter
方法,也就是執行查詢操作獲取值,設置到入參對象對應屬性中
-
如果沒有配置
KeyGenerator
- 執行寫操作
- 獲得受影響的行數
PreparedStatementHandler
org.apache.ibatis.executor.statement.PreparedStatementHandler
:繼承BaseStatementHandler抽象類,創建java.sql.PreparedStatement
進行數據庫操作(默認),部分代碼如下:
public class PreparedStatementHandler extends BaseStatementHandler {
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public void batch(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 添加到批處理
ps.addBatch();
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 執行
ps.execute();
// 結果處理器並返回結果
return resultSetHandler.handleResultSets(ps);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 執行
ps.execute();
// 結果處理器並返回 Cursor 結果
return resultSetHandler.handleCursorResultSets(ps);
}
}
-
上面的方法都很簡單,都是直接通過
PreparedStatement
對象執行數據庫操作 -
在查詢方法中,需要通過
resultSetHandler
對結果集進行映射,返回對應的Java對象
instantiateStatement方法
創建一個PreparedStatement
對象,方法如下:
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
/*
* 獲得 KeyGenerator 對象
* 1. 配置了 <selectKey /> 則會生成 SelectKeyGenerator 對象
* 2. 配置了 useGeneratedKeys="true" 則會生成 Jdbc3KeyGenerator 對象
* 否則為 NoKeyGenerator 對象
*/
// <1> 處理 Jdbc3KeyGenerator 的情況
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
// <1.1> 獲得 keyColumn 配置
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
// <1.2 >創建 PreparedStatement 對象,並返回自增鍵,並可通過 getGeneratedKeys() 方法獲取
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
// <1.3> 創建 PreparedStatement 對象,並返回我們配置的 column 列名自增鍵,並可通過 getGeneratedKeys() 方法獲取
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
// <2> 創建 PrepareStatement 對象
return connection.prepareStatement(sql);
} else {
// <3> 創建 PrepareStatement 對象,指定 ResultSetType
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
-
處理
Jdbc3KeyGenerator
的情況,也就是配置了useGeneratedKeys="true"
- 獲得
keyColumn
配置,哪些自增鍵需要返回 - 如果keyColumn為null,返回
PreparedStatement
對象,設置RETURN_GENERATED_KEYS
,表示所有自增列都返回,可通過getGeneratedKeys()
方法獲取 - 如果keyColumn不為null,返回
PreparedStatement
對象,設置需要返回的自增列為keyColumn,可通過getGeneratedKeys()
方法獲取
- 獲得
-
沒有配置Jdbc3KeyGenerator對象,創建
PreparedStatement
對象返回,默認情況 -
沒有配置Jdbc3KeyGenerator對象,但是指定了ResultSetType,則返回
PreparedStatement
對象,指定ResultSetType
parameterize方法
設置PreparedStatement的占位符參數,方法如下:
@Override
public void parameterize(Statement statement) throws SQLException {
// 通過 DefaultParameterHandler 設置 PreparedStatement 的占位符參數
parameterHandler.setParameters((PreparedStatement) statement);
}
update方法
執行數據庫更新操作,方法如下:
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 執行
ps.execute();
// 獲得更新數量
int rows = ps.getUpdateCount();
// 入參對象
Object parameterObject = boundSql.getParameterObject();
/*
* 獲得 KeyGenerator 對象
* 1. 配置了 <selectKey /> 則會生成 SelectKeyGenerator 對象
* 2. 配置了 useGeneratedKeys="true" 則會生成 Jdbc3KeyGenerator 對象
* 否則為 NoKeyGenerator 對象
*/
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
// 執行 keyGenerator 的后置處理邏輯,也就是對我們配置的自增鍵進行賦值
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
- 執行數據庫更新操作
- 獲得受影響行數
- 根據配置的
KeyGenerator
對象,執行后置處理,執行查詢操作獲取值,或者獲取返回的自增鍵,設置到入參對象對應屬性中
CallableStatementHandler
org.apache.ibatis.executor.statement.CallableStatementHandler
:繼承BaseStatementHandler抽象類,創建java.sql.CallableStatement
進行數據庫操作,用於存儲過程
在執行完數據庫的操作后需要調用DefaultResultSetHandler
的handleOutputParameters
方法,處理需要作為出參的參數,這里就不做過得的講述了😈
KeyGenerator
在上面已經講到在執行數據庫更新操作時,需要通過KeyGenerator來進行前置處理或者后置處理,我一般用於自增主鍵
先來看看它的實現類,如下圖所示:

-
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator
:實現KeyGenerator接口,只有后置處理的實現,獲取返回的自增鍵,設置到入參的屬性中,配置了useGeneratedKeys="true"
則會創建該對象 -
org.apache.ibatis.executor.keygen.SelectKeyGenerator
:實現KeyGenerator接口,執行數據庫查詢操作,獲取到對應的返回結果設置到入參的屬性中,添加了<selectKey />
標簽則會創建該對象,前后置處理可以配置 -
org.apache.ibatis.executor.keygen.NoKeyGenerator
:實現KeyGenerator接口,空實現,一個單例,沒有配置上面的兩種方式,則默認為該對象
KeyGenerator接口,代碼如下:
public interface KeyGenerator {
/**
* 在 SQL 執行后設置自增鍵到入參中
*
* @param executor 執行器
* @param ms MappedStatement 對象
* @param stmt Statement對象
* @param parameter 入參對象
*/
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
/**
* 在 SQL 執行前設置自增鍵到入參中
*
* @param executor 執行器
* @param ms MappedStatement 對象
* @param stmt Statement對象
* @param parameter 入參對象
*/
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}
Jdbc3KeyGenerator
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator
:實現KeyGenerator接口,只有后置處理的實現,獲取返回的自增鍵,設置到入參的屬性中,配置了useGeneratedKeys="true"
則會創建該對象,在《MyBatis初始化(二)之加載Mapper接口與XML映射文件》的XMLStatementBuilder小節的parseStatementNode
方法中的第8
步看到
實現方法:
public class Jdbc3KeyGenerator implements KeyGenerator {
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// do nothing
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// 批處理多個自增鍵
processBatch(ms, stmt, parameter);
}
}
可以看到前置處理的實現方法為空,后置處理的實現方法調用processBatch
方法
processBatch方法
processBatch(MappedStatement ms, Statement stmt, Object parameter)
方法,從結果集中獲自增鍵設置到入參對象的屬性中,代碼如下:
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
// 獲取 keyProperty 配置
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
// 獲取 Statement 執行后自增鍵對應的 ResultSet 對象
try (ResultSet rs = stmt.getGeneratedKeys()) {
// 獲取 ResultSet 的 ResultSetMetaData 元數據對象
final ResultSetMetaData rsmd = rs.getMetaData();
final Configuration configuration = ms.getConfiguration();
if (rsmd.getColumnCount() < keyProperties.length) { // 自增鍵與 keyProperty 數量不一致則跳過
// Error?
} else {
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
- 獲取
keyProperty
配置,也就是需要將自增鍵設置到入參對象的屬性名稱 - 通過
Statement
的getGeneratedKeys()
方法獲取到自增鍵 - 如果自增鍵與屬性個數不相同則跳過,不進行處理了
- 否則調用
assignKeys
方法,分配自增鍵給對應的屬性
assignKeys方法
/**
* 關於 ParamMap,可以看到 {@link org.apache.ibatis.reflection.ParamNameResolver#getNamedParams} 這個方法
* 根據入參獲取獲取參數名稱與參數值的映射關系
* 如果為多個入參或者一個入參並且添加了 @Param 注解,則會返回 ParamMap 對象,key為參數名稱,value為參數值
*
* 需要使用到獲取自增鍵時,我們一般入參都是一個實體類,則進入的是下面第3種情況
*/
@SuppressWarnings("unchecked")
private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
Object parameter) throws SQLException {
if (parameter instanceof ParamMap || parameter instanceof StrictMap) { // <1>
// Multi-param or single param with @Param
assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
} else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
&& ((ArrayList<?>) parameter).get(0) instanceof ParamMap) { // <2>
// Multi-param or single param with @Param in batch operation
assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, ((ArrayList<ParamMap<?>>) parameter));
} else { // <3>
// Single param without @Param
assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
}
}
- 因為我的入參通常是一個實體類的時候,配置自增鍵的生成,這里我們直接看第三種情況,其他兩種可參考我的注釋進行閱讀
assignKeysToParam方法
private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
String[] keyProperties, Object parameter) throws SQLException {
// 將入參對象 parameter 轉換成集合,因為批處理時可能傳入多個入參對象
Collection<?> params = collectionize(parameter);
if (params.isEmpty()) {
return;
}
List<KeyAssigner> assignerList = new ArrayList<>();
for (int i = 0; i < keyProperties.length; i++) {
// 每一個 keyProperty 都創建一個 KeyAssigner 對象,設置 column 的位置
assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i]));
}
Iterator<?> iterator = params.iterator();
while (rs.next()) {
if (!iterator.hasNext()) {
throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, params.size()));
}
Object param = iterator.next();
// 往每個入參對象中設置配置的 keyProperty 為對應的自增鍵
assignerList.forEach(x -> x.assign(rs, param));
}
}
- 將入參對象
parameter
轉換成集合,因為批處理時可能傳入多個入參對象 - 每一個
keyProperty
都創建一個KeyAssigner
對象,設置自增鍵的位置,通過該對象往入參中設置該屬性值 - 遍歷入參
params
集合,每個入參對象都遍歷assignerList
集合,通過KeyAssigner
往入參中設置屬性值
KeyAssigner
定義在Jdbc3KeyGenerator的一個內部類,單個自增鍵的分配者,將該自增鍵設置到入參對象的屬性中,構造方法如下:
private class KeyAssigner {
/**
* 全局配置對象
*/
private final Configuration configuration;
/**
* Statement 執行后返回的 元數據對象
*/
private final ResultSetMetaData rsmd;
/**
* 類型處理器注冊中心
*/
private final TypeHandlerRegistry typeHandlerRegistry;
/**
* 屬性對應列所在的位置
*/
private final int columnPosition;
/**
* 參數名稱,添加了 @Param 注解時才有
*/
private final String paramName;
/**
* Java 屬性名稱
*/
private final String propertyName;
/**
* 類型處理器
*/
private TypeHandler<?> typeHandler;
protected KeyAssigner(Configuration configuration, ResultSetMetaData rsmd, int columnPosition, String paramName,
String propertyName) {
super();
this.configuration = configuration;
this.rsmd = rsmd;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.columnPosition = columnPosition;
this.paramName = paramName;
this.propertyName = propertyName;
}
}
- 定義了結果元數據
ResultSetMetaData
對象、自增鍵所在位置、屬性名稱、類型處理器
assign方法
assign(ResultSet rs, Object param)
方法,將當前自增鍵設置到param
入參的屬性中,代碼如下:
protected void assign(ResultSet rs, Object param) {
if (paramName != null) {
// If paramName is set, param is ParamMap
param = ((ParamMap<?>) param).get(paramName);
}
MetaObject metaParam = configuration.newMetaObject(param);
try {
if (typeHandler == null) {
if (metaParam.hasSetter(propertyName)) {
Class<?> propertyType = metaParam.getSetterType(propertyName);
// 根據 Java Type 和 Jdbc Type 獲取對應的類型處理器
typeHandler = typeHandlerRegistry.getTypeHandler(propertyType, JdbcType.forCode(rsmd.getColumnType(columnPosition)));
} else {
throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '"
+ metaParam.getOriginalObject().getClass().getName() + "'.");
}
}
if (typeHandler == null) {
// Error?
} else {
// 將 Jdbc Type 轉換成 Java Type
Object value = typeHandler.getResult(rs, columnPosition);
// 將該屬性值設置到 入參對象中
metaParam.setValue(propertyName, value);
}
} catch (SQLException e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e,
e);
}
}
- 從結果集中根據位置獲取到該自增鍵,然后設置到入參對象的屬性中
SelectKeyGenerator
org.apache.ibatis.executor.keygen.SelectKeyGenerator
:實現KeyGenerator接口,執行數據庫查詢操作,獲取到對應的返回結果設置到入參的屬性中,添加了<selectKey />
標簽則會創建該對象,前后置處理可以配置,在《MyBatis初始化(二)之加載Mapper接口與XML映射文件》的XMLStatementBuilder小節的parseStatementNode
方法中的第7
步看到
實現方法:
public class SelectKeyGenerator implements KeyGenerator {
/**
* <selectKey /> 解析成 MappedStatement 對象的 id 的后綴
* 例如 <selectKey /> 所在的 MappedStatement 的 id 為 namespace.test
* 那么它的 id 就是 namespace.test!selectKey
*/
public static final String SELECT_KEY_SUFFIX = "!selectKey";
/**
* 是否在 SQL 執行后執行
*/
private final boolean executeBefore;
/**
* <selectKey /> 解析成 MappedStatement 對象
*/
private final MappedStatement keyStatement;
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (executeBefore) { // 如果是在 SQL 執行之前進行生成 key
processGeneratedKeys(executor, ms, parameter);
}
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (!executeBefore) { // 如果是在 SQL 執行之后進行生成 key
processGeneratedKeys(executor, ms, parameter);
}
}
}
-
可以看到
<selectKey />
標簽還會生成一個MappedStatement
對象,用於執行查詢語句 -
executeBefore
屬性表示,是否為前置處理,所以<selectKey />
要么就是前置處理,要么就是后置處理,都是調用processGeneratedKeys
方法
processGeneratedKeys方法
執行數據庫的查詢操作,生成“主鍵”,設置到入參對象的屬性中,代碼如下:
private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
try {
if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
// <1> 獲取 keyProperty 配置
String[] keyProperties = keyStatement.getKeyProperties();
final Configuration configuration = ms.getConfiguration();
// <2> 創建入參 MetaObject 對象
final MetaObject metaParam = configuration.newMetaObject(parameter);
if (keyProperties != null) {
// Do not close keyExecutor.
// The transaction will be closed by parent executor.
// <3> 創建一個 SimpleExecutor 執行器
Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
// <4> 執行數據庫查詢
List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
if (values.size() == 0) {
throw new ExecutorException("SelectKey returned no data.");
} else if (values.size() > 1) {
throw new ExecutorException("SelectKey returned more than one value.");
} else {
// <5> 為數據庫查詢的到數據創建 MetaObject 對象
MetaObject metaResult = configuration.newMetaObject(values.get(0));
if (keyProperties.length == 1) { // <6.1> 單個屬性
if (metaResult.hasGetter(keyProperties[0])) {
// 往入參中設置該屬性為從數據庫查詢到的數據
setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
} else {
// no getter for the property - maybe just a single value object
// so try that
setValue(metaParam, keyProperties[0], values.get(0));
}
} else { //<6.2> 多個屬性
handleMultipleProperties(keyProperties, metaParam, metaResult);
}
}
}
}
} catch (ExecutorException e) {
throw e;
} catch (Exception e) {
throw new ExecutorException("Error selecting key or setting result to parameter object. Cause: " + e, e);
}
}
private void setValue(MetaObject metaParam, String property, Object value) {
if (metaParam.hasSetter(property)) {
metaParam.setValue(property, value);
} else {
throw new ExecutorException("省略...");
}
}
- 獲取
keyProperty
配置,也就是需要設置值到入參中的屬性名稱 - 創建入參的
MetaObject
對象metaParam
,便於操作 - 創建一個
SimpleExecutor
執行器 - 使用該執行器執行數據庫查詢操作
- 為數據庫查詢獲取到的結果創建
MetaObject
對象metaResult
- 將查詢結果設置到入參對象中
- 如果
keyProperty
配置的僅僅是一個屬性,則從metaResult
中獲取查詢結果設置到metaParam
入參對象的該屬性中 - 如果多個屬性需要賦值,則調用
handleMultipleProperties
方法將查詢結果設置到入參對象的多個屬性中
- 如果
handleMultipleProperties方法
private void handleMultipleProperties(String[] keyProperties, MetaObject metaParam, MetaObject metaResult) {
// 獲取 keyColumn 配置
String[] keyColumns = keyStatement.getKeyColumns();
if (keyColumns == null || keyColumns.length == 0) { // 沒有配置列名則直接去屬性名
// no key columns specified, just use the property names
for (String keyProperty : keyProperties) {
// 往入參中設置該屬性為從數據庫查詢到的數據,從查詢到的結果中取屬性名
setValue(metaParam, keyProperty, metaResult.getValue(keyProperty));
}
} else {
if (keyColumns.length != keyProperties.length) { // 列名和屬性名的個數不一致則拋出異常
throw new ExecutorException("If SelectKey has key columns, the number must match the number of key properties.");
}
for (int i = 0; i < keyProperties.length; i++) {
// 往入參中設置該屬性為從數據庫查詢到的數據,從查詢到的結果中取列名
setValue(metaParam, keyProperties[i], metaResult.getValue(keyColumns[i]));
}
}
}
- 沒有配置列名,則根據
keyProperty
屬性名稱從metaResult
查詢結果中獲取結果,一個一個設置到metaParam
入參對象的該屬性中 - 沒有配置列名,則根據
keyColumn
列名從metaResult
查詢結果中獲取結果,一個一個設置到metaParam
入參對象的該屬性中
NoKeyGenerator
org.apache.ibatis.executor.keygen.NoKeyGenerator
:實現KeyGenerator接口,空實現,一個單例,沒有配置上面的兩種方式,則默認為該對象,代碼如下:
public class NoKeyGenerator implements KeyGenerator {
/**
* A shared instance.
* @since 3.4.3
*/
public static final NoKeyGenerator INSTANCE = new NoKeyGenerator();
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// Do Nothing
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// Do Nothing
}
}
總結
本文分析了MyBatis在執行SQL的過程中,SimpleExecutor
(默認類型)執行器需要通過PrepareStatementHandler
(默認)來執行數據庫的操作,創建PrepareStatement
(默認)對象來完成數據操作
如果你配置了useGeneratedKeys="true"
,則需要在執行完數據庫更新操作后,通過Jdbc3KeyGenerator
設置自增鍵到入參對象中(后置處理)
如果你添加了<selectKey />
標簽,則需要通過SelectKeyGenerator
執行數據庫查詢操作獲取到結果,設置到入參對象中(前置處理、后置處理)
如果是查詢操作則需要通過ResultSetHandler
對結果集進行映射轉換成Java對象,這就是我們下一篇文檔需要分析的內容😄
參考文章:芋道源碼《精盡 MyBatis 源碼分析》