Principio de actualización por lotes de Mybatis-Plus

método por lotes


Tamaño de lote predeterminado del método updateBatchById de IService
imagen.png
= 1000 com.baomidou.mybatisplus.extension.service.impl.ServiceImpl#updateBatchById

   @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateBatchById(Collection<T> entityList, int batchSize) {
    
    
        String sqlStatement = getSqlStatement(SqlMethod.UPDATE_BY_ID);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
    
    
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(sqlStatement, param);
        });
    }

Construya una devolución de llamada e ingrese el método ejecutarBatch

 /**
     * 执行批量操作
     *
     * @param entityClass 实体类
     * @param log         日志对象
     * @param list        数据集合
     * @param batchSize   批次大小
     * @param consumer    consumer
     * @param <E>         T
     * @return 操作结果
     * @since 3.4.0
     */
    public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
    
    
        Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
        return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, sqlSession -> {
    
    
            int size = list.size();
            int i = 1;
            for (E element : list) {
    
    
                consumer.accept(sqlSession, element);
                if ((i % batchSize == 0) || i == size) {
    
    
                    sqlSession.flushStatements();
                }
                i++;
            }
        });
    }

Básicamente puedes ver esto en este método. Después de ejecutar el método 1000 veces, las declaraciones de descarga se ejecutan una vez. En otras palabras, en teoría, se acumulan 1000 actualizaciones SQL antes de realizar una actualización de la base de datos. BatchExcutor se utiliza para la ejecución por lotes
.

LoteExcutor

Antes de analizar la clase BatchExecutor, primero comprenda los conocimientos relacionados con el procesamiento por lotes de JDBC.

Procesamiento por lotes JDBC

El procesamiento por lotes permite agrupar declaraciones SQL relacionadas en lotes y enviarlas con una sola llamada a la base de datos, completando la interacción con la base de datos en una sola ejecución. Cabe señalar que el procesamiento por lotes en JDBC solo admite insertar, actualizar, eliminar y otros tipos de declaraciones SQL, y no admite declaraciones SQL de tipo seleccionado.
 ** Al enviar varias declaraciones SQL a la base de datos a la vez, se puede reducir la sobrecarga de comunicación, mejorando así el rendimiento. **
No se requiere ningún controlador JDBC para admitir esta función. Se debe utilizar el método DatabaseMetaData.supportsBatchUpdates() para determinar si la base de datos de destino admite el procesamiento de actualizaciones por lotes. Este método devolverá verdadero si el controlador JDBC admite esta función.
El método addBatch() de Statement, PreparedStatement y CallableStatement se utiliza para agregar una única declaración a un lote. ejecutarBatch() se utiliza para ejecutar todas las declaraciones que componen el lote.
ejecutarBatch() devuelve una matriz de números enteros, cada elemento de la matriz representa el recuento de actualizaciones de la declaración de actualización correspondiente.
Al igual que las declaraciones por lotes se agregan al proceso, se pueden eliminar usando el método clearBatch(). Este método eliminará todas las declaraciones agregadas usando el método addBatch(). Sin embargo, no puede especificar una declaración para eliminarla.

El proceso de procesamiento por lotes utilizando objetos Statement

Una Declaración puede ejecutar múltiples SQL (siempre que el SQL sea el mismo y se use el marcador de posición).
La siguiente es una secuencia típica de pasos para el procesamiento por lotes utilizando el objeto Declaración.
Utilice el método createStatement() para crear un objeto Declaración.
Utilice setAutoCommit() para establecer la confirmación automática en falso.
Utilice el método addBatch() para agregar declaraciones SQL al lote en el objeto Declaración creado.
Utilice el método ejecutarBatch() en el objeto Declaración creado para ejecutar todas las declaraciones SQL.
Finalmente, use el método commit() para confirmar todos los cambios.

Uso de objetos PrepareStatement para procesamiento por lotes

Una declaración puede ejecutar múltiples SQL (siempre que el SQL sea el mismo y se use el marcador de posición).
La siguiente es la secuencia típica de pasos para el procesamiento por lotes usando el objeto PrepareStatement:
usar marcadores de posición para crear declaraciones SQL.
Utilice el método prepareStatement() para crear un objeto PrepareStatement.
Utilice setAutoCommit() para establecer la confirmación automática en falso.
Utilice el método addBatch() para agregar declaraciones SQL al lote en el objeto Declaración creado.
Utilice el método ejecutarBatch() en el objeto Declaración creado para ejecutar todas las declaraciones SQL.
Finalmente, use el método commit() para confirmar todos los cambios.

Clase BatchExecutor

BatchExecutor también hereda la clase abstracta BaseExecutor e implementa la función de procesar por lotes múltiples declaraciones SQL. Debido a que JDBC no admite declaraciones SQL de tipo seleccionado y solo admite declaraciones SQL de tipo inserción, actualización y eliminación, en la clase BatchExecutor, el procesamiento por lotes se dirige principalmente al método update(). La lógica general implementada por la clase BatchExecutor: el método doUpdate() agrega principalmente las declaraciones SQL que deben agruparse por lotes al objeto Statement o PrepareStatement por lotes a través del método Statement.addBatch(), y luego ejecuta la declaración a través de doFlushStatements(). método. El método ejecutarBatch() ejecuta el procesamiento por lotes. En el método doQueryCursor() y el método doQuery(), primero se ejecutará el método flushStatements(). La capa inferior del método flushStatements() es en realidad el método doFlushStatements(), por lo que es equivalente a agregar primero a la Declaración o Se ejecuta la declaración por lotes en el objeto PrepareStatement y luego se ejecuta la operación de consulta.

