1. 概述
本文,我們來分享 SQL 執行的第三部分,keygen
包。整體類圖如下:
- 我們可以看到,整體是以 KeyGenerator 為核心。所以,本文主要會看到的就是 KeyGenerator 對自增主鍵的獲取。
2. KeyGenerator
org.apache.ibatis.executor.keygen.KeyGenerator
,主鍵生成器接口。代碼如下:
// KeyGenerator.java |
- 可在 SQL 執行之前或之后,進行處理主鍵的生成。
- 實際上,KeyGenerator 類的命名雖然包含 Generator ,但是目前 MyBatis 默認的 KeyGenerator 實現類,都是基於數據庫來實現主鍵自增的功能。
-
parameter
參數,指的是什么呢?以下面的方法為示例:- 上面的,
country
方法參數,就是一個parameter
參數。 - KeyGenerator 在獲取到主鍵后,會設置回
parameter
參數的對應屬性。
- 上面的,
KeyGenerator 有三個子類,如下圖所示:
- 具體的,我們下面逐小節來分享。
3. Jdbc3KeyGenerator
org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator
,實現 KeyGenerator 接口,基於 Statement#getGeneratedKeys()
方法的 KeyGenerator 實現類,適用於 MySQL、H2 主鍵生成。
3.1 構造方法
// Jdbc3KeyGenerator.java |
- 單例。
3.2 processBefore
|
- 空實現。因為對於 Jdbc3KeyGenerator 類的主鍵,是在 SQL 執行后,才生成。
3.3 processAfter
// Jdbc3KeyGenerator.java |
- 調用
#processBatch(Executor executor, MappedStatement ms, Statement stmt, Object parameter)
方法,處理返回的自增主鍵。單個parameter
參數,可以認為是批量的一個特例。
3.4 processBatch
// Jdbc3KeyGenerator.java |
<1>
處,獲得主鍵屬性的配置。如果為空,則直接返回,說明不需要主鍵。- 【重要】
<2>
處,調用Statement#getGeneratedKeys()
方法,獲得返回的自增主鍵。 <3>
處,調用#getSoleParameter(Object parameter)
方法,獲得唯一的參數對象。詳細解析,先跳到 「3.4.1 getSoleParameter」 。<3.1>
處,調用#assignKeysToParam(...)
方法,設置主鍵們,到參數soleParam
中。詳細解析,見 「3.4.2 assignKeysToParam」 。<3.2>
處,調用#assignKeysToOneOfParams(...)
方法,設置主鍵們,到參數parameter
中。詳細解析,見 「3.4.3 assignKeysToOneOfParams」 。
<4>
處,關閉 ResultSet 對象。
3.4.1 getSoleParameter
// Jdbc3KeyGenerator.java |
-
<1>
處,如下可以符合這個條件。代碼如下: -
<2>
處,如下可以符合這個條件。代碼如下:- 雖然有
country
和someId
參數,但是最終會被封裝成一個parameter
參數,類型為 ParamMap 類型。為什么呢?答案在ParamNameResolver#getNamedParams(Object[] args)
方法中。 - 如果是這個情況,獲得的主鍵,會設置回
country
的id
屬性,因為注解上的keyProperty = "country.id"
配置。 - 😈 此處比較繞,也相對用的少。
- 雖然有
-
<3>
處,如下可以符合這個條件。代碼如下:- 相比
<2>
的示例,主要是keyProperty = "id"
的修改,和去掉了@Param("someId") Integer someId
參數。 - 實際上,這種情況,和
<1>
是類似的。
- 相比
- 三種情況,
<2>
和<3>
有點復雜,胖友實際上,理解<1>
即可。
3.4.2 assignKeysToParam
// Jdbc3KeyGenerator.java |
<1>
處,包裝成 Collection 對象。通過這樣的方式,使單個param
參數的情況下,可以統一。-
<2>
處,遍歷paramAsCollection
數組:<2.1>
處, 順序遍歷rs
,相當於把當前的 ResultSet 對象的主鍵們,賦值給obj
對象的對應屬性。<2.2>
處,創建 MetaObject 對象,實現對obj
對象的屬性訪問。-
<2.3>
處,調用#getTypeHandlers(...)
方法,獲得 TypeHandler 數組。代碼如下:// Jdbc3KeyGenerator.java
private TypeHandler<?>[] getTypeHandlers(TypeHandlerRegistry typeHandlerRegistry, MetaObject metaParam, String[] keyProperties, ResultSetMetaData rsmd) throws SQLException {
// 獲得主鍵們,對應的每個屬性的,對應的 TypeHandler 對象
TypeHandler<?>[] typeHandlers = new TypeHandler<?>[keyProperties.length];
for (int i = 0; i < keyProperties.length; i++) {
if (metaParam.hasSetter(keyProperties[i])) {
Class<?> keyPropertyType = metaParam.getSetterType(keyProperties[i]);
typeHandlers[i] = typeHandlerRegistry.getTypeHandler(keyPropertyType, JdbcType.forCode(rsmd.getColumnType(i + 1)));
} else {
throw new ExecutorException("No setter found for the keyProperty '" + keyProperties[i] + "' in '"
+ metaParam.getOriginalObject().getClass().getName() + "'.");
}
}
return typeHandlers;
}- x
<2.4>
處,調用#populateKeys(...)
方法,填充主鍵們。詳細解析,見 「3.5 populateKeys」 。
3.4.3 assignKeysToOneOfParams
// Jdbc3KeyGenerator.java |
<1>
處,需要有.
。例如:@Options(useGeneratedKeys = true, keyProperty = "country.id")
。<2>
處,獲得真正的參數值。<3>
處,獲得主鍵的屬性的配置。<4>
處,調用#assignKeysToParam(...)
方法,設置主鍵們,到參數param
中。所以,后續流程,又回到了 「3.4.2」 咧。- 關於這個方法,胖友自己模擬下這個情況,調試下會比較好理解。😈 當然,也可以不理解,嘿嘿。
3.5 populateKeys
// Jdbc3KeyGenerator.java |
- 代碼比較簡單,胖友看下注釋即可。
4. SelectKeyGenerator
org.apache.ibatis.executor.keygen.SelectKeyGenerator
,實現 KeyGenerator 接口,基於從數據庫查詢主鍵的 KeyGenerator 實現類,適用於 Oracle、PostgreSQL 。
4.1 構造方法
// SelectKeyGenerator.java |
4.2 processBefore
// SelectKeyGenerator.java |
- 調用
#processGeneratedKeys(...)
方法。
4.3 processAfter
// SelectKeyGenerator.java |
- 也是調用
#processGeneratedKeys(...)
方法。
4.4 processGeneratedKeys
// SelectKeyGenerator.java |
<1>
處,有查詢主鍵的 SQL 語句,即keyStatement
對象非空。<2>
處,創建執行器,類型為 SimpleExecutor 。- 【重要】
<3>
處,調用Executor#query(...)
方法,執行查詢主鍵的操作。😈 簡單腦暴下,按照 SelectKeyGenerator 的思路,豈不是可以可以接入 SnowFlake 算法,從而實現分布式主鍵。 <4.1>
處,查不到結果,拋出 ExecutorException 異常。<4.2>
處,查詢的結果過多,拋出 ExecutorException 異常。-
<4.3>
處,創建 MetaObject 對象,訪問查詢主鍵的結果。-
<4.3.1>
處,單個主鍵,調用#setValue(MetaObject metaParam, String property, Object value)
方法,設置屬性到metaParam
中,相當於設置到parameter
中。代碼如下:// SelectKeyGenerator.java
private void setValue(MetaObject metaParam, String property, Object value) {
if (metaParam.hasSetter(property)) {
metaParam.setValue(property, value);
} else {
throw new ExecutorException("No setter found for the keyProperty '" + property + "' in " + metaParam.getOriginalObject().getClass().getName() + ".");
}
}- 簡單,胖友自己瞅瞅。
-
<4.3.2>
處,多個主鍵,調用#handleMultipleProperties(String[] keyProperties, MetaObject metaParam, MetaObject metaResult)
方法,遍歷,進行賦值。代碼如下:// SelectKeyGenerator.java
private void handleMultipleProperties(String[] keyProperties,
MetaObject metaParam, MetaObject metaResult) {
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]));
}
}
}- 最終,還是會調用
#setValue(...)
方法,進行賦值。
- 最終,還是會調用
-
4.5 示例
5. NoKeyGenerator
org.apache.ibatis.executor.keygen.NoKeyGenerator
,實現 KeyGenerator 接口,空的 KeyGenerator 實現類,即無需主鍵生成。代碼如下:
// NoKeyGenerator.java |