mybatis-KeyGenerator


1. 概述

本文,我們來分享 SQL 執行的第三部分,keygen 包。整體類圖如下:類圖

  • 我們可以看到,整體是以 KeyGenerator 為核心。所以,本文主要會看到的就是 KeyGenerator 對自增主鍵的獲取。

2. KeyGenerator

org.apache.ibatis.executor.keygen.KeyGenerator ,主鍵生成器接口。代碼如下:

// KeyGenerator.java

public interface KeyGenerator {

// SQL 執行前
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

// SQL 執行后
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

}
  • 可在 SQL 執行之前或之后,進行處理主鍵的生成。
  • 實際上,KeyGenerator 類的命名雖然包含 Generator ,但是目前 MyBatis 默認的 KeyGenerator 實現類,都是基於數據庫來實現主鍵自增的功能。
  • parameter 參數,指的是什么呢?以下面的方法為示例:

    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert({"insert into country (countryname,countrycode) values (#{countryname},#{countrycode})"})
    int insertBean(Country country);
    • 上面的,country 方法參數,就是一個 parameter 參數。
    • KeyGenerator 在獲取到主鍵后,會設置回 parameter 參數的對應屬性。

KeyGenerator 有三個子類,如下圖所示:類圖

  • 具體的,我們下面逐小節來分享。

3. Jdbc3KeyGenerator

org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator ,實現 KeyGenerator 接口,基於 Statement#getGeneratedKeys() 方法的 KeyGenerator 實現類,適用於 MySQL、H2 主鍵生成。

3.1 構造方法

// Jdbc3KeyGenerator.java

/**
* A shared instance.
*
* 共享的單例
*
* @since 3.4.3
*/
public static final Jdbc3KeyGenerator INSTANCE = new Jdbc3KeyGenerator();
  • 單例。

3.2 processBefore

@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// do nothing
}
  • 空實現。因為對於 Jdbc3KeyGenerator 類的主鍵,是在 SQL 執行后,才生成。

3.3 processAfter

// Jdbc3KeyGenerator.java

@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
processBatch(ms, stmt, parameter);
}
  • 調用 #processBatch(Executor executor, MappedStatement ms, Statement stmt, Object parameter) 方法,處理返回的自增主鍵。單個 parameter 參數,可以認為是批量的一個特例。

3.4 processBatch

// Jdbc3KeyGenerator.java

public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
// <1> 獲得主鍵屬性的配置。如果為空,則直接返回,說明不需要主鍵
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
ResultSet rs = null;
try {
// <2> 獲得返回的自增主鍵
rs = stmt.getGeneratedKeys();
final Configuration configuration = ms.getConfiguration();
if (rs.getMetaData().getColumnCount() >= keyProperties.length) {
// <3> 獲得唯一的參數對象
Object soleParam = getSoleParameter(parameter);
if (soleParam != null) {
// <3.1> 設置主鍵們,到參數 soleParam 中
assignKeysToParam(configuration, rs, keyProperties, soleParam);
} else {
// <3.2> 設置主鍵們,到參數 parameter 中
assignKeysToOneOfParams(configuration, rs, keyProperties, (Map<?, ?>) parameter);
}
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
} finally {
// <4> 關閉 ResultSet 對象
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
// ignore
}
}
}
}
  • <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

