Spring Batch實(shí)戰(zhàn)示例
前言
在企業(yè)級應(yīng)用中,批量數(shù)據(jù)處理是一個非常常見的需求。比如月底的工資代發(fā)、銀行對賬、數(shù)據(jù)報表生成等。當(dāng)數(shù)據(jù)量達(dá)到幾十萬甚至上百萬時,如何高效、可靠地處理這些數(shù)據(jù),就成了一個技術(shù)挑戰(zhàn)。
本文將以"50萬筆工資代發(fā)"為實(shí)際場景,詳細(xì)介紹如何使用Spring Batch框架來處理大規(guī)模批量數(shù)據(jù),并重點(diǎn)講解當(dāng)處理失敗時,如何實(shí)現(xiàn)部分回滾機(jī)制,確保已成功處理的數(shù)據(jù)不會因?yàn)樯倭渴∮涗浂炕貪L。
一、什么是Spring Batch?
1.1 Spring Batch簡介
Spring Batch是一個輕量級的、全面的批處理框架,由Spring團(tuán)隊(duì)開發(fā),旨在幫助企業(yè)開發(fā)健壯的批處理應(yīng)用程序。它于2008年首次發(fā)布,經(jīng)過十多年的發(fā)展,已經(jīng)成為Java批處理領(lǐng)域的事實(shí)標(biāo)準(zhǔn)。
Spring Batch的核心設(shè)計理念包括:
- Chunk-oriented Processing(塊級處理):將大量數(shù)據(jù)分批處理,避免內(nèi)存溢出
- 事務(wù)管理:每個Chunk作為一個獨(dú)立的事務(wù),支持部分回滾
- 容錯機(jī)制:支持跳過(Skip)、重試(Retry)等容錯策略
- 作業(yè)調(diào)度:支持定時任務(wù)、手動觸發(fā)等多種調(diào)度方式
- 監(jiān)控與統(tǒng)計:提供完整的執(zhí)行記錄和統(tǒng)計信息
1.2 核心概念詳解
Job(作業(yè))
Job是批處理的核心概念,代表一個完整的批處理任務(wù)。一個Job可以包含多個Step,按順序或并行執(zhí)行。
@Bean
public Job salaryPaymentJob() {
return jobBuilderFactory.get("salaryPaymentJob")
.start(step1())
.next(step2())
.build();
}
Step(步驟)
Step是Job的基本執(zhí)行單元,每個Step包含:
- ItemReader:讀取數(shù)據(jù)
- ItemProcessor:處理數(shù)據(jù)(可選)
- ItemWriter:寫入數(shù)據(jù)
@Bean
public Step salaryPaymentStep() {
return stepBuilderFactory.get("salaryPaymentStep")
.<SalaryPayment, SalaryPayment>chunk(1000)
.reader(reader())
.processor(processor())
.writer(writer())
.build();
}
Chunk(數(shù)據(jù)塊)
Chunk是Spring Batch處理數(shù)據(jù)的基本單位。每次從Reader讀取指定數(shù)量的記錄,處理后一起提交到數(shù)據(jù)庫:
讀取1000條 → 處理1000條 → 寫入1000條 → 提交事務(wù)
1.3 應(yīng)用場景
Spring Batch適用于以下典型場景:
| 場景 | 描述 | 示例 |
|---|---|---|
| 數(shù)據(jù)遷移 | 跨系統(tǒng)數(shù)據(jù)同步 | 從舊系統(tǒng)遷移數(shù)據(jù)到新系統(tǒng) |
| 數(shù)據(jù)轉(zhuǎn)換 | ETL過程 | 從數(shù)據(jù)庫讀取、轉(zhuǎn)換、寫入數(shù)據(jù)倉庫 |
| 批量處理 | 定期批量操作 | 月底工資代發(fā)、銀行對賬 |
| 報表生成 | 定期生成報表 | 每日交易匯總報表 |
1.4 與其他框架對比
| 特性 | Spring Batch | Quartz | Scheduled Executor |
|---|---|---|---|
| 批量處理 | ? 專用 | 需要 | 需要 |
| 事務(wù)管理 | ? 內(nèi)置 | 無 | 無 |
| 容錯機(jī)制 | ? 完善的Skip/Retry | 無 | 無 |
| 監(jiān)控統(tǒng)計 | ? 數(shù)據(jù)庫持久化 | 基礎(chǔ) | 無 |
| 并行處理 | ? 多種模式 | 無 | 基礎(chǔ) |
為什么需要部分回滾?
想象一下:你需要處理50萬筆工資代發(fā),如果第49萬筆記錄因?yàn)殂y行卡號錯誤而失敗,在沒有部分回滾機(jī)制的情況下,前面489,999筆已成功處理的數(shù)據(jù)會全部回滾!這對于業(yè)務(wù)來說是不可接受的。
二、系統(tǒng)架構(gòu)設(shè)計
為了實(shí)現(xiàn)50萬筆工資代發(fā)的高效處理,我們設(shè)計了如下的系統(tǒng)架構(gòu):

上圖展示了Spring Batch工資代發(fā)系統(tǒng)的分層架構(gòu):
- Web層:提供監(jiān)控面板,支持Job啟動/停止、實(shí)時狀態(tài)監(jiān)控和統(tǒng)計信息查詢
- Batch控制層:REST API接口,包含JobLauncher和JobRepository
- Spring Batch核心層:Job→Step→Chunk的處理流程,包含Reader、Processor、Writer三大組件
- 數(shù)據(jù)存儲層:MySQL數(shù)據(jù)庫、CSV文件和Job執(zhí)行日志
2.1 核心組件說明
| 組件 | 職責(zé) | 實(shí)現(xiàn)類 |
|---|---|---|
| Job | 整個批處理任務(wù) | SalaryPaymentJob |
| Step | 任務(wù)中的一個步驟 | SalaryPaymentStep |
| ItemReader | 數(shù)據(jù)讀取器 | FlatFileItemReader(讀取CSV) |
| ItemProcessor | 數(shù)據(jù)處理器 | SalaryPaymentProcessor(數(shù)據(jù)驗(yàn)證) |
| ItemWriter | 數(shù)據(jù)寫入器 | JdbcBatchItemWriter(批量寫入數(shù)據(jù)庫) |
三、部分回滾機(jī)制原理
3.1 Chunk-Oriented Processing
Spring Batch采用**Chunk-Oriented Processing(塊級處理)**模式,這是實(shí)現(xiàn)部分回滾的核心機(jī)制:

上圖展示了Batch處理的核心流程:Reader讀取數(shù)據(jù) → Processor處理驗(yàn)證 → Writer批量寫入,形成完整的處理管道。
對于50萬筆數(shù)據(jù)的處理,Chunk機(jī)制的工作方式如下:
50萬筆數(shù)據(jù)
│
├─? Chunk 1 (1-1000筆) ──? 獨(dú)立事務(wù) ──? 成功提交
├─? Chunk 2 (1001-2000筆) ──? 獨(dú)立事務(wù) ──? 成功提交
├─? Chunk 3 (2001-3000筆) ──? 獨(dú)立事務(wù) ──? 第2500筆失敗 → 重試3次 → 跳過 → 其余999筆提交
├─? Chunk 4 (3001-4000筆) ──? 獨(dú)立事務(wù) ──? 成功提交
...
└─? Chunk 500 (499001-500000筆) ──? 獨(dú)立事務(wù) ──? 成功提交
最終結(jié)果:499,999筆成功,1筆被跳過
關(guān)鍵配置:
- chunkSize: 1000(每1000筆提交一次)
- skipLimit: 100(最多跳過100筆失敗記錄)
- retryLimit: 3(每筆失敗重試3次)
3.2 事務(wù)邊界與部分回滾
每個Chunk是獨(dú)立的事務(wù)單元,這是實(shí)現(xiàn)部分回滾的關(guān)鍵:

上圖清晰地展示了事務(wù)邊界和部分回滾的工作機(jī)制:
事務(wù)規(guī)則:
- Chunk內(nèi)任意記錄失敗 → 整個Chunk回滾
- 重試成功 → 繼續(xù)處理
- 重試失敗且可跳過 → 跳過該記錄,繼續(xù)處理Chunk內(nèi)剩余記錄
- 跳過次數(shù)超限 → 整個Job失敗
實(shí)際案例: 假設(shè)Chunk 3中有1000筆數(shù)據(jù),第500筆驗(yàn)證失敗:
- Spring Batch回滾整個Chunk 3
- 重新讀取Chunk 3的1000筆數(shù)據(jù)
- 處理到第500筆時,捕獲異常
- 重試3次后仍然失敗
- 檢查是否可跳過(IllegalArgumentException在跳過列表中)
- 跳過第500筆,繼續(xù)處理501-1000筆
- 最終Chunk 3成功提交999筆,1筆被跳過
3.3 容錯策略配置
.faultTolerant() // 啟用容錯
.skipLimit(100) // 最多跳過100條
.skip(IllegalArgumentException.class) // 跳過數(shù)據(jù)驗(yàn)證異常
.skip(NullPointerException.class) // 跳過空指針異常
.retryLimit(3) // 失敗重試3次
.retry(Exception.class) // 重試所有異常
四、50萬筆工資代發(fā)數(shù)據(jù)處理流程
在理解了部分回滾機(jī)制后,我們來看完整的工資代發(fā)數(shù)據(jù)處理流程:

上圖展示了從CSV文件讀取到數(shù)據(jù)庫寫入的完整數(shù)據(jù)流,包含以下關(guān)鍵步驟:
- 數(shù)據(jù)讀取:FlatFileItemReader讀取CSV文件,每行映射為SalaryPayment對象
- 數(shù)據(jù)驗(yàn)證:SalaryPaymentProcessor進(jìn)行數(shù)據(jù)校驗(yàn)
- 員工ID非空驗(yàn)證
- 金額范圍驗(yàn)證(0.01-100萬)
- 銀行卡號格式驗(yàn)證(16-19位數(shù)字)
- 狀態(tài)更新:設(shè)置狀態(tài)為PROCESSING,生成唯一交易ID
- 批量寫入:JdbcBatchItemWriter批量寫入數(shù)據(jù)庫
- 異常處理:驗(yàn)證失敗的記錄被跳過,記錄到失敗列表
五、核心代碼實(shí)現(xiàn)
5.1 Job配置
@Configuration
public class SalaryPaymentJobConfig {
@Value("${batch.chunk.size:1000}")
private int chunkSize; // 每次處理的記錄數(shù)
@Value("${batch.skip.limit:100}")
private int skipLimit; // 跳過限制
@Value("${batch.retry.limit:3}")
private int retryLimit; // 重試次數(shù)
@Bean
public Step salaryPaymentStep() {
return stepBuilderFactory
.get("salaryPaymentStep")
.<SalaryPayment, SalaryPayment>chunk(chunkSize)
.reader(salaryPaymentReader())
.processor(salaryPaymentProcessor())
.writer(salaryPaymentWriter())
.faultTolerant() // 啟用容錯
.skipLimit(skipLimit)
.skip(IllegalArgumentException.class)
.skip(NullPointerException.class)
.retryLimit(retryLimit)
.retry(Exception.class)
.listener(new SalaryItemReadListener())
.listener(new SalaryItemWriteListener())
.build();
}
@Bean
public Job salaryPaymentJob(Step step, SalaryJobExecutionListener listener) {
return jobBuilderFactory.get("salaryPaymentJob")
.incrementer(new RunIdIncrementer())
.listener(listener)
.start(step)
.build();
}
}
5.2 數(shù)據(jù)讀取器
@Bean
public FlatFileItemReader<SalaryPayment> salaryPaymentReader() {
FlatFileItemReader<SalaryPayment> reader = new FlatFileItemReader<>();
reader.setName("salaryPaymentReader");
reader.setResource(new ClassPathResource("input/salary-payments.csv"));
reader.setLinesToSkip(1); // 跳過CSV標(biāo)題行
// 設(shè)置列映射
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
tokenizer.setNames(new String[]{
"employeeId", "employeeName", "accountNumber",
"accountName", "bankName", "amount", "currency",
"paymentDate", "remark"
});
// 設(shè)置字段映射
BeanWrapperFieldSetMapper<SalaryPayment> mapper = new BeanWrapperFieldSetMapper<>();
mapper.setTargetType(SalaryPayment.class);
DefaultLineMapper<SalaryPayment> lineMapper = new DefaultLineMapper<>();
lineMapper.setLineTokenizer(tokenizer);
lineMapper.setFieldSetMapper(mapper);
reader.setLineMapper(lineMapper);
return reader;
}
5.3 數(shù)據(jù)處理器(驗(yàn)證邏輯)
public class SalaryPaymentProcessor implements ItemProcessor<SalaryPayment, SalaryPayment> {
private static final BigDecimal MIN_AMOUNT = new BigDecimal("0.01");
private static final BigDecimal MAX_AMOUNT = new BigDecimal("1000000");
@Override
public SalaryPayment process(SalaryPayment item) throws Exception {
// 1. 數(shù)據(jù)驗(yàn)證
if (item.getEmployeeId() == null || item.getEmployeeId().trim().isEmpty()) {
throw new IllegalArgumentException("員工ID不能為空");
}
// 2. 金額驗(yàn)證
if (item.getAmount() == null) {
throw new IllegalArgumentException("發(fā)放金額不能為空");
}
if (item.getAmount().compareTo(MIN_AMOUNT) < 0) {
throw new IllegalArgumentException("發(fā)放金額不能小于0.01元");
}
if (item.getAmount().compareTo(MAX_AMOUNT) > 0) {
throw new IllegalArgumentException("發(fā)放金額不能大于100萬元");
}
// 3. 銀行卡號驗(yàn)證
if (item.getAccountNumber() == null ||
item.getAccountNumber().length() < 16 ||
item.getAccountNumber().length() > 19) {
throw new IllegalArgumentException("銀行賬號長度必須在16-19位之間");
}
// 4. 設(shè)置處理狀態(tài)
item.setStatus("PROCESSING");
item.setTransactionId("SAL" + System.currentTimeMillis() + item.getEmployeeId());
return item;
}
}
5.4 數(shù)據(jù)寫入器
public class SalaryPaymentWriter implements ItemWriter<SalaryPayment> {
private final JdbcBatchItemWriter<SalaryPayment> delegate;
public SalaryPaymentWriter(DataSource dataSource) {
this.delegate = new JdbcBatchItemWriter<>();
this.delegate.setDataSource(dataSource);
this.delegate.setSql(
"INSERT INTO salary_payment " +
"(employee_id, employee_name, account_number, account_name, " +
"bank_name, amount, currency, payment_date, remark, " +
"status, transaction_id, create_time, update_time) " +
"VALUES (:employeeId, :employeeName, :accountNumber, :accountName, " +
":bankName, :amount, :currency, :paymentDate, :remark, " +
":status, :transactionId, :createTime, :updateTime)");
this.delegate.setItemSqlParameterSourceProvider(
new BeanPropertyItemSqlParameterSourceProvider<>()
);
}
@Override
public void write(List<? extends SalaryPayment> items) throws Exception {
delegate.write(items);
}
}
5.5 自定義SkipPolicy
@Component
public class PartialRollbackHandler implements SkipPolicy {
private static final int SKIP_LIMIT = 100;
@Override
public boolean shouldSkip(Throwable throwable, int skipCount) {
// 超過跳過限制
if (skipCount >= SKIP_LIMIT) {
return false;
}
// 文件不存在,不能跳過
if (throwable instanceof FileNotFoundException) {
return false;
}
// 數(shù)據(jù)格式錯誤,可以跳過
if (throwable instanceof FlatFileParseException) {
return true;
}
// 數(shù)據(jù)驗(yàn)證失敗,可以跳過
if (throwable instanceof IllegalArgumentException ||
throwable instanceof NullPointerException) {
return true;
}
return false;
}
}
六、監(jiān)控與調(diào)度架構(gòu)
除了數(shù)據(jù)處理,Spring Batch還提供了完善的監(jiān)控和調(diào)度能力:

上圖展示了完整的監(jiān)控與調(diào)度架構(gòu):
調(diào)度層:支持三種調(diào)度方式
- Quartz調(diào)度器:支持分布式調(diào)度,適合集群環(huán)境
- Spring Task調(diào)度:簡單的定時任務(wù),輕量級選擇
- Cron表達(dá)式:靈活的時間配置
執(zhí)行層:核心執(zhí)行組件
- JobLauncher:啟動作業(yè),創(chuàng)建執(zhí)行上下文
- JobOperator:操作作業(yè),支持停止/重啟/重試
- StepExecution:步驟執(zhí)行,采用Chunk處理模式
- ThreadPoolExecutor:線程池,實(shí)現(xiàn)并發(fā)處理
監(jiān)控層:監(jiān)控與統(tǒng)計
- JobRepository:存儲元數(shù)據(jù)(BATCH_JOB_INSTANCE、BATCH_JOB_EXECUTION、BATCH_STEP_EXECUTION)
- JobExplorer:查詢作業(yè)狀態(tài)、獲取執(zhí)行歷史
- Metrics:處理記錄數(shù)、執(zhí)行時間、失敗率統(tǒng)計
數(shù)據(jù)層:數(shù)據(jù)存儲
- MySQL 8.0:存儲元數(shù)據(jù)表、業(yè)務(wù)數(shù)據(jù)表、日志記錄
- Redis緩存:執(zhí)行狀態(tài)緩存、計數(shù)器、分布式鎖
七、數(shù)據(jù)庫設(shè)計
7.1 工資代發(fā)表
CREATE TABLE salary_payment (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
employee_id VARCHAR(50) NOT NULL COMMENT '員工ID',
employee_name VARCHAR(100) NOT NULL COMMENT '員工姓名',
account_number VARCHAR(50) NOT NULL COMMENT '銀行賬號',
account_name VARCHAR(100) NOT NULL COMMENT '賬戶名稱',
bank_name VARCHAR(100) NOT NULL COMMENT '開戶行',
amount DECIMAL(18,2) NOT NULL COMMENT '發(fā)放金額',
currency VARCHAR(10) NOT NULL DEFAULT 'CNY' COMMENT '幣種',
payment_date DATETIME NOT NULL COMMENT '發(fā)放日期',
remark VARCHAR(500) COMMENT '備注',
status VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '狀態(tài)',
transaction_id VARCHAR(100) COMMENT '交易ID',
error_message VARCHAR(1000) COMMENT '錯誤信息',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_employee_id (employee_id),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
7.2 Spring Batch元表
Spring Batch框架會自動創(chuàng)建以下元表來存儲Job執(zhí)行信息:
batch_job_instance- Job實(shí)例表batch_job_execution- Job執(zhí)行表batch_job_execution_params- Job參數(shù)表batch_step_execution- Step執(zhí)行表batch_step_execution_context- Step上下文表
八、REST API設(shè)計
@RestController
@RequestMapping("/api/batch")
public class BatchJobController {
// 啟動Job
@PostMapping("/start")
public ResponseEntity<Map<String, Object>> startJob(
@RequestParam String inputFile
) {
JobParameters params = new JobParametersBuilder()
.addLong("startTime", System.currentTimeMillis())
.addString("inputFile", inputFile)
.toJobParameters();
JobExecution execution = jobLauncher.run(salaryPaymentJob, params);
return ResponseEntity.ok(result);
}
// 獲取Job狀態(tài)
@GetMapping("/status/{jobExecutionId}")
public ResponseEntity<Map<String, Object>> getJobStatus(
@PathVariable Long jobExecutionId
) {
JobExecution execution = jobRepository.getJobExecution(jobExecutionId);
// 返回執(zhí)行詳情
}
// 停止Job
@PostMapping("/stop/{jobExecutionId}")
public ResponseEntity<Map<String, Object>> stopJob(
@PathVariable Long jobExecutionId
) {
JobExecution execution = jobRepository.getJobExecution(jobExecutionId);
execution.stop();
return ResponseEntity.ok(result);
}
// 獲取統(tǒng)計信息
@GetMapping("/statistics")
public ResponseEntity<Map<String, Object>> getStatistics() {
// 返回總數(shù)、成功數(shù)、失敗數(shù)等統(tǒng)計
}
// 健康檢查
@GetMapping("/health")
public ResponseEntity<Map<String, Object>> health() {
// 返回系統(tǒng)健康狀態(tài)
}
}
九、性能優(yōu)化與并行處理
當(dāng)數(shù)據(jù)量達(dá)到50萬甚至更多時,單線程處理可能成為瓶頸。Spring Batch提供了多種并行處理方式。
9.1 多線程并發(fā)處理
Spring Batch支持多線程并發(fā)處理,大幅提升處理效率:

上圖展示了多線程并發(fā)處理的工作原理:
核心機(jī)制:
- 主線程:創(chuàng)建線程池,分配任務(wù)
- 工作線程:并發(fā)執(zhí)行多個Step或Chunk
- 線程安全:JobRepository保證線程安全的狀態(tài)管理
- 負(fù)載均衡:任務(wù)均勻分配到各個線程
配置示例:
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("salary-batch-");
executor.initialize();
return executor;
}
// 在Step中使用
.step(stepName)
.chunk(chunkSize)
.taskExecutor(taskExecutor())
.throttleLimit(10) // 限制并發(fā)數(shù)
.build();
9.2 分區(qū)處理(Partitioning)
對于超大數(shù)據(jù)集,可以使用分區(qū)處理實(shí)現(xiàn)更高程度的并行:

上圖展示了分區(qū)處理的架構(gòu):
核心組件:
- Master Step:負(fù)責(zé)創(chuàng)建和管理分區(qū)
- Slave Step:每個分區(qū)獨(dú)立執(zhí)行
- Partitioner:將數(shù)據(jù)分成多個分區(qū)
- TaskExecutor:線程池執(zhí)行分區(qū)任務(wù)
配置示例:
@Bean
public Step masterStep() {
return stepBuilderFactory.get("masterStep")
.partitioner(slaveStep().getName(), rangePartitioner(1, 10))
.step(slaveStep())
.gridSize(10) // 分成10個分區(qū)
.taskExecutor(taskExecutor())
.build();
}
@Bean
public Partitioner rangePartitioner(int min, int max) {
return new Partitioner() {
@Override
public Map<String, ExecutionContext> partition(int gridSize) {
Map<String, ExecutionContext> result = new HashMap<>();
int range = (max - min) / gridSize;
for (int i = 0; i < gridSize; i++) {
ExecutionContext context = new ExecutionContext();
context.putInt("minValue", min + i * range);
context.putInt("maxValue", min + (i + 1) * range - 1);
result.put("partition" + i, context);
}
return result;
}
};
}
9.3 調(diào)優(yōu)參數(shù)
| 參數(shù) | 推薦值 | 說明 |
|---|---|---|
| chunkSize | 1000-5000 | 根據(jù)記錄大小調(diào)整,越大吞吐量越高但內(nèi)存占用也越大 |
| skipLimit | 100-500 | 根據(jù)數(shù)據(jù)質(zhì)量設(shè)置 |
| retryLimit | 3-5 | 過多會浪費(fèi)時間,過少可能誤判暫時性故障 |
| 線程池大小 | CPU核心數(shù)*2 | 用于多線程處理 |
9.4 批量寫入優(yōu)化
使用JDBC批量操作代替單條插入:
// 單條插入(慢)
for (SalaryPayment p : payments) {
jdbcTemplate.update(sql, p.getId(), p.getName(), ...);
}
// 批量插入(快)
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
// 設(shè)置參數(shù)
}
@Override
public int getBatchSize() {
return payments.size();
}
});
9.5 索引優(yōu)化
-- 為常用查詢字段添加索引 CREATE INDEX idx_employee_id ON salary_payment(employee_id); CREATE INDEX idx_status ON salary_payment(status); CREATE INDEX idx_create_time ON salary_payment(create_time); -- 復(fù)合索引 CREATE INDEX idx_status_employee ON salary_payment(status, employee_id);
十、實(shí)際應(yīng)用場景
場景1:月底工資代發(fā)
某公司月底需要為50,000名員工發(fā)放工資,使用Spring Batch:
- 設(shè)置chunkSize=1000,分成50個Chunk處理
- 假設(shè)第23個Chunk中第23,456號員工銀行卡號錯誤
- 系統(tǒng)重試3次后跳過該記錄
- 最終結(jié)果:49,999筆成功,1筆記錄到失敗列表供后續(xù)處理
場景2:銀行對賬文件處理
銀行提供100萬筆交易對賬文件:
- 設(shè)置chunkSize=5000,提高處理效率
- 使用多線程并發(fā)處理(Partitioning)
- 完成后生成對賬差異報告
場景3:數(shù)據(jù)報表生成
每天凌晨生成T+1交易報表:
- 使用Spring Task定時調(diào)度
- 讀取當(dāng)日交易數(shù)據(jù)
- 生成Excel報表并發(fā)送郵件
十一、常見問題與解決方案
Q1: Job執(zhí)行一半掛了怎么辦?
Spring Batch支持Job重啟。通過JobRepository記錄的執(zhí)行狀態(tài),可以從上次失敗的位置繼續(xù)執(zhí)行:
.job(salaryPaymentJob)
.allowStartIfComplete(false) // 已完成的Job不重新執(zhí)行
.restartable(true) // 允許重啟
Q2: 如何實(shí)現(xiàn)并行處理?
使用Partitioning方式實(shí)現(xiàn)多線程并行處理:
@Bean
public Step masterStep() {
return stepBuilderFactory.get("masterStep")
.partitioner(slaveStep().getName(), partitioner())
.step(slaveStep())
.gridSize(10) // 分成10個分區(qū)并行處理
.taskExecutor(taskExecutor())
.build();
}
Q3: 處理失敗的數(shù)據(jù)如何重試?
可以通過以下方式重試:
- 查詢status='FAILED'的記錄
- 修正錯誤數(shù)據(jù)
- 將status改回'PENDING'
- 重新執(zhí)行Job
十二、總結(jié)
Spring Batch作為成熟的批處理框架,提供了完整的解決方案來處理大規(guī)模批量數(shù)據(jù)。
適用場景:
- 銀行對賬、清算
- 工資代發(fā)、批量轉(zhuǎn)賬
- 報表生成、數(shù)據(jù)導(dǎo)出
注意事項(xiàng):
- 合理設(shè)置chunkSize,平衡內(nèi)存和性能
- 配置合適的Skip和Retry策略
- 做好失敗記錄的重處理機(jī)制
- 定期清理Job執(zhí)行歷史數(shù)據(jù)
到此這篇關(guān)于Spring Batch實(shí)戰(zhàn)示例的文章就介紹到這了,更多相關(guān)Spring Batch內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis 注解操作與 XML 配置增刪改查全流程實(shí)現(xiàn)代碼
本文介紹了MyBatis,它是一款輕量級的持久層框架,能簡化JDBC冗余代碼,支持自定義SQL與對象映射,并兼顧開發(fā)效率與靈活性,通過實(shí)例代碼介紹了MyBatis 注解操作與 XML 配置增刪改查全流程實(shí)現(xiàn)代碼,感興趣的朋友一起看看吧2026-01-01
Spring整合Mybatis使用<context:property-placeholder>時的坑
這篇文章主要介紹了Spring整合Mybatis使用<context:property-placeholder>時的坑 的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-06-06
JAVA實(shí)現(xiàn)生成順序ID,不浪費(fèi)ID
這篇文章主要介紹了JAVA實(shí)現(xiàn)生成順序ID,不浪費(fèi)ID問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04
Java異常處理UncaughtExceptionHandler使用實(shí)例代碼詳解
當(dāng)一個線程由于未捕獲異常即將終止時,Java虛擬機(jī)將使用thread . getuncaughtexceptionhandler()查詢線程的uncaughtException處理程序,并調(diào)用處理程序的uncaughtException方法,將線程和異常作為參數(shù)傳遞2023-03-03
Java創(chuàng)建,編輯與刪除Excel迷你圖表的實(shí)現(xiàn)方法
迷你圖是Excel工作表單元格中表示數(shù)據(jù)的微型圖表。本文將通過Java代碼示例介紹如何在Excel中創(chuàng)建迷你圖表,以及編輯和刪除表格中的迷你圖表,需要的可以參考一下2022-05-05
Java編程實(shí)現(xiàn)高斯模糊和圖像的空間卷積詳解
這篇文章主要介紹了Java編程實(shí)現(xiàn)高斯模糊和圖像的空間卷積詳解,具有一定參考價值,需要的朋友可以了解下。2017-11-11
Java?FTP協(xié)議實(shí)現(xiàn)文件下載功能
FTP(File?Transfer?Protocol)就是文件傳輸協(xié)議。通過FTP客戶端從遠(yuǎn)程FTP服務(wù)器上拷貝文件到本地計算機(jī)稱為下載,將本地計算機(jī)上的文件復(fù)制到遠(yuǎn)程FTP服務(wù)器上稱為上傳,上傳和下載是FTP最常用的兩個功能2022-11-11

