JDBC 批量操作
如果对同一个预处理语句进行多次调用,大多数 JDBC 驱动程序通过批量处理可以提高性能。通过将更新分组为批次,可以限制数据库的往返次数。
使用 JdbcTemplate
进行基本批量操作
通过实现特殊接口 BatchPreparedStatementSetter
的两个方法,并将该实现作为第二个参数传递给 batchUpdate
方法调用,即可完成 JdbcTemplate
批量处理。可以使用 getBatchSize
方法提供当前批次的大小。可以使用 setValues
方法设置预处理语句的参数值。该方法将按照你在 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
都提供了提供批量更新的另一种方式。您无需实现特殊的批量接口,而是在调用中以列表形式提供所有参数值。框架会遍历这些值并使用内部的预处理语句设置器。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
。
在这种情况下,对于底层 从 6.1.2 版本开始,Spring 会绕过 PostgreSQL 和 MS SQL Server 上默认的 或者,可以考虑显式指定相应的 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
。