método doUpdate()

Este método agrega principalmente las declaraciones SQL que requieren procesamiento por lotes al objeto Declaración o PrepareStatement del lote a través del método Statement.addBatch() y espera la ejecución del lote. Se basa principalmente en juzgar si el modo SQL actualmente ejecutado es el mismo que el último modo SQL ejecutado y los objetos MappedStatement correspondientes son los mismos para determinar si se debe utilizar un objeto Statement existente o crear un nuevo objeto Statement para realizar addBatch(). operación.

@Override
  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);
      applyTransactionTimeout(stmt);
      handler.parameterize(stmt);//fix Issues 322
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
    
    
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);    //fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
  // handler.parameterize(stmt);
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

Aquí encontré una situación especial
donde se realizan actualizaciones por lotes. Lógicamente se reutilizará la misma declaración, pero debido a que los parámetros están vacíos.

       userService.updateBatchById(Arrays.asList(u, u1, u2, u3));

Por ejemplo, mapStatement definido en xml de esta manera, el método de actualización de MybatisPlus es usar la etiqueta if para juzgar que está vacío.

<update id="updateByExampleSelective" parameterType="map" >
  update user
  <set >
    <if test="record.age != null" >
      age = #{record.age,jdbcType=int},
    </if>
  </set>
  ············

</update>

Luego, si la edad del parámetro u1 no está vacía y la edad del parámetro u2 está vacía, también dará lugar a diferentes comparaciones de SQL y no se agregará al mismo lote.

Método doFlushStatements()

En el método doFlushStatements(), el método subyacente ejecutarBatch() de Statement se ejecuta para enviar la operación por lotes. El objeto BatchResult mantiene el resultado de la ejecución de un método Statement.executeBatch(). En comparación con el procesamiento por lotes JDBC, esto equivale a encapsular múltiples métodos de ejecuciónBatch().

@Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    
    
    try {
    
    
      List<BatchResult> results = new ArrayList<BatchResult>();
      //如果明确指定了要回滚事务,则直接返回空集合,忽略 statementList集合中记录的 SQL语句
      if (isRollback) {
    
    
        return Collections.emptyList();
      }
      //遍历statementList集合
      for (int i = 0, n = statementList.size(); i < n; i++) {
    
    
        Statement stmt = statementList.get(i);
        applyTransactionTimeout(stmt);
        //获取对应BatchResult对象
        BatchResult batchResult = batchResultList.get(i);
        try {
    
    
          //调用 Statement.executeBatch()方法批量执行其中记录的 SQL语句,并使用返回的int数组
          //更新 BatchResult.updateCounts字段,其中每一个元素都表示一条 SQL语句影响的记录条数
          batchResult.setUpdateCounts(stmt.executeBatch());
          MappedStatement ms = batchResult.getMappedStatement();
          List<Object> parameterObjects = batchResult.getParameterObjects();
          //获取配置的KeyGenerator对象
          KeyGenerator keyGenerator = ms.getKeyGenerator();
          if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
    
    
        	//获取数据库生成的主键,并设置到parameterObjects中
            Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
            jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
          } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) {
    
     //issue #141
            for (Object parameter : parameterObjects) {
    
    
              //对于其他类型的 keyGenerator,会调用其processAfter()方法
              keyGenerator.processAfter(this, ms, stmt, parameter);
            }
          }
          // Close statement to close cursor #1109
          closeStatement(stmt);
        } catch (BatchUpdateException e) {
    
    //异常处理
          StringBuilder message = new StringBuilder();
          message.append(batchResult.getMappedStatement().getId())
              .append(" (batch index #")
              .append(i + 1)
              .append(")")
              .append(" failed.");
          if (i > 0) {
    
    
            message.append(" ")
                .append(i)
                .append(" prior sub executor(s) completed successfully, but will be rolled back.");
          }
          throw new BatchExecutorException(message.toString(), e, results, batchResult);
        }
        //添加batchResult到results集合中
        results.add(batchResult);
      }
      return results;
    } finally {
    
    //关闭或清空对应对象
      for (Statement stmt : statementList) {
    
    
        closeStatement(stmt);
      }
      currentSql = null;
      statementList.clear();
      batchResultList.clear();
    }
  }

El método clave es stmt.executeBatch (), que puede ejecutar todo el SQL bajo la declaración actual en lotes.

Método doQuery(), método doQueryCursor()

En los métodos doQuery() y doQueryCursor(), son similares a los de la clase SimpleExecutro. La única diferencia es que el método flushStatements() se ejecuta primero, y la capa inferior del método flushStatements() es en realidad doFlushStatements( ), por lo que es equivalente a agregar primero la instrucción Ejecutar la instrucción por lotes en el objeto Declaración o PrepareStatement y luego ejecutar la operación de consulta.

 @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException {
    
    
    Statement stmt = null;
    try {
    
    
      flushStatements();
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);
      return handler.<E>query(stmt, resultHandler);
    } finally {
    
    
      closeStatement(stmt);
    }
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    
    
    flushStatements();
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Connection connection = getConnection(ms.getStatementLog());
    Statement stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return handler.<E>queryCursor(stmt);
  }

Supongo que te gusta

Origin blog.csdn.net/qq_37436172/article/details/129519035
Recomendado
Clasificación