【MyBatis原始碼分析】insert方法、update方法、delete方法處理流程(下篇)

五月的倉頡發表於2017-06-07

Configuration的newStatementHandler分析

SimpleExecutor的doUpdate方法上文有分析過:

 1 public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
 2     Statement stmt = null;
 3     try {
 4       Configuration configuration = ms.getConfiguration();
 5       StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
 6       stmt = prepareStatement(handler, ms.getStatementLog());
 7       return handler.update(stmt);
 8     } finally {
 9       closeStatement(stmt);
10     }
11 }

這兩天重看第5行的newStatementHandler方法的時候,發現方法上文在這個方法中分析地太簡略了,這裡過一遍一下Configuration的newStatementHandler方法,方法的實現為:

1 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
2     StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
3     statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
4     return statementHandler;
5 }

第3行的程式碼是加入外掛沒什麼好看的,看下第2行的程式碼,StatementHandler介面真正例項化出來的是RoutingStatementHandler,構造方法實現為:

 1 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 2 
 3     switch (ms.getStatementType()) {
 4       case STATEMENT:
 5         delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
 6         break;
 7       case PREPARED:
 8         delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
 9         break;
10       case CALLABLE:
11         delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
12         break;
13       default:
14         throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
15     }
16 
17 }

RoutingStatementHandler同樣是裝飾器模式的實現,實現了StatementHandler介面並持有StatementHandler介面引用delegate。這裡StatementType的為PREPARED,因此執行的第7行的判斷,例項化出PreparedStatementHandler,例項化的過程為:

 1 protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 2     this.configuration = mappedStatement.getConfiguration();
 3     this.executor = executor;
 4     this.mappedStatement = mappedStatement;
 5     this.rowBounds = rowBounds;
 6 
 7     this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
 8     this.objectFactory = configuration.getObjectFactory();
 9 
10     if (boundSql == null) { // issue #435, get the key before calculating the statement
11       generateKeys(parameterObject);
12       boundSql = mappedStatement.getBoundSql(parameterObject);
13     }
14 
15     this.boundSql = boundSql;
16 
17     this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
18     this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
19 }

這裡的重點是BoundSql,它可以通過MappedStatement獲取到,BoundSql中儲存了幾個重要的內容:

  1. 引數物件本身
  2. 引數列表
  3. 待執行的SQL語句

有些基於MyBatis二次開發的框架通常都會拿到BoundSql中的SQL語句進行修改並重新設定進BoundSql中。

 

生成Statement

上文已經寫了生成Connection的流程,本文繼續看,首先還是再貼一下SimpleExecutor的prepareStatement方法:

1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
2     Statement stmt;
3     Connection connection = getConnection(statementLog);
4     stmt = handler.prepare(connection, transaction.getTimeout());
5     handler.parameterize(stmt);
6     return stmt;
7 }

接著就是第4行的程式碼,生成Statement了,第4行的程式碼實現為:

1 public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
2     return delegate.prepare(connection, transactionTimeout);
3 }

delegate上文是裝飾器模式中的被裝飾角色,其介面型別為StatementHandler,真實型別為PreparedStatementHandler,這個在最開頭的部分已經分析過了。看一下prepare方法實現:

 1 public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
 2     ErrorContext.instance().sql(boundSql.getSql());
 3     Statement statement = null;
 4     try {
 5       statement = instantiateStatement(connection);
 6       setStatementTimeout(statement, transactionTimeout);
 7       setFetchSize(statement);
 8       return statement;
 9     } catch (SQLException e) {
10       closeStatement(statement);
11       throw e;
12     } catch (Exception e) {
13       closeStatement(statement);
14       throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
15     }
16 }

第6行的程式碼設定的是查詢超時時間、第7行的程式碼設定的是接收的資料大小,就不跟進去看了,接著看下第6行的instantiateStatement方法實現:

 1 protected Statement instantiateStatement(Connection connection) throws SQLException {
 2     String sql = boundSql.getSql();
 3     if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
 4       String[] keyColumnNames = mappedStatement.getKeyColumns();
 5       if (keyColumnNames == null) {
 6         return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
 7       } else {
 8         return connection.prepareStatement(sql, keyColumnNames);
 9       }
10     } else if (mappedStatement.getResultSetType() != null) {
11       return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
12     } else {
13       return connection.prepareStatement(sql);
14     }
15 }

第2行,從boundSql中獲取真正的SQL語句,第一部分已經分析過了。拿到SQL語句之後,執行第3行與第5行的判斷,這裡就是我們熟悉的通過Connection拿Statement的程式碼了,通過prepareStatement方法獲取到PreparedStatement,其真實型別為com.mysql.jdbc.JDBC4PreparedStatement,是PreparedStatement的子類。

 

Statement引數設定

獲取了Statement後,下一步就是設定引數了,看一下設定引數的程式碼,還是回到SimpleExecutor的prepareStatement方法:

1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
2     Statement stmt;
3     Connection connection = getConnection(statementLog);
4     stmt = handler.prepare(connection, transaction.getTimeout());
5     handler.parameterize(stmt);
6     return stmt;
7 }

跟第5行的程式碼:

 1 public void parameterize(Statement statement) throws SQLException {
 2     parameterHandler.setParameters((PreparedStatement) statement);
 3 }

