JDBC 批量操作
如果对同一个预处理语句(prepared statement)进行批量多次调用,大多数 JDBC 驱动程序都能提供更好的性能。 通过将更新分组到批次中,您可以限制到数据库的往返次数。
使用 JdbcTemplate 进行基本批量操作
您可以通过实现一个特殊接口 BatchPreparedStatementSetter 的两个方法,并将该实现作为第二个参数传递给 batchUpdate
方法调用,来实现 JdbcTemplate 批量处理。您可以使用 getBatchSize 方法提供当前批次的大小。
您可以使用 setValues 方法为预处理语句的参数设置值。此方法的调用次数与您在 getBatchSize 调用中指定的次数相同。
以下示例根据列表中的条目更新 t_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 {
Actor actor = actors.get(i);
ps.setString(1, actor.getFirstName());
ps.setString(2, actor.getLastName());
ps.setLong(3, actor.getId().longValue());
}
public int getBatchSize() {
return actors.size();
}
});
}
// ... 其他方法
}
如果您正在处理更新流或从文件读取,您可能有一个首选的批次大小,但最后一个批次可能没有那么多条目。
在这种情况下,您可以使用 InterruptibleBatchPreparedStatementSetter 接口,它允许您在输入源耗尽后中断批次。
isBatchExhausted 方法允许您发出批次结束的信号。
使用对象列表进行批量操作
JdbcTemplate 和 NamedParameterJdbcTemplate 都提供了另一种提供批量更新的方式。
无需实现特殊的批处理接口,您可以将所有参数值作为列表在调用中提供。框架会循环遍历这些值并使用内部预处理语句设置器。
API 取决于您是否使用命名参数。对于命名参数,您提供一个 SqlParameterSource 数组,批次中的每个成员对应一个条目。
您可以使用 SqlParameterSourceUtils.createBatch 便捷方法创建此数组,传入一组 bean 风格的对象
(带有对应于参数的 getter 方法)、以 String 为键的 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));
}
// ... 其他方法
}
对于使用经典 ? 占位符的 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<>();
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);
}
// ... 其他方法
}
我们前面描述的所有批量更新方法都返回一个 int 数组,其中包含每个批次条目的受影响行数。
此计数由 JDBC 驱动程序报告。如果计数不可用,JDBC 驱动程序将返回 -2 值。
|
在这样的场景中,随着底层 或者,您可以考虑显式指定相应的 JDBC 类型,可以通过 |
多批次批量操作
前面的批量更新示例处理的是非常大的批次,以至于您希望将它们分解为几个较小的批次。
您可以使用前面提到的方法通过多次调用 batchUpdate 方法来做到这一点,但现在有一个更方便的方法。
此方法除了 SQL 语句外,还需要一个包含参数的对象 Collection、每个批次的更新次数,
以及一个用于设置预处理语句参数值的 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,
(PreparedStatement ps, Actor actor) -> {
ps.setString(1, actor.getFirstName());
ps.setString(2, actor.getLastName());
ps.setLong(3, actor.getId().longValue());
});
return updateCounts;
}
// ... 其他方法
}
此调用的批量更新方法返回一个 int 数组的数组,其中包含每个批次的受影响行数数组,每个批次包含一个数组。
顶级数组的长度表示运行的批次数量,第二级数组的长度表示该批次中的更新数量。
每个批次中的更新数量应该是所有批次提供的批次大小(除了最后一个批次可能更少),这取决于提供的更新对象的总数。
每个更新语句的更新计数是 JDBC 驱动程序报告的计数。如果计数不可用,JDBC 驱动程序返回 -2 值。