|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
JDBC 批处理操作
大多数 JDBC 驱动程序在对同一预编译语句(prepared statement)进行多次调用时,如果将这些调用批量处理,可以显著提升性能。通过将更新操作分组为批次,可以减少与数据库之间的往返次数。
使用 进行基本批量操作JdbcTemplate
您可以通过实现一个特殊接口 JdbcTemplate 的两个方法,并将该实现作为第二个参数传递给 BatchPreparedStatementSetter 方法调用来完成 batchUpdate 的批处理操作。您可以使用 getBatchSize 方法来指定当前批次的大小,使用 setValues 方法为预编译语句(prepared statement)的参数设置值。该方法会被调用的次数等于您在 getBatchSize 方法中指定的次数。以下示例根据一个列表中的条目更新 t_actor 表,并且整个列表被用作一个批次:
-
Java
-
Kotlin
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();
}
});
}
// ... additional methods
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val jdbcTemplate = JdbcTemplate(dataSource)
fun batchUpdate(actors: List<Actor>): IntArray {
return jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
object: BatchPreparedStatementSetter {
override fun setValues(ps: PreparedStatement, i: Int) {
ps.setString(1, actors[i].firstName)
ps.setString(2, actors[i].lastName)
ps.setLong(3, actors[i].id)
}
override fun getBatchSize() = actors.size
})
}
// ... additional methods
}
如果你正在处理一个更新流或从文件中读取数据,你可能会有一个偏好的批处理大小,但最后一批可能没有那么多条目。在这种情况下,你可以使用 InterruptibleBatchPreparedStatementSetter 接口,该接口允许你在输入源耗尽时中断当前批次。isBatchExhausted 方法可用于指示批次的结束。
使用对象列表进行批量操作
JdbcTemplate 和 NamedParameterJdbcTemplate 都提供了另一种执行批量更新的方式。你无需实现特殊的批量接口,而是在调用时以列表形式提供所有参数值。框架会遍历这些值,并使用内部的预编译语句(PreparedStatement)设置器进行处理。该 API 的具体形式取决于你是否使用命名参数。对于命名参数,你需要提供一个 SqlParameterSource 数组,其中每个元素对应批量操作中的一个成员。你可以使用 SqlParameterSourceUtils.createBatch 工具方法来创建这个数组,传入一个由 bean 风格对象(其 getter 方法与参数名对应)、以 String 为键的 Map 实例(包含对应的参数值),或者两者的混合组成的数组。
以下示例展示了使用命名参数进行批量更新:
-
Java
-
Kotlin
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
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
fun batchUpdate(actors: List<Actor>): IntArray {
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
-
Kotlin
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);
}
// ... additional methods
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val jdbcTemplate = JdbcTemplate(dataSource)
fun batchUpdate(actors: List<Actor>): IntArray {
val batch = mutableListOf<Array<Any>>()
for (actor in actors) {
batch.add(arrayOf(actor.firstName, actor.lastName, actor.id))
}
return jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?", batch)
}
// ... additional methods
}
我们前面介绍的所有批量更新方法都会返回一个 int 数组,其中包含每个批次条目所影响的行数。该计数由 JDBC 驱动程序报告。如果该计数不可用,JDBC 驱动程序将返回值 -2。
|
在这种情况下,当在底层的 或者,您也可以考虑显式指定相应的 JDBC 类型,
可以通过 |
使用多个批次进行批量操作
前面的批量更新示例处理的是规模非常大的批次,你可能希望将其拆分为若干个较小的批次。你可以通过多次调用之前提到的 batchUpdate 方法来实现这一点,但现在有一种更为便捷的方法。该方法除了接收 SQL 语句外,还接收一个包含参数的对象 Collection、每个批次要执行的更新数量,以及一个 ParameterizedPreparedStatementSetter 用于设置预编译语句中的参数值。框架会遍历所提供的参数值,并将更新调用按指定的批次大小进行拆分。
以下示例展示了一个使用批处理大小为100的批量更新:
-
Java
-
Kotlin
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;
}
// ... additional methods
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val jdbcTemplate = JdbcTemplate(dataSource)
fun batchUpdate(actors: List<Actor>): Array<IntArray> {
return jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
actors, 100) { ps, argument ->
ps.setString(1, argument.firstName)
ps.setString(2, argument.lastName)
ps.setLong(3, argument.id)
}
}
// ... additional methods
}
此调用的批量更新方法返回一个 int 数组的数组,其中包含每个批次的一个数组项,该数组项记录了每次更新所影响的行数。
顶级数组的长度表示已执行的批次数,第二级数组的长度表示该批次中更新语句的数量。
每个批次中的更新数量应等于所提供的批次大小(最后一个批次可能更少),具体取决于所提供的更新对象总数。
每条更新语句的更新计数由 JDBC 驱动程序报告。如果该计数不可用,JDBC 驱动程序将返回值 -2。