15.4 JDBC批量操作

大多数JDBC驱动在针对同一SQL语句做批处理时能够获得更好的性能。批量更新操作可以节省数据库的来回传输次数。

15.4.1 使用JdbcTemplate来进行基础的批量操作

通过JdbcTemplate 实现批处理需要实现特定接口的两个方法,BatchPreparedStatementSetter,并且将其作为第二个参数传入到batchUpdate方法调用中。使用getBatchSize提供当前批量操作的大小。使用setValues方法设置语句的Value参数。这个方法会按getBatchSize设置中指定的调用次数。下面的例子中通过传入列表来批量更新actor表。在这个例子中整个列表使用了批量操作:

  1. public class JdbcActorDao implements ActorDao {
  2. private JdbcTemplate jdbcTemplate;
  3. public void setDataSource(DataSource dataSource) {
  4. this.jdbcTemplate = new JdbcTemplate(dataSource);
  5. }
  6. public int[] batchUpdate(final List<Actor> actors) {
  7. int[] updateCounts = jdbcTemplate.batchUpdate("update t_actor set first_name = ?, " +
  8. "last_name = ? where id = ?",
  9. new BatchPreparedStatementSetter() {
  10. public void setValues(PreparedStatement ps, int i) throws SQLException {
  11. ps.setString(1, actors.get(i).getFirstName());
  12. ps.setString(2, actors.get(i).getLastName());
  13. ps.setLong(3, actors.get(i).getId().longValue());
  14. }
  15. public int getBatchSize() {
  16. return actors.size();
  17. }
  18. });
  19. return updateCounts;
  20. }
  21. // ... additional methods
  22. }

如果你需要处理批量更新或者从文件中批量读取,你可能需要确定一个合适的批处理大小,但是最后一次批处理可能达不到这个大小。在这种场景下你可以使用InterruptibleBatchPreparedStatementSetter接口,允许在输入流耗尽之后终止批处理,isBatchExhausted方法使得你可以指定批处理结束时间。

15.4.2 对象列表的批量处理

JdbcTemplate和NamedParameterJdbcTemplate都提供了批量更新的替代方案。这个时候不是实现一个特定的批量接口,而是在调用时传入所有的值列表。框架会循环访问这些值并且使用内部的SQL语句setter方法。你是否已声明参数对应API是不一样的。针对已声明参数你需要传入qlParameterSource数组,每项对应单次的批量操作。你可以使用SqlParameterSource.createBatch方法来创建这个数组,传入JavaBean数组或是包含参数值的Map数组。

下面是一个使用已声明参数的批量更新例子:

  1. public class JdbcActorDao implements ActorDao {
  2. private NamedParameterTemplate namedParameterJdbcTemplate;
  3. public void setDataSource(DataSource dataSource) {
  4. this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
  5. }
  6. public int[] batchUpdate(final List<Actor> actors) {
  7. SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray());
  8. int[] updateCounts = namedParameterJdbcTemplate.batchUpdate(
  9. "update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
  10. batch);
  11. return updateCounts;
  12. }
  13. // ... additional methods
  14. }

对于使用“?”占位符的SQL语句,你需要传入带有更新值的对象数组。对象数组每一项对应SQL语句中的一个占位符,并且传入顺序需要和SQL语句中定义的顺序保持一致。

下面是使用经典JDBC“?”占位符的例子:

  1. public class JdbcActorDao implements ActorDao {
  2. private JdbcTemplate jdbcTemplate;
  3. public void setDataSource(DataSource dataSource) {
  4. this.jdbcTemplate = new JdbcTemplate(dataSource);
  5. }
  6. public int[] batchUpdate(final List<Actor> actors) {
  7. List<Object[]> batch = new ArrayList<Object[]>();
  8. for (Actor actor : actors) {
  9. Object[] values = new Object[] {
  10. actor.getFirstName(),
  11. actor.getLastName(),
  12. actor.getId()};
  13. batch.add(values);
  14. }
  15. int[] updateCounts = jdbcTemplate.batchUpdate(
  16. "update t_actor set first_name = ?, last_name = ? where id = ?",
  17. batch);
  18. return updateCounts;
  19. }
  20. // ... additional methods
  21. }

上面所有的批量更新方法都返回一个数组,包含具体成功的行数。这个计数是由JDBC驱动返回的。如果拿不到计数。JDBC驱动会返回-2。

15.4.3 多个批处理操作
上面最后一个例子更新的批处理数量太大,最好能再分割成更小的块。最简单的方式就是你多次调用batchUpdate来实现,但是可以有更优的方法。要使用这个方法除了SQL语句,还需要传入参数集合对象,每次Batch的更新数和一个ParameterizedPreparedStatementSetter去设置预编译SQL语句的参数值。框架会循环调用提供的值并且将更新操作切割成指定数量的小批次。

下面的例子设置了更新批次数量为100的批量更新操作:

  1. public class JdbcActorDao implements ActorDao {
  2. private JdbcTemplate jdbcTemplate;
  3. public void setDataSource(DataSource dataSource) {
  4. this.jdbcTemplate = new JdbcTemplate(dataSource);
  5. }
  6. public int[][] batchUpdate(final Collection<Actor> actors) {
  7. int[][] updateCounts = jdbcTemplate.batchUpdate(
  8. "update t_actor set first_name = ?, last_name = ? where id = ?",
  9. actors,
  10. 100,
  11. new ParameterizedPreparedStatementSetter<Actor>() {
  12. public void setValues(PreparedStatement ps, Actor argument) throws SQLException {
  13. ps.setString(1, argument.getFirstName());
  14. ps.setString(2, argument.getLastName());
  15. ps.setLong(3, argument.getId().longValue());
  16. }
  17. });
  18. return updateCounts;
  19. }
  20. // ... additional methods
  21. }

这个调用的批量更新方法返回一个包含int数组的二维数组,包含每次更新生效的行数。第一层数组长度代表批处理执行的数量,第二层数组长度代表每个批处理生效的更新数。每个批处理的更新数必须和所有批处理的大小匹配,除非是最后一次批处理可能小于这个数,具体依赖于更新对象的总数。每次更新语句生效的更新数由JDBC驱动提供。如果更新数量不存在,JDBC驱动会返回-2