# 3.5JDBC批处理操作

如果将多个调用批处理到同一条准备好的语句，则大多数JDBC驱动程序都会提高性能。 通过将更新分组，可以限制到数据库的往返次数。

## 3.5.1 使用JdbcTemplate的基本批处理操作

你可以通过实现特殊接口的两个方法BatchPreparedStatementSetter并将该实现作为batchUpdate方法调用中的第二个参数传入来完成JdbcTemplate批处理。 你可以使用getBatchSize方法提供当前批处理的大小。 你可以使用setValues方法设置准备好的语句的参数值。 此方法称为你在getBatchSize调用中指定的次数。 以下示例根据列表中的条目更新actor表，并将整个列表用作批处理：

```java
public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public int[] batchUpdate(final List<Actor> actors) {
        return this.jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                new BatchPreparedStatementSetter() {
                    public void setValues(PreparedStatement ps, int i) throws SQLException {
                        ps.setString(1, actors.get(i).getFirstName());
                        ps.setString(2, actors.get(i).getLastName());
                        ps.setLong(3, actors.get(i).getId().longValue());
                    }
                    public int getBatchSize() {
                        return actors.size();
                    }
                });
    }

    // ... additional methods
}
```

如果处理更新流或从文件读取，则可能具有首选的批处理大小，但最后一批可能没有该数量的条目。在这种情况下，可以使用InterruptibleBatchPreparedStatementSetter接口，该接口可在输入源耗尽后中断批处理。 isBatchExhausted方法使你可以发出批处理结束的信号。

## 3.5.2 对象列表的批处理操作

JdbcTemplate和NamedParameterJdbcTemplate都提供了另​​一种提供批处理更新的方式。无需实现特殊的批处理接口，而是将调用中的所有参数值作为列表提供。框架遍历这些值并使用内部的准备好的语句设置器。 API会有所不同，具体取决于你是否使用命名参数。对于命名参数，你提供一个SqlParameterSource数组，该批处理的每个成员都有一个条目。你可以使用SqlParameterSourceUtils.createBatch便捷方法创建此数组，传入一个bean样式的对象数组（带有与参数相对应的getter方法），字符串键的Map实例（包含对应的参数作为值），或者混合使用都。

以下示例显示了使用命名参数的批量更新：

```java
public class JdbcActorDao implements ActorDao {

    private NamedParameterTemplate namedParameterJdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    }

    public int[] batchUpdate(List<Actor> actors) {
        return this.namedParameterJdbcTemplate.batchUpdate(
                "update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
                SqlParameterSourceUtils.createBatch(actors));
    }

    // ... additional methods
}
```

对于使用经典的SQL语句？ 占位符，则传入包含包含更新值的对象数组的列表。 该对象数组在SQL语句中的每个占位符必须有一个条目，并且它们的顺序必须与SQL语句中定义的顺序相同。

以下示例与前面的示例相同，不同之处在于它使用经典的JDBC？占位符：

```java
public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public int[] batchUpdate(final List<Actor> actors) {
        List<Object[]> batch = new ArrayList<Object[]>();
        for (Actor actor : actors) {
            Object[] values = new Object[] {
                    actor.getFirstName(), actor.getLastName(), actor.getId()};
            batch.add(values);
        }
        return this.jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                batch);
    }

    // ... additional methods
}
```

我们前面介绍的所有批处理更新方法都返回一个int数组，其中包含每个批处理条目的受影响行数。此计数由JDBC驱动程序报告。如果该计数不可用，则JDBC驱动程序将返回值-2。

> 在这种情况下，通过在基础PreparedStatement上自动设置值，需要从给定的Java类型派生每个值的对应JDBC类型。尽管这通常效果很好，但存在潜在的问题（例如，包含Map的空值）。在这种情况下，Spring默认情况下会调用ParameterMetaData.getParameterType，这对于JDBC驱动程序可能会很昂贵。如果遇到性能问题，应使用最新的驱动程序版本，并考虑将spring.jdbc.getParameterType.ignore属性设置为true（作为JVM系统属性或在类路径根目录中的spring.properties文件中）。 ，如Oracle 12c（SPR-16139）所述。
>
> 或者，你可以考虑通过“ BatchPreparedStatementSetter”（如前所示），通过为基于“ List ”的调用提供的显式类型数组，通过在服务器上的“ registerSqlType”调用来显式指定相应的JDBC类型。自定义“ MapSqlParameterSource”实例，或者通过“ BeanPropertySqlParameterSource”实例从Java声明的属性类型中获取SQL类型，即使对于null值也是如此。

## 3.5.3 具有多个批次的批次操作

前面的批处理更新示例处理的批处理过大，你想将它们分解成几个较小的批处理。 你可以通过多次调用batchUpdate方法来使用前面提到的方法来执行此操作，但是现在有了一个更方便的方法。 除了SQL语句外，此方法还包含一个对象集合，该对象包含参数，每个批处理要进行的更新次数以及一个ParameterizedPreparedStatementSetter来设置准备好的语句的参数值。 框架遍历提供的值，并将更新调用分成指定大小的批处理。

以下示例显示了使用100的批量大小的批量更新：

```java
public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public int[][] batchUpdate(final Collection<Actor> actors) {
        int[][] updateCounts = jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                actors,
                100,
                new ParameterizedPreparedStatementSetter<Actor>() {
                    public void setValues(PreparedStatement ps, Actor argument) throws SQLException {
                        ps.setString(1, argument.getFirstName());
                        ps.setString(2, argument.getLastName());
                        ps.setLong(3, argument.getId().longValue());
                    }
                });
        return updateCounts;
    }

    // ... additional methods
}
```

此调用的批处理更新方法返回一个int数组数组，该数组包含每个批处理的数组条目以及每个更新受影响的行数的数组。 顶层数组的长度指示已执行的批处理数量，第二层数组的长度指示该批处理中的更新数量。 每个批次中的更新数量应该是为所有批次提供的批次大小（最后一个可能更少），这取决于所提供的更新对象的总数。 每个更新语句的更新计数是JDBC驱动程序报告的计数。 如果该计数不可用，则JDBC驱动程序将返回值-2。
