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中儲存了幾個重要的內容:
- 引數物件本身
- 引數列表
- 待執行的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也是一樣的操作,這裡就不再贅述了。