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
。