繼續跟第2行的程式碼:

 1 public void setParameters(PreparedStatement ps) {
 2     ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
 3     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
 4     if (parameterMappings != null) {
 5       for (int i = 0; i < parameterMappings.size(); i++) {
 6         ParameterMapping parameterMapping = parameterMappings.get(i);
 7         if (parameterMapping.getMode() != ParameterMode.OUT) {
 8           Object value;
 9           String propertyName = parameterMapping.getProperty();
10           if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
11             value = boundSql.getAdditionalParameter(propertyName);
12           } else if (parameterObject == null) {
13             value = null;
14           } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
15             value = parameterObject;
16           } else {
17             MetaObject metaObject = configuration.newMetaObject(parameterObject);
18             value = metaObject.getValue(propertyName);
19           }
20           TypeHandler typeHandler = parameterMapping.getTypeHandler();
21           JdbcType jdbcType = parameterMapping.getJdbcType();
22           if (value == null && jdbcType == null) {
23             jdbcType = configuration.getJdbcTypeForNull();
24           }
25           try {
26             typeHandler.setParameter(ps, i + 1, value, jdbcType);
27           } catch (TypeException e) {
28             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
29           } catch (SQLException e) {
30             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
31           }
32         }
33       }
34     }
35 }

最終執行的是第26行的程式碼,從26行的程式碼我們可以知道,引數設定到最後都是通過引數的TypeHandler來執行的,JDBC為我們預定義了很多TypeHandler,比如int值的TypeHandler就是IntegerTypeHandler,當然我們也可以定義自己的TypeHandler,通常來說繼承BaseTypeHandler就可以了。

但是在此之前,會獲取到Statement(setParameters方法形參)、佔位符位置號(for迴圈的遍歷引數i)、引數值(通過屬性名獲取)與jdbcType(配置在配置檔案中,預設為null),最終執行TypeHandler的setParameters方法,這是BaseTypeHandler中的一個方法:

 1 public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
 2     if (parameter == null) {
 3       if (jdbcType == null) {
 4         throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
 5       }
 6       try {
 7         ps.setNull(i, jdbcType.TYPE_CODE);
 8       } catch (SQLException e) {
 9         throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
10                 "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +
11                 "Cause: " + e, e);
12       }
13     } else {
14       try {
15         setNonNullParameter(ps, i, parameter, jdbcType);
16       } catch (Exception e) {
17         throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
18                 "Try setting a different JdbcType for this parameter or a different configuration property. " +
19                 "Cause: " + e, e);
20       }
21     }
22 }

這裡的引數不為null,走13行的else,執行setNonNullParameter方法,這是IntegerTypeHandler中的一個方法:

 1 public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
 2       throws SQLException {
 3     ps.setInt(i, parameter);
 4 }

這裡的程式碼就比較熟悉了,PreparedStatement的setInt方法。

 

執行更新操作並處理結果

最後一步,執行更新操作並對結果進行處理,回到SimpleExecuto的doUpdate方法:

 1 public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
 2     Statement stmt = null;
 3     try {
 4       Configuration configuration = ms.getConfiguration();
 5       StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
 6       stmt = prepareStatement(handler, ms.getStatementLog());
 7       return handler.update(stmt);
 8     } finally {
 9       closeStatement(stmt);
10     }
11 }

第6行已經準備好了Statement,第7行執行update操作並對結果進行處理並返回:

 1 public int update(Statement statement) throws SQLException {
 2     return delegate.update(statement);
 3 }

這裡的委託delegate前面已經說過了,其真實型別是PreparedStatementHandler,update方法的實現為:

1 public int update(Statement statement) throws SQLException {
2     PreparedStatement ps = (PreparedStatement) statement;
3     ps.execute();
4     int rows = ps.getUpdateCount();
5     Object parameterObject = boundSql.getParameterObject();
6     KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
7     keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
8     return rows;
9 }

第3行的execute方法是PreparedStatement中的方法,execute方法執行操作,然後第4行通過getUpdateCount()方法獲取本次操作更新了幾條資料,作為最終的值返回給使用者。

第5行的程式碼通過BoundSql獲取引數物件,這裡是MailDO物件,因為我們知道在插入場景下,開發者是有這種需求的,需要返回插入的主鍵id,此時會將主鍵id設定到MailDO中。

第6行的程式碼通過MappedStatement獲取KeyGenerator,一個主鍵生成器。

第7行的程式碼做了一個操作完畢的後置處理:

 1 public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
 2     processBatch(ms, stmt, getParameters(parameter));
 3 }

首先將物件包裝成集合型別,然後跟第2行的程式碼processBatch方法:

 1 public void processBatch(MappedStatement ms, Statement stmt, Collection<Object> parameters) {
 2     ResultSet rs = null;
 3     try {
 4       rs = stmt.getGeneratedKeys();
 5       final Configuration configuration = ms.getConfiguration();
 6       final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
 7       final String[] keyProperties = ms.getKeyProperties();
 8       final ResultSetMetaData rsmd = rs.getMetaData();
 9       TypeHandler<?>[] typeHandlers = null;
10       if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) {
11         for (Object parameter : parameters) {
12           // there should be one row for each statement (also one for each parameter)
13           if (!rs.next()) {
14             break;
15           }
16           final MetaObject metaParam = configuration.newMetaObject(parameter);
17           if (typeHandlers == null) {
18             typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd);
19           }
20           populateKeys(rs, metaParam, keyProperties, typeHandlers);
21         }
22       }
23     } catch (Exception e) {
24       throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
25     } finally {
26       if (rs != null) {
27         try {
28           rs.close();
29         } catch (Exception e) {
30           // ignore
31         }
32       }
33     }
34 }

簡單說這裡就是遍歷集合,通過JDBC4PreparedStatement的getGeneratedKeys獲取ResultSet,然後從ResultSet中使用getLong方法獲取生成的主鍵,設定到MailDO中。完成整個操作。

最後,本文演示的是insert資料的update方法流程,前文已經說過insert、update、delete在MyBatis中都是一樣的,因此update、delete也是一樣的操作,這裡就不再贅述了。

相關文章