JDBC 批处理操作
如果您将对同一准备语句的多个调用批量处理,大多数JDBC驱动程序会提供改进的性能。通过将更新分组到批处理中,您可以限制与数据库的往返次数。
使用JdbcTemplate
进行基本批处理操作
您可以通过实现特殊接口BatchPreparedStatementSetter
的两种方法来完成JdbcTemplate
批处理,并将该实现作为第二个参数传递到您的batchUpdate
方法调用中。您可以使用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
值。