mybatis源碼閱讀(九) ---KeyGenerator瞭解一下

不一樣的數據庫產品對應的主鍵生成策略不同,好比Oracle,DB2等數據庫產品是經過sequence實現主鍵id自增的,在執行insert語句以前必須有明確的指定主鍵值,而mysql等數據困在執行insert語句時,能夠不指定主鍵,由數據庫自動生成自增主鍵。java

1.KeyGenerator定義

public interface KeyGenerator {

  //在執行insert以前執行,設置屬性 order = "BEFORE"
  void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

  // 在執行insert以後執行,設置屬性order="AFTER"
  void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

}

mybatis 提供了三個KeyGenerator接口的實現node

Jdbc3KeyGenerator:用於處理數據庫支持自增主鍵的狀況,如MySQL的auto_increment。mysql

NoKeyGenerator:空實現,不須要處理主鍵。sql

SelectKeyGenerator:用於處理數據庫不支持自增主鍵的狀況,好比Oracle,postgres的sequence序列。數據庫

先看看jdbc是怎麼返回主鍵的apache

        Class.forName("com.mysql.jdbc.Driver");

        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "123");
        conn.setAutoCommit(false);
        PreparedStatement pstm = conn.prepareStatement("insert into students(name, email) values(?, ?)",
        Statement.RETURN_GENERATED_KEYS);

        pstm.setString(1, "name1");
        pstm.setString(2, "email1");
        pstm.addBatch();
        pstm.setString(1, "name2");
        pstm.setString(2, "email2");
        pstm.addBatch();
        pstm.executeBatch();
        // 返回自增主鍵值
        ResultSet rs = pstm.getGeneratedKeys();
        while (rs.next()) {
        Object value = rs.getObject(1);
        System.out.println(value);
        }
        conn.commit();
        rs.close();
        pstm.close();
        conn.close();

以上代碼,僅做爲演示使用。Mybatis是對JDBC的封裝,其Jdbc3KeyGenerator類,就是使用上面的原理,來返回數據庫生成的主鍵值的。mybatis

2.KeyGenerator 初始化

在以前介紹的mapper文件解析中XMLStatementBuilder.parseStatementNode()方法中有以下代碼片斷來初始化KeyGeneratorapp

KeyGenerator keyGenerator;
// 獲取selectKey節點對應的selectKeyGenerator的id
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {//SQL節點下是否存在<selectKey>節點
  keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
  // 根據SQL節點的useGeneratedKeys屬性值、mybatis-config.xml中的全局useGeneratedKeys配置,
  // 以及是不是insert語句 以爲使用哪一個KeyGenerator接口
  keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
      configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
      ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}

 

3. Jdbc3KeyGenerator源碼解讀

@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  processBatch(ms, stmt, getParameters(parameter));
}

public void processBatch(MappedStatement ms, Statement stmt, Collection<Object> parameters) {
  ResultSet rs = null;
  try {
    // 得到返回的主鍵值結果集
    rs = stmt.getGeneratedKeys();
    final Configuration configuration = ms.getConfiguration();
    final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    final String[] keyProperties = ms.getKeyProperties();
    final ResultSetMetaData rsmd = rs.getMetaData();
    TypeHandler<?>[] typeHandlers = null;
    if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) {
        // 給參數object對象的屬性賦主鍵值(批量插入,多是多個)
        for (Object parameter : parameters) {
        // there should be one row for each statement (also one for each parameter)
        if (!rs.next()) {
          break;
        }
        final MetaObject metaParam = configuration.newMetaObject(parameter);
        if (typeHandlers == null) {
          typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd);
        }
          // 賦值
        populateKeys(rs, metaParam, keyProperties, typeHandlers);
      }
    }
  } catch (Exception e) {
    throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
  } finally {
    if (rs != null) {
      try {
        rs.close();
      } catch (Exception e) {
        // ignore
      }
    }
  }
}
private void populateKeys(ResultSet rs, MetaObject metaParam, String[] keyProperties, TypeHandler<?>[] typeHandlers) throws SQLException {
    for (int i = 0; i < keyProperties.length; i++) {
        // 主鍵字段,多是多個(通常狀況下,是一個)
    String property = keyProperties[i];
    TypeHandler<?> th = typeHandlers[i];
    if (th != null) {
      Object value = th.getResult(rs, i + 1);
        // 反射賦值
      metaParam.setValue(property, value);
    }
  }
}

mapper配置方式。ide

<insert id="insert"  useGeneratedKeys="true" keyProperty="type_id">

4.SelectKeyGenerator

對於不支持自動生成的自增主鍵的數據庫,例如Oracle,用戶能夠利用SelectKeyGenerator來獲取生成的主鍵的功能,SelectKeyGenerator中的processBefore()和processAfter()方法的實現都是調用的processGeneratedKeys方法。源碼以下post

@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  if (executeBefore) {
    processGeneratedKeys(executor, ms, parameter);
  }
}

@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  if (!executeBefore) {
    processGeneratedKeys(executor, ms, parameter);
  }
}
private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
  try {
    if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
        // 獲取<selectKey>節點上KeyProperties配置的屬性名稱,它表示主鍵對應的屬性
      String[] keyProperties = keyStatement.getKeyProperties();
      final Configuration configuration = ms.getConfiguration();
      final MetaObject metaParam = configuration.newMetaObject(parameter);
      if (keyProperties != null) {
        // Do not close keyExecutor.
        // The transaction will be closed by parent executor.
        Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
        // 執行 SQL語句 獲得主鍵值
        List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
        // 檢測values集合的長度,改集合只能維1
        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 {
            // 建立主鍵對象對應的MetaObject對象
          MetaObject metaResult = configuration.newMetaObject(values.get(0));
          if (keyProperties.length == 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 {
            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);
  }
}

配置以下

<selectKey order="AFTER" resultType="java.lang.Integer" keyProperty="po.id">
    <choose>
        <when  test="po.DBTYPE =='ORACLE'">
            SELECT mon_deviceindex_id_seq.NEXTVAL-1 from DUAL
        </when>
        <otherwise>
            select last_value from mon_deviceindex_id_seq
       </otherwise>
    </choose>
</selectKey>

(1)selectKey 初始化過程

org.apache.ibatis.builder.xml.XMLStatementBuilder.parseSelectKeyNode()解析<selectKey>元素,構建SelectKeyGenerator的源碼。

private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
  // 獲取所有的selectKey節點
  List<XNode> selectKeyNodes = context.evalNodes("selectKey");
  // 解析selectKey節點
  if (configuration.getDatabaseId() != null) {
    parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
  }
  parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
  // 刪除selectKey節點
  removeSelectKeyNodes(selectKeyNodes);
}

private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
  for (XNode nodeToHandle : list) {
    String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    String databaseId = nodeToHandle.getStringAttribute("databaseId");
    if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
      parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
    }
  }
}

private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
  String resultType = nodeToHandle.getStringAttribute("resultType");
  Class<?> resultTypeClass = resolveClass(resultType);
  StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
  String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
  boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));

  //defaults
  boolean useCache = false;
  boolean resultOrdered = false;
  KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
  Integer fetchSize = null;
  Integer timeout = null;
  boolean flushCache = false;
  String parameterMap = null;
  String resultMap = null;
  ResultSetType resultSetTypeEnum = null;

  // 生成SqlSource
  SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
  // selectKey節點中只能配置select語句
  SqlCommandType sqlCommandType = SqlCommandType.SELECT;

  // 建立MappedStatement對象,並添加到configuration的mappedStatements集合中保存
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);

  id = builderAssistant.applyCurrentNamespace(id, false);

  MappedStatement keyStatement = configuration.getMappedStatement(id, false);
  // 建立對應的KeyGenerator(主鍵自增策略),添加到configuration中
  configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}

所以,只有SelectKeyGenerator會保存到Configuration對象的Map<String, KeyGenerator> keyGenerators屬性當中。<selectKey>元素,會被Mybatis解析爲一個MappedStatement對象,並做爲構造參數傳遞至SelectKeyGenerator內保存起來。

至此,每個MappedStatement對象,都恰當的綁定了一個KeyGenerator對象,就能夠開始工做了。

5. KeyGenerator的使用過程

keyGenerator.processBefore()方法調用時機:

org.apache.ibatis.executor.statement.BaseStatementHandler.BaseStatementHandler()構造方法源碼。

if (boundSql == null) { // issue #435, get the key before calculating the statement
  // 獲取主鍵
  generateKeys(parameterObject);
  boundSql = mappedStatement.getBoundSql(parameterObject);
}
protected void generateKeys(Object parameter) {
  KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
  ErrorContext.instance().store();
  keyGenerator.processBefore(executor, mappedStatement, null, parameter);
  ErrorContext.instance().recall();
}

即,建立StatementHandler對象時,就會執行keyGenerator.processBefore()方法。keyGenerator.processAfter()方法,天然就是Statement執行後執行了。

org.apache.ibatis.executor.statement.SimpleStatementHandler.update(Statement)方法源碼。其餘的StatementHandler都是相似的。

@Override
public int update(Statement statement) throws SQLException {
  String sql = boundSql.getSql();
  Object parameterObject = boundSql.getParameterObject();
  KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
  int rows;
  if (keyGenerator instanceof Jdbc3KeyGenerator) {
    statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
    rows = statement.getUpdateCount();
    keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
  } else if (keyGenerator instanceof SelectKeyGenerator) {
    statement.execute(sql);
    rows = statement.getUpdateCount();
    keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
  } else {
    statement.execute(sql);
    rows = statement.getUpdateCount();
  }
  return rows;
}

 

注意:因爲selectKey自己返回單個序列主鍵值,也就沒法支持批量insert操做並返回主鍵id列表了。若是要執行批量insert,請選擇使用for循環執行屢次插入操做。

相關文章
相關標籤/搜索