Mybatis @Flush annotation analysis

Mybatis @Flush annotation analysis

When looking at the source code, I found the @ Flush annotation. I haven't used it before, so I have this article

Note: the type of actuator here must be BatchExecutor

Let's start with an example

  public void testShouldSelectClassUsingMapperClass(){
      //Specify that the type of executor for this query is BATCH. The corresponding type is BatchExecutor
      SqlSession session = sqlMapper.openSession(ExecutorType.BATCH)
      ClassMapper mapper = session.getMapper(ClassMapper.class);
      long start = System.currentTimeMillis();
      // This query is the normal query method
      List<ClassDo> classDos = mapper.listClassOwnerStudentByClassId(1, new String[]{"son of a bitch"});
      System.out.println("Start update");
      // Here are two update operations
      System.out.println(mapper.updateClassNameById(1, "Class 1 of planning section 1"));
      System.out.println(mapper.updateClassNameById(2, "Class 1 of planning section 2"));
      System.out.println("End update");
      // Call the flush method, which I wrote myself. The return value is list < batchresult >, and the flush method has no corresponding Xml.
      // Only when this method is called can the database be really updated
      for (BatchResult flush : mapper.flush()) {
        //Updated sql
        //Updated parameters
         //The number of rows affected by the update, including the above getParameterObjects, and the return values are an array
         // This batch is in the dimension of statement. Therefore, the above two queries belong to one statement, but the parameters are different
        // Therefore, getParameterObjects is an array, and getUpdateCounts must also be an array.
public interface ClassMapper {
  // This is not, must not
    List<BatchResult> flush();

   //The following two have XMl files
    List<ClassDo> listClassOwnerStudentByClassId(Integer orderId,@Param("array") String[] names);

   int updateClassNameById(@Param("id") Integer id,@Param("className") String className);


Looking at the results

1. Role of notes

First look at the comments on the source code

 * The maker annotation that invoke a flush statements via Mapper interface.
 * <pre>
 * public interface UserMapper {
 *   &#064;Flush
 *   List&lt;BatchResult&gt; flush();
 * }
 * </pre>
public @interface Flush {

This annotation is a tag interface that calls refresh statements through Mapper's interface

2. How does annotation work?

When the method is called, the invoke method of MapperProxy will be called, where PlainMethodInvoker will be built, and MapperMethod will be wrapped in PlainMethodInvoker. Build SqlCommand and MethodSignature in MapperMethod. The first @ Flush operation is to build SqlCommand object. If the current method is modified by @ Flush and there is no corresponding Mapper, the query will be changed to SqlCommandType.FLUSH

  1. The first step is to parse the @ Flush annotation.
   public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      // Method name
      final String methodName = method.getName();
     // method class
      final Class<?> declaringClass = method.getDeclaringClass();
      // Get MappedStatement from Configuration by fully qualified type name and method name.
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
     // If ms is null. The premise of using Flush is not to write MapperStatement.
      if (ms == null) {
        if (method.getAnnotation(Flush.class) != null) {
          //The type of this query is FLUSH.
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
      } else {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
  1. Use @ Flush annotation.

In the execute method of MapperMethod, the Type of this operation will be determined by Type. It has been determined that its Type is FLUSH.

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result; //Call it strategic model
    // The type is FLUSH
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
      case FLUSH:
        // The key point is the 'flushStatements' method
        result = sqlSession.flushStatements();
        throw new BindingException("Unknown execution method for: " + command.getName());
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    return result;

Continue to sqlSession.flushStatements()
Follow the above code and click on it. Polymorphism will occur here. One is to the doFlushStatements method of BatchExecutor, and the other is the doFlushStatements method of simpleexecution. The most important thing here is to take the first case. But I will continue to analyze both cases.

  1. BatchExecutor

    statementList is the attribute of BatchExecutor. In this method, the Statement previously added to statementList will be actually executed, and then the updated rows will be encapsulated as an attribute of batchResult. This is batch update.

    How many questions?

    Why is statementList a List

    Because there may be multiple statements in an update operation. Here is a List,

    Why is batchResult a List and parameterObjects a List,

    Because multiple statements correspond to multiple results, parameterObjects is because a Statement may be called multiple times and the parameter values are different.

    Where are the statementList and batchResult values added?

      public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
        try {
          List<BatchResult> results = new ArrayList<>();
          if (isRollback) {
            return Collections.emptyList();
          //Traversal statement
          for (int i = 0, n = statementList.size(); i < n; i++) {
            //Set query parameters
            Statement stmt = statementList.get(i);
            BatchResult batchResult = batchResultList.get(i);
            try {
              // Here, the executeBatch of the statement will be called to batch execute the previously prepared sql.
              // So, here's the question. Where was the statement added? Where is batchResult added?
              MappedStatement ms = batchResult.getMappedStatement();
              //Get updated action
              List<Object> parameterObjects = batchResult.getParameterObjects();
              // If KeyGenerator is used.
              KeyGenerator keyGenerator = ms.getKeyGenerator();
              if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
                Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
                jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
              } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141
                for (Object parameter : parameterObjects) {
                  keyGenerator.processAfter(this, ms, stmt, parameter);
              //Close Statement
            } catch (BatchUpdateException e) {
              StringBuilder message = new StringBuilder();
                  .append(" (batch index #")
                  .append(i + 1)
                  .append(" failed.");
              if (i > 0) {
                message.append(" ")
                    .append(" prior sub executor(s) completed successfully, but will be rolled back.");
              throw new BatchExecutorException(message.toString(), e, results, batchResult);
            // Add the result to the result and return it.
          return results;
        } finally {
          for (Statement stmt : statementList) {
          currentSql = null;
  2. SimpleExecutor

    // Empty operation. Returns an empty list, which has nothing to see. 
      public List<BatchResult> doFlushStatements(boolean isRollback) {
        return Collections.emptyList();

3. When to add values to statementList and batchResultList

The simplest way is to click, find all the referenced addresses, and find the add method, because it is a List

It seems that there is only one place where the add method is called

During the update operation, the built Statement will be added to the statementList. At the same time, BatchResult will be built and added to it. The last call is the PreparedStatement#addBatch() method. And returns a fixed value.

You can see several parameters of BatchResult, namely MapperStatement, sql and parameterObject parameters


  1. What is currentSql in the following code?

    This is an updated Mapper. Multiple calls will only generate a Statement and a BatchResult

    When this method is called for the first time, currentSql is null. After it is added to the first Statement, currentSql and currentStatement will be assigned as currently assembled. If this method is called for the second time, it will go to if and add the parameters of this query to the parameterObject property of the previous BatchResult

// doUpdate method of BatchExecutor
// The logic here is basically the same as the previous query. They all get the Connection and process parameters, but they are not executed here. Instead, PreparedStatement#addBatch() is called;
  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    final Configuration configuration = ms.getConfiguration();
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      handler.parameterize(stmt);// fix Issues 322
      BatchResult batchResult = batchResultList.get(last);
    } else {
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);    // fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    //You can see that the returned value is a fixed value of Integer.MIN_VALUE + 1002;


Mybatis @Flush is prepared for batch updates. The type of executor must be set to BatchExecutor (it can be set globally and locally when obtaining Session), and the method marked with @ Flush cannot have a corresponding xml file. The return value is List, as shown below

    List<BatchResult> flush();

When the method is called, it can be updated by calling Mapper's @ Flush modified method.

Tags: Java Mybatis mybaties

Posted on Fri, 19 Nov 2021 15:52:23 -0500 by Dr.Flink