/**
* 獲得唯一的參數對象
*
* 如果獲得不到唯一的參數對象,則返回 null
*
* @param parameter 參數對象
* @return 唯一的參數對象
*/
private Object getSoleParameter(Object parameter) {
// <1> 如果非 Map 對象,則直接返回 parameter
if (!(parameter instanceof ParamMap || parameter instanceof StrictMap)) {
return parameter;
}
// <3> 如果是 Map 對象,則獲取第一個元素的值
// <2> 如果有多個元素,則說明獲取不到唯一的參數對象,則返回 null
Object soleParam = null;
for (Object paramValue : ((Map<?, ?>) parameter).values()) {
if (soleParam == null) {
soleParam = paramValue;
} else if (soleParam != paramValue) {
soleParam = null;
break;
}
}
return soleParam;
}
  • <1> 處,如下可以符合這個條件。代碼如下:

    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert({"insert into country (countryname,countrycode) values (#{country.countryname},#{country.countrycode})"})
    int insertNamedBean(@Param("country") Country country);
  • <2> 處,如下可以符合這個條件。代碼如下:

    @Options(useGeneratedKeys = true, keyProperty = "country.id")
    @Insert({"insert into country (countryname, countrycode) values (#{country.countryname}, #{country.countrycode})"})
    int insertMultiParams_keyPropertyWithWrongParamName2(@Param("country") Country country,
    @Param("someId") Integer someId);
    • 雖然有 country 和 someId 參數,但是最終會被封裝成一個 parameter 參數,類型為 ParamMap 類型。為什么呢?答案在 ParamNameResolver#getNamedParams(Object[] args) 方法中。
    • 如果是這個情況,獲得的主鍵,會設置回 country 的 id 屬性,因為注解上的 keyProperty = "country.id" 配置。
    • 😈 此處比較繞,也相對用的少。
  • <3> 處,如下可以符合這個條件。代碼如下:

    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert({"insert into country (countryname, countrycode) values (#{country.countryname}, #{country.countrycode})"})
    int insertMultiParams_keyPropertyWithWrongParamName3(@Param("country") Country country);
    • 相比 <2> 的示例,主要是 keyProperty = "id" 的修改,和去掉了 @Param("someId") Integer someId 參數。
    • 實際上,這種情況,和 <1> 是類似的。
  • 三種情況,<2> 和 <3> 有點復雜,胖友實際上,理解 <1> 即可。

3.4.2 assignKeysToParam

// Jdbc3KeyGenerator.java

private void assignKeysToParam(final Configuration configuration, ResultSet rs, final String[] keyProperties, Object param)
throws SQLException {
final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
final ResultSetMetaData rsmd = rs.getMetaData();
// Wrap the parameter in Collection to normalize the logic.
// <1> 包裝成 Collection 對象
Collection<?> paramAsCollection;
if (param instanceof Object[]) {
paramAsCollection = Arrays.asList((Object[]) param);
} else if (!(param instanceof Collection)) {
paramAsCollection = Collections.singletonList(param);
} else {
paramAsCollection = (Collection<?>) param;
}
TypeHandler<?>[] typeHandlers = null;
// <2> 遍歷 paramAsCollection 數組
for (Object obj : paramAsCollection) {
// <2.1> 順序遍歷 rs
if (!rs.next()) {
break;
}
// <2.2> 創建 MetaObject 對象
MetaObject metaParam = configuration.newMetaObject(obj);
// <2.3> 獲得 TypeHandler 數組
if (typeHandlers == null) {
typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd);
}
// <2.4> 填充主鍵們
populateKeys(rs, metaParam, keyProperties, typeHandlers);
}
}
  • <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

protected void assignKeysToOneOfParams(final Configuration configuration, ResultSet rs, final String[] keyProperties,
Map<?, ?> paramMap) throws SQLException {
// Assuming 'keyProperty' includes the parameter name. e.g. 'param.id'.
// <1> 需要有 `.` 。
int firstDot = keyProperties[0].indexOf('.');
if (firstDot == -1) {
throw new ExecutorException(
"Could not determine which parameter to assign generated keys to. "
+ "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
+ "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
+ paramMap.keySet());
}
// 獲得真正的參數值
String paramName = keyProperties[0].substring(0, firstDot);
Object param;
if (paramMap.containsKey(paramName)) {
param = paramMap.get(paramName);
} else {
throw new ExecutorException("Could not find parameter '" + paramName + "'. "
+ "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
+ "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
+ paramMap.keySet());
}
// Remove param name from 'keyProperty' string. e.g. 'param.id' -> 'id'
// 獲得主鍵的屬性的配置
String[] modifiedKeyProperties = new String[keyProperties.length];
for (int i = 0; i < keyProperties.length; i++) {
if (keyProperties[i].charAt(firstDot) == '.' && keyProperties[i].startsWith(paramName)) {
modifiedKeyProperties[i] = keyProperties[i].substring(firstDot + 1);
} else {
throw new ExecutorException("Assigning generated keys to multiple parameters is not supported. "
+ "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
+ "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
+ paramMap.keySet());
}
}
// 設置主鍵們,到參數 param 中
assignKeysToParam(configuration, rs, modifiedKeyProperties, param);
}
  • <1> 處,需要有 . 。例如:@Options(useGeneratedKeys = true, keyProperty = "country.id") 。
  • <2> 處,獲得真正的參數值。
  • <3> 處,獲得主鍵的屬性的配置。
  • <4> 處,調用 #assignKeysToParam(...) 方法,設置主鍵們,到參數 param 中。所以,后續流程,又回到了 「3.4.2」 咧。
  • 關於這個方法,胖友自己模擬下這個情況,調試下會比較好理解。😈 當然,也可以不理解,嘿嘿。

3.5 populateKeys

// Jdbc3KeyGenerator.java

private void populateKeys(ResultSet rs, MetaObject metaParam, String[] keyProperties, TypeHandler<?>[] typeHandlers) throws SQLException {
// 遍歷 keyProperties
for (int i = 0; i < keyProperties.length; i++) {
// 獲得屬性名
String property = keyProperties[i];
// 獲得 TypeHandler 對象
TypeHandler<?> th = typeHandlers[i];
if (th != null) {
// 從 rs 中,獲得對應的 值
Object value = th.getResult(rs, i + 1);
// 設置到 metaParam 的對應 property 屬性種
metaParam.setValue(property, value);
}
}
}
  • 代碼比較簡單,胖友看下注釋即可。

4. SelectKeyGenerator

org.apache.ibatis.executor.keygen.SelectKeyGenerator ,實現 KeyGenerator 接口,基於從數據庫查詢主鍵的 KeyGenerator 實現類,適用於 Oracle、PostgreSQL 。

4.1 構造方法

// SelectKeyGenerator.java

public static final String SELECT_KEY_SUFFIX = "!selectKey";

/**
* 是否在 before 階段執行
*
* true :before
* after :after
*/
private final boolean executeBefore;
/**
* MappedStatement 對象
*/
private final MappedStatement keyStatement;

public SelectKeyGenerator(MappedStatement keyStatement, boolean executeBefore) {
this.executeBefore = executeBefore;
this.keyStatement = keyStatement;
}

4.2 processBefore

// SelectKeyGenerator.java

@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (executeBefore) {
processGeneratedKeys(executor, ms, parameter);
}
}
  • 調用 #processGeneratedKeys(...) 方法。

4.3 processAfter

// SelectKeyGenerator.java

@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
if (!executeBefore) {
processGeneratedKeys(executor, ms, parameter);
}
}
  • 也是調用 #processGeneratedKeys(...) 方法。

4.4 processGeneratedKeys

// SelectKeyGenerator.java

private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
try {
// <1> 有查詢主鍵的 SQL 語句,即 keyStatement 對象非空
if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
String[] keyProperties = keyStatement.getKeyProperties();
final Configuration configuration = ms.getConfiguration();
final MetaObject metaParam = configuration.newMetaObject(parameter);
// Do not close keyExecutor.
// The transaction will be closed by parent executor.
// <2> 創建執行器,類型為 SimpleExecutor
Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
// <3> 執行查詢主鍵的操作
List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
// <4.1> 查不到結果,拋出 ExecutorException 異常
if (values.size() == 0) {
throw new ExecutorException("SelectKey returned no data.");
// <4.2> 查詢的結果過多,拋出 ExecutorException 異常
} else if (values.size() > 1) {
throw new ExecutorException("SelectKey returned more than one value.");
} else {
// <4.3> 創建 MetaObject 對象,訪問查詢主鍵的結果
MetaObject metaResult = configuration.newMetaObject(values.get(0));
// <4.3.1> 單個主鍵
if (keyProperties.length == 1) {
// 設置屬性到 metaParam 中,相當於設置到 parameter 中
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));
}
// <4.3.2> 多個主鍵
} else {
// 遍歷,進行賦值
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);
}
}
  • <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

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
}

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM