Spring Framework中JDBC批量操作的三種實(shí)現(xiàn)方式
這篇文章詳細(xì)介紹了如何使用 Spring 的 JdbcTemplate 進(jìn)行高效的數(shù)據(jù)庫(kù)批量更新,從而減少與數(shù)據(jù)庫(kù)之間的網(wǎng)絡(luò)往返次數(shù)(round-trips),提升性能。
下面我將用通俗易懂的方式,結(jié)合代碼示例和實(shí)際場(chǎng)景,幫你系統(tǒng)地理解這一節(jié)的核心思想和三種主要批量處理方式。
為什么要用“批量操作”?
在沒有批量處理時(shí),如果你要插入或更新 1000 條數(shù)據(jù):
for (Actor actor : actors) {
jdbcTemplate.update("INSERT INTO t_actor ...", actor.getName(), ...);
}
這會(huì)向數(shù)據(jù)庫(kù)發(fā)送 1000 次獨(dú)立的 SQL 請(qǐng)求 → 1000 次網(wǎng)絡(luò)通信 → 效率極低。
而使用 批量操作(Batch Operations),你可以把多個(gè) SQL 操作“打包”成一組,一次性提交給數(shù)據(jù)庫(kù)執(zhí)行:
? 結(jié)果:
- 減少網(wǎng)絡(luò)開銷
- 提高吞吐量(可能提升幾十倍)
- 更高效地利用數(shù)據(jù)庫(kù)資源
基本批量操作:使用 BatchPreparedStatementSetter
這是最傳統(tǒng)、控制最精細(xì)的方式。
使用方法:
實(shí)現(xiàn) BatchPreparedStatementSetter 接口的兩個(gè)方法:
getBatchSize():返回本次批量操作的總條數(shù)setValues(PreparedStatement ps, int i):為第i條記錄設(shè)置參數(shù)
示例說明:
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(); // 整個(gè)列表作為一批
}
});
}
關(guān)鍵點(diǎn):
- 整個(gè)
actors列表被當(dāng)作一個(gè)“大批次”一次性提交。 - 返回值是
int[],每個(gè)元素表示對(duì)應(yīng) SQL 語(yǔ)句影響的行數(shù)。 - 適用于:數(shù)據(jù)量不大且能一次性加載到內(nèi)存的情況。
特殊情況:流式輸入(不確定總數(shù))
如果數(shù)據(jù)來自文件、流或數(shù)據(jù)庫(kù)游標(biāo),無法預(yù)知總數(shù),可以使用:
InterruptibleBatchPreparedStatementSetter
它有一個(gè)額外方法:
boolean isBatchExhausted();
當(dāng)你讀完所有數(shù)據(jù)后返回 true,Spring 就停止繼續(xù)添加語(yǔ)句。
更簡(jiǎn)潔的方式:用對(duì)象列表做批量操作
Spring 提供了更簡(jiǎn)潔的 API —— 不用手動(dòng)實(shí)現(xiàn)接口,只需傳入一個(gè)對(duì)象列表!
方式一:使用命名參數(shù)(Named Parameters) + NamedParameterJdbcTemplate
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) // 自動(dòng)提取 getter
);
}
優(yōu)點(diǎn):
- 使用
:firstName這種命名參數(shù),可讀性強(qiáng) SqlParameterSourceUtils.createBatch(actors)自動(dòng)從Actor對(duì)象的 getter 提取字段- 支持 POJO(JavaBean)、Map 等格式
要求:Actor 類必須有 getFirstName()、getId() 等標(biāo)準(zhǔn) getter 方法
方式二:使用 ? 占位符 + List<Object[]>
List<Object[]> batch = new ArrayList<>();
for (Actor actor : actors) {
Object[] values = new Object[] {
actor.getFirstName(),
actor.getLastName(),
actor.getId()
};
batch.add(values);
}
jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
batch
);
優(yōu)點(diǎn):
- 簡(jiǎn)單直觀
- 不依賴命名參數(shù)
注意事項(xiàng):
- 參數(shù)順序必須嚴(yán)格匹配 SQL 中的
?順序 - 如果值為
null,Spring 需要通過反射推斷類型(可能影響性能)
性能提示:
Spring 默認(rèn)調(diào)用 ParameterMetaData.getParameterType() 來判斷 null 值的類型,但某些數(shù)據(jù)庫(kù)驅(qū)動(dòng)(如 Oracle 12c)這個(gè)操作很慢。
可以通過設(shè)置系統(tǒng)屬性關(guān)閉:
# 在 JVM 啟動(dòng)參數(shù)或 spring.properties 中添加 spring.jdbc.getParameterType.ignore=true
或者顯式指定類型(更推薦)。
處理超大數(shù)據(jù)集:分多個(gè)小批次提交(Multiple Batches)
前面的例子都是一次性提交全部數(shù)據(jù)。但如果數(shù)據(jù)量太大(比如 10 萬條),一次性加載到內(nèi)存會(huì)導(dǎo)致內(nèi)存溢出(OOM)。
解決方案:
使用分批提交(chunking),每 100 條提交一次。
public int[][] batchUpdate(final Collection<Actor> actors) {
int[][] updateCounts = jdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
actors, // 所有數(shù)據(jù)
100, // 每批最多 100 條
(ps, actor) -> { // Lambda 設(shè)置參數(shù)
ps.setString(1, actor.getFirstName());
ps.setString(2, actor.getLastName());
ps.setLong(3, actor.getId().longValue());
}
);
return updateCounts;
}
返回值解釋:
int[][] updateCounts
- 第一層數(shù)組:每個(gè)“批次”的結(jié)果
- 第二層數(shù)組:該批次中每條 SQL 的影響行數(shù)
例如:
[ [1, 1, 1, ..., 1], // 第1批,共100條,每條影響1行 [1, 1, 1, ..., 1], // 第2批 [1, 1, 1] // 最后一批只有3條 ]
適用場(chǎng)景:
- 處理大量數(shù)據(jù)(數(shù)萬、數(shù)十萬條)
- 防止內(nèi)存溢出
- 支持進(jìn)度監(jiān)控、錯(cuò)誤恢復(fù)
三種方式對(duì)比總結(jié)
| 方式 | 適用場(chǎng)景 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|---|
BatchPreparedStatementSetter | 需要精確控制每條記錄參數(shù) | 靈活、支持中斷 | 寫法較繁瑣 |
List<Object[]> 或 SqlParameterSourceUtils.createBatch() | 數(shù)據(jù)已在內(nèi)存中,結(jié)構(gòu)簡(jiǎn)單 | 寫法簡(jiǎn)潔 | 不適合超大數(shù)據(jù)集 |
| 分批提交(multiple batches) | 超大數(shù)據(jù)集 | 內(nèi)存友好、可控性強(qiáng) | 稍復(fù)雜 |
性能建議
合理設(shè)置批大?。╞atch size)
- 太?。喝杂卸啻瓮?/li>
- 太大:內(nèi)存壓力大,事務(wù)過長(zhǎng)
- 推薦值:50 ~ 1000(根據(jù)測(cè)試調(diào)整)
開啟數(shù)據(jù)庫(kù)的批量支持
- MySQL:確保連接 URL 包含
rewriteBatchedStatements=true
jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
- Oracle:原生支持較好
- PostgreSQL:需要驅(qū)動(dòng)支持(
reWriteBatchedInserts=true)
使用連接池(如 HikariCP)
- 批量操作期間保持連接穩(wěn)定
避免自動(dòng)提交(Auto-commit)
- 在事務(wù)中執(zhí)行批量操作,防止中途失敗導(dǎo)致部分寫入
實(shí)際應(yīng)用建議(Spring Boot 項(xiàng)目)
如果你使用的是 Spring Boot,推薦寫法如下:
@Service
public class ActorService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void updateActorsInBatches(List<Actor> actors) {
jdbcTemplate.batchUpdate(
"UPDATE t_actor SET first_name = ?, last_name = ? WHERE id = ?",
actors,
100,
(ps, actor) -> {
ps.setString(1, actor.getFirstName());
ps.setString(2, actor.getLastName());
ps.setLong(3, actor.getId());
}
);
}
}
搭配事務(wù)注解:
@Transactional
public void importHugeData() {
List<Actor> hugeList = readFromCsvOrDatabase();
updateActorsInBatches(hugeList); // 分批提交
}
總結(jié)一句話:
批量操作 = 把多條 SQL “打包”發(fā)送給數(shù)據(jù)庫(kù),減少網(wǎng)絡(luò)來回,提高性能。Spring 提供了多種方式讓你既能簡(jiǎn)單使用,也能精細(xì)控制。
下一步你可以思考:
- 如何從 CSV 文件逐行讀取并分批導(dǎo)入?
- 如何在批量操作中捕獲部分失敗的記錄?
- 如何結(jié)合
@Async實(shí)現(xiàn)并行批量處理?
以上就是Spring Framework中JDBC批量操作的三種實(shí)現(xiàn)方式的詳細(xì)內(nèi)容,更多關(guān)于Spring JDBC批量操作方式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java 學(xué)習(xí)筆記(入門篇)_java程序helloWorld
安裝配置完Java的jdk,下面就開始寫第一個(gè)java程序--hello World.用來在控制臺(tái)輸出“Hello World”,接下來詳細(xì)介紹,感興趣的朋友可以參考下2013-01-01
Java實(shí)現(xiàn)的并發(fā)任務(wù)處理實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)的并發(fā)任務(wù)處理方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了基于線程操作并發(fā)任務(wù)的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
SpringBoot定時(shí)任務(wù)設(shè)計(jì)之時(shí)間輪案例原理詳解
這篇文章主要為大家介紹了SpringBoot定時(shí)任務(wù)設(shè)計(jì)之時(shí)間輪案例原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
使用idea搭建spring項(xiàng)目,利用xml文件的形式進(jìn)行配置方式
本文介紹了如何使用SpringIOC和SpringDI的思想開發(fā)一個(gè)打印機(jī)模擬程序,實(shí)現(xiàn)了靈活配置彩色墨盒或灰色墨盒以及打印頁(yè)面大小的功能,通過創(chuàng)建接口和實(shí)現(xiàn)類,并在配置文件中進(jìn)行依賴注入,實(shí)現(xiàn)了控制反轉(zhuǎn)2024-11-11
springdata jpa使用Example快速實(shí)現(xiàn)動(dòng)態(tài)查詢功能
這篇文章主要介紹了springdata jpa使用Example快速實(shí)現(xiàn)動(dòng)態(tài)查詢功能,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
Java的lambda表達(dá)式實(shí)現(xiàn)解析
這篇文章主要為大家詳細(xì)介紹了Java的lamda表達(dá)式實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06

