[MyBatis Source Code Analysis] Processing flow of insert method, update method and delete method (next part)

Keywords: Java SQL JDBC Mybatis MySQL

New Statement Handler Analysis of Configuration

SimpleExecutor's doUpdate method has been analyzed above:

 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 }

When I look at the newStatementHandler method in line 5 these two days, I find that the method mentioned above is too simple to analyze. Here, I will go through Configuration's newStatementHandler method. The method is implemented as follows:

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 }

The third line of code is to add a plug-in is not very attractive. Look at the second line of code. The StatementHandler interface is actually instantiated as Routing StatementHandler. The construction method is as follows:

 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 }

Routing StatementHandler is also an implementation of the decorator pattern, which implements the StatementHandler interface and holds the StatementHandler interface to reference delegate. Here the StatementType is PREPARED, so the judgment in line 7 is executed to instantiate the PreparedStatementHandler, and the instantiation process is as follows:

 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 }

The focus here is BoundSql, which can be obtained through MappedStatement, which stores several important contents:

  1. Parametric object itself
  2. parameter list
  3. SQL statement to be executed

Some frameworks based on MyBatis secondary development are usually modified by the SQL statements in BoundSql and reset into BoundSql.

 

Generating Statement

The process of generating Connection has been written above. Let's continue with this article. First, we will paste SimpleExecutor's prepareStatement method:

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 }

Then the fourth line of code generates the Statement, and the fourth line of code is implemented as follows:

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

delegate is the decorated role in the decorator mode above. Its interface type is Statement Handler, and the real type is Prepared Statement Handler. This has been analyzed in the first part. Look at the prepare method implementation:

 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 }

Line 6 sets the query timeout and line 7 sets the size of the data received, so we won't follow up. Then look at the instantiateStatement implementation of line 6:

 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 }

Line 2, get the real SQL statement from boundSql, and the first part has been analyzed. After you get the SQL statement, execute the judgment on line 3 and line 5. Here is the familiar code to get the Statement through Connection. PreparedStatement is obtained through the preparedStatement method. Its real type is com. mysql. jdbc. JDBC 4 PreparedStatement, which is a subclass of PreparedStatement.

 

Statement parameter setting

After obtaining the Statement, the next step is to set the parameters. Look at the code for setting the parameters, or go back to SimpleExecutor's prepareStatement method:

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 }

Following line 5:

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

Continue with line 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 }

The final execution is the 26th line of code. From the 26th line of code, we can see that the parameter settings are executed through the parameter TypeHandler. JDBC has predefined many TypeHandlers for us, such as IntegerTypeHandler for int value. Of course, we can also define our own TypeHandler. Usually, we can inherit BaseTypeHandler.

But before that, you get Statement (setParameters method parameter), placeholder position number (for loop traversal parameter i), parameter value (obtained by attribute name) and jdbcType (configured in configuration file, default is null), and finally execute the setParameters method of TypeHandler, which is a method of 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 }

Here the parameter is not null, go 13 lines else, and execute the setNonNullParameter method, which is one of the methods in IntegerTypeHandler:

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

The code here is familiar with the setInt method of PreparedStatement.

 

Perform update operations and process results

The last step is to perform the update operation and process the results, back to SimpleExecuto's doUpdate method:

 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 }

Line 6 is ready for Statement, and line 7 performs the update operation and processes the results and returns:

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

The delegate here, as mentioned earlier, is a real type of PreparedStatementHandler, and the update method is implemented as follows:

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 }

The execute method in line 3 is the method in the PreparedStatement, and the execute method performs the operation. The fourth line retrieves several updated data from the operation through the getUpdateCount() method and returns it to the user as the final value.

Line 5 gets the parameter object through BoundSql, which is the MailDO object, because we know that in the insertion scenario, the developer needs to return the inserted primary key id, which is then set to MailDO.

Line 6 uses MappedStatement to get KeyGenerator, a primary key generator.

Line 7 does a post-processing after the completion of the operation:

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

First wrap the object as a collection type, then follow the code processBatch method in line 2:

 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 }

Simply put, this is to traverse the collection, get the ResultSet through getGeneratedKeys of JDBC 4 PreparedStatement, and then use the getLong method to get the generated primary key from the ResultSet and set it into MailDO. Complete the whole operation.

Finally, this article demonstrates the update method flow of insert data. As mentioned earlier, insert, update and delete are the same in MyBatis, so update and delete are the same operation, which will not be repeated here.

Posted by xylex on Mon, 24 Jun 2019 13:16:40 -0700