不一樣的數據庫產品對應的主鍵生成策略不同,好比Oracle,DB2等數據庫產品是經過sequence實現主鍵id自增的,在執行insert語句以前必須有明確的指定主鍵值,而mysql等數據困在執行insert語句時,能夠不指定主鍵,由數據庫自動生成自增主鍵。java
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
在以前介紹的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; }
@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">
對於不支持自動生成的自增主鍵的數據庫,例如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>
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對象,就能夠開始工做了。
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循環執行屢次插入操做。