天天看點

#yyds幹貨盤點# mybatis源碼解讀:executor包(主鍵自增功能)

 mybatis源碼解讀:executor包(主鍵自增功能)

executor執行器包作為mybatis的核心将其他各個包凝聚在一起,會調用配置解析包解析出配置資訊,會依賴基礎包提供的基礎功能,最終executor包将所有的操作串連在一起,通過session包向外暴露出一套完整的服務。

1.主鍵自增功能

在進行資料插入操作時,經常需要一個自增生成的主鍵編号,這既能保證主鍵的唯一性, 又能保證主鍵的連續性。mybatis的executor包中的keygen子包提供主鍵自增功能。

1.主鍵自增的配置與生效

mybatis通過KeyGenerator接口提供主鍵自增功能,而KeyGenerator的實作類有Jdbc3KeyGenenrator、SelectKeyGenerator、NoKeyGenerator這3種。在實際使用時,這3種實作類中隻能有一種實作類生效,而如果生效的是NoKeyGenerator則表明不具有任何主鍵自增功能。

在XMLStatementBuilder類中有用到主鍵功能被解析。

/**
 *       // 單條語句的解析器,解析類似:
 *       //   <select id="selectUser" resultType="User">
 *       //    select * from `user` where id = #{id}
 *       //  </select>
 */
public class XMLStatementBuilder extends BaseBuilder {
   /* 解析select、insert、update、delete這四類節點
   */
  public void parseStatementNode() {
    // 讀取目前節點的id與databaseId
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    // 驗證id與databaseId是否比對。MyBatis允許多資料庫配置,是以有些語句隻對特定資料庫生效
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    // 讀取節點名
    String nodeName = context.getNode().getNodeName();
    // 讀取和判斷語句類型
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 處理語句中的Include節點
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());
    // 參數類型
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    // 語句類型
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // 處理SelectKey節點,在這裡會将KeyGenerator加入到Configuration.keyGenerators中
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // 此時,<selectKey> 和 <include> 節點均已被解析完畢并被删除,開始進行SQL解析
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    // 判斷是否已經有解析好的KeyGenerator
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      // 全局或者本語句隻要啟用自動key生成,則使用key生成
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // 讀取各個配置屬性
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");
    // 在MapperBuilderAssistant的幫助下建立MappedStatement對象,并寫入到Configuration中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
  }      

processSelectKeyNodes方法最終解析了selectKey節點從xml中删除了,而解析出來的資訊則放入了configuration的keyGenerators中。之後如果沒有解析好的keyGenerator,則會更根據useGeneratedKeys判斷是否使用Jdbc3KeyGenerator.

 最終KeyGenerator資訊會被儲存在整個Statement中。在Statement執行時直接調用KeyGenerator中的processBefore方法和processAfter方法即可,必然會有Jdbc3KeyGenerator、SelectKeyGenerator、NoKeyGenerator三者中的一個來實際執行者兩個方法。

2. Jdbc3KeyGenerator類

2.1. Jdbc3KeyGenerator類的功能

資料庫已經支援主鍵自增了,那麼Jdbc3KeyGenerator類存在的意義是什麼呢?

它存在的意義是提供自增主鍵的回寫功能,能夠将資料庫中産生的id值回寫給java對象本身。

2.2. Jdbc3KeyGenerator類的原理

public class Jdbc3KeyGenerator implements KeyGenerator {
 @Override
  public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    processBatch(ms, stmt, parameter);
  }
  public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
    // 拿到主鍵的屬性名
    final String[] keyProperties = ms.getKeyProperties();
    if (keyProperties == null || keyProperties.length == 0) {
      // 沒有主鍵則無需操作
      return;
    }
    // 調用Statement對象的getGeneratedKeys方法擷取自動生成的主鍵值
    try (ResultSet rs = stmt.getGeneratedKeys()) {
      // 擷取輸出結果的描述資訊
      final ResultSetMetaData rsmd = rs.getMetaData();
      final Configuration configuration = ms.getConfiguration();
      if (rsmd.getColumnCount() < keyProperties.length) {
        // 主鍵數目比結果的總字段數目還多,則發生了錯誤。
        // 但因為此處是擷取主鍵這樣的附屬操作,是以忽略錯誤,不影響主要工作
      } 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);
    }
  }
}