大多数JDBC驱动在针对同一SQL语句做批处理时能够获得更好的性能。批量更新操作可以节省数据库的来回传输次数。
15.4.1 使用JdbcTemplate来进行基础的批量操作
通过JdbcTemplate 实现批处理需要实现特定接口的两个方法,BatchPreparedStatementSetter,并且将其作为第二个参数传入到batchUpdate方法调用中。使用getBatchSize提供当前批量操作的大小。使用setValues方法设置语句的Value参数。这个方法会按getBatchSize设置中指定的调用次数。下面的例子中通过传入列表来批量更新actor表。在这个例子中整个列表使用了批量操作:
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) {
int[] updateCounts = 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();
}
});
return updateCounts;
}
// ... additional methods
}
如果你需要处理批量更新或者从文件中批量读取,你可能需要确定一个合适的批处理大小,但是最后一次批处理可能达不到这个大小。在这种场景下你可以使用InterruptibleBatchPreparedStatementSetter接口,允许在输入流耗尽之后终止批处理,isBatchExhausted方法使得你可以指定批处理结束时间。
15.4.2 对象列表的批量处理
JdbcTemplate和NamedParameterJdbcTemplate都提供了批量更新的替代方案。这个时候不是实现一个特定的批量接口,而是在调用时传入所有的值列表。框架会循环访问这些值并且使用内部的SQL语句setter方法。你是否已声明参数对应API是不一样的。针对已声明参数你需要传入qlParameterSource数组,每项对应单次的批量操作。你可以使用SqlParameterSource.createBatch方法来创建这个数组,传入JavaBean数组或是包含参数值的Map数组。
下面是一个使用已声明参数的批量更新例子:
public class JdbcActorDao implements ActorDao {
private NamedParameterTemplate namedParameterJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public int[] batchUpdate(final List<Actor> actors) {
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray());
int[] updateCounts = namedParameterJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
batch);
return updateCounts;
}
// ... additional methods
}
对于使用“?”占位符的SQL语句,你需要传入带有更新值的对象数组。对象数组每一项对应SQL语句中的一个占位符,并且传入顺序需要和SQL语句中定义的顺序保持一致。
下面是使用经典JDBC“?”占位符的例子:
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);
}
int[] updateCounts = jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
batch);
return updateCounts;
}
// ... additional methods
}
上面所有的批量更新方法都返回一个数组,包含具体成功的行数。这个计数是由JDBC驱动返回的。如果拿不到计数。JDBC驱动会返回-2。
15.4.3 多个批处理操作
上面最后一个例子更新的批处理数量太大,最好能再分割成更小的块。最简单的方式就是你多次调用batchUpdate来实现,但是可以有更优的方法。要使用这个方法除了SQL语句,还需要传入参数集合对象,每次Batch的更新数和一个ParameterizedPreparedStatementSetter去设置预编译SQL语句的参数值。框架会循环调用提供的值并且将更新操作切割成指定数量的小批次。
下面的例子设置了更新批次数量为100的批量更新操作:
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