mcpField
S
spring-batch

spring-batch

github.com/claude-dev-suite数据与数据库4652 次安装4 天前更新
代码仓库

关于 spring-batch

这项 Spring Batch 技能为使用 Spring Batch 构建 Java 批处理任务提供实用参考,涵盖作业与步骤配置、分块读写、参数、故障排查及实施实践。

主要功能

  1. 01作业与步骤配置示例
  2. 02基于分块的读取、处理和写入
  3. 03文件、JDBC 和 JPA 读写器
  4. 04作业参数用法
  5. 05故障排查与实施实践

适用场景

  1. 01构建 CSV 导入与导出任务
  2. 02为大批量数据配置分块处理
  3. 03设置批处理读取的数据库分页
  4. 04了解重启与故障处理方法

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill spring-batch

安装后在 Claude Code 中输入/spring-batch确认已加载。

附带文件advanced.md

详细介绍

Spring Batch

完整参考:跳过/重试配置、分区、监听器、使用 JobLauncherTestUtils 进行测试、组合写入器和异步处理,请参阅 advanced.md。

深入了解:使用 mcp__documentation__fetch_docs,技术指定为:spring-boot,主题指定为:batch,以获取全面的文档。

快速入门

@Configuration
@EnableBatchProcessing
public class BatchConfig {

    @Bean
    public Job importJob(JobRepository jobRepository, Step step1) {
        return new JobBuilder("importJob", jobRepository)
            .incrementer(new RunIdIncrementer())
            .start(step1)
            .build();
    }

    @Bean
    public Step step1(JobRepository jobRepository,
                      PlatformTransactionManager transactionManager,
                      ItemReader<InputData> reader,
                      ItemProcessor<InputData, OutputData> processor,
                      ItemWriter<OutputData> writer) {
        return new StepBuilder("step1", jobRepository)
            .<InputData, OutputData>chunk(100, transactionManager)
            .reader(reader)
            .processor(processor)
            .writer(writer)
            .build();
    }
}

核心组件

Job
@Bean
public Job complexJob(JobRepository jobRepository,
                      Step extractStep, Step transformStep, Step loadStep) {
    return new JobBuilder("etlJob", jobRepository)
        .incrementer(new RunIdIncrementer())
        .validator(jobParametersValidator())
        .listener(jobExecutionListener())
        .start(extractStep)
        .next(transformStep)
        .next(loadStep)
        .build();
}

// Job with decision
@Bean
public Job conditionalJob(JobRepository jobRepository,
                          Step step1, Step step2, Step errorStep,
                          JobExecutionDecider decider) {
    return new JobBuilder("conditionalJob", jobRepository)
        .start(step1)
        .next(decider)
        .on("COMPLETED").to(step2)
        .from(decider).on("FAILED").to(errorStep)
        .end()
        .build();
}
Step - 分块处理
@Bean
public Step chunkStep(JobRepository jobRepository,
                      PlatformTransactionManager txManager) {
    return new StepBuilder("chunkStep", jobRepository)
        .<Person, Person>chunk(100, txManager)
        .reader(reader())
        .processor(processor())
        .writer(writer())
        .faultTolerant()
        .skipLimit(10)
        .skip(ValidationException.class)
        .retryLimit(3)
        .retry(TransientException.class)
        .build();
}

// Tasklet Step (for simple operations)
@Bean
public Step taskletStep(JobRepository jobRepository,
                        PlatformTransactionManager txManager) {
    return new StepBuilder("taskletStep", jobRepository)
        .tasklet((contribution, chunkContext) -> {
            cleanupService.cleanup();
            return RepeatStatus.FINISHED;
        }, txManager)
        .build();
}

ItemReader

FlatFileItemReader
@Bean
public FlatFileItemReader<Person> csvReader() {
    return new FlatFileItemReaderBuilder<Person>()
        .name("personReader")
        .resource(new ClassPathResource("data/input.csv"))
        .delimited()
        .delimiter(",")
        .names("firstName", "lastName", "email", "age")
        .linesToSkip(1)  // Skip header
        .fieldSetMapper(new BeanWrapperFieldSetMapper<>() {{
            setTargetType(Person.class);
        }})
        .build();
}
JdbcPagingItemReader
@Bean
public JdbcPagingItemReader<Person> pagingReader(DataSource dataSource) {
    Map<String, Order> sortKeys = new HashMap<>();
    sortKeys.put("id", Order.ASCENDING);

    return new JdbcPagingItemReaderBuilder<Person>()
        .name("pagingReader")
        .dataSource(dataSource)
        .selectClause("SELECT id, first_name, last_name, email")
        .fromClause("FROM persons")
        .whereClause("WHERE status = :status")
        .parameterValues(Map.of("status", "ACTIVE"))
        .sortKeys(sortKeys)
        .rowMapper(new BeanPropertyRowMapper<>(Person.class))
        .pageSize(100)
        .build();
}
JpaPagingItemReader
@Bean
public JpaPagingItemReader<Person> jpaReader(EntityManagerFactory emf) {
    return new JpaPagingItemReaderBuilder<Person>()
        .name("jpaReader")
        .entityManagerFactory(emf)
        .queryString("SELECT p FROM Person p WHERE p.status = :status")
        .parameterValues(Map.of("status", Status.ACTIVE))
        .pageSize(100)
        .build();
}

ItemProcessor

@Component
public class PersonProcessor implements ItemProcessor<Person, Person> {

    @Override
    public Person process(Person person) throws Exception {
        // Return null to filter out item
        if (!isValid(person)) {
            return null;
        }

        // Transform
        person.setEmail(person.getEmail().toLowerCase());
        person.setFullName(person.getFirstName() + " " + person.getLastName());
        return person;
    }
}

// Composite processor
@Bean
public CompositeItemProcessor<Person, Person> compositeProcessor() {
    return new CompositeItemProcessorBuilder<Person, Person>()
        .delegates(List.of(
            validationProcessor(),
            transformationProcessor(),
            enrichmentProcessor()
        ))
        .build();
}

ItemWriter

JdbcBatchItemWriter
@Bean
public JdbcBatchItemWriter<Person> jdbcWriter(DataSource dataSource) {
    return new JdbcBatchItemWriterBuilder<Person>()
        .dataSource(dataSource)
        .sql("INSERT INTO persons (first_name, last_name, email) VALUES (:firstName, :lastName, :email)")
        .beanMapped()
        .build();
}
JpaItemWriter
@Bean
public JpaItemWriter<Person> jpaWriter(EntityManagerFactory emf) {
    JpaItemWriter<Person> writer = new JpaItemWriter<>();
    writer.setEntityManagerFactory(emf);
    writer.setUsePersist(true);  // false = merge
    return writer;
}
FlatFileItemWriter
@Bean
public FlatFileItemWriter<Person> csvWriter() {
    return new FlatFileItemWriterBuilder<Person>()
        .name("personWriter")
        .resource(new FileSystemResource("output/persons.csv"))
        .delimited()
        .delimiter(",")
        .names("firstName", "lastName", "email")
        .headerCallback(writer -> writer.write("First Name,Last Name,Email"))
        .build();
}

作业参数

@Bean
@StepScope
public FlatFileItemReader<Person> parameterizedReader(
        @Value("#{jobParameters['inputFile']}") String inputFile) {
    return new FlatFileItemReaderBuilder<Person>()
        .name("reader")
        .resource(new FileSystemResource(inputFile))
        .delimited()
        .names("firstName", "lastName", "email")
        .targetType(Person.class)
        .build();
}

// Running job with parameters
@Service
@RequiredArgsConstructor
public class JobLauncherService {

    private final JobLauncher jobLauncher;
    private final Job importJob;

    public void runJob(String inputFile, LocalDate date) throws Exception {
        JobParameters params = new JobParametersBuilder()
            .addString("inputFile", inputFile)
            .addLocalDate("date", date)
            .addLong("timestamp", System.currentTimeMillis())
            .toJobParameters();

        JobExecution execution = jobLauncher.run(importJob, params);
        log.info("Job status: {}", execution.getStatus());
    }
}

不适用此技能的情况

  • 实时处理 - 使用流处理(Kafka Streams、Flink)
  • 简单的定时任务 - 改用 spring-scheduling
  • 微服务数据同步 - 考虑使用消息传递实现事件驱动
  • 小型数据集 - 批处理的开销可能不值得

反模式

反模式问题解决方案
分块大小过大内存问题、事务耗时长调整分块大小(100-1000)
未配置跳过策略单个错误就会导致作业停止为预期错误配置跳过策略
有状态的 ItemProcessor线程安全问题让处理器保持无状态
忽略作业参数无法重启失败的作业包含标识参数
未进行监控故障无提示配置 JobExecutionListener
大数据量时使用单线程处理速度慢使用分区

快速故障排除

问题诊断修复方法
作业未启动检查作业仓库验证数据库架构
作业重启失败检查作业参数添加 RunIdIncrementer
分块处理缓慢检查提交间隔调整分块大小
内存问题监控堆内存使用情况减小分块大小、以流方式处理数据
跳过策略未生效检查跳过策略配置可跳过的异常

最佳实践

  • ✅ 对大量数据使用分块处理
  • ✅ 配置跳过/重试机制以实现容错
  • ✅ 使用分区实现并行处理
  • ✅ 实现监听器以进行监控
  • ✅ 使用 JobLauncherTestUtils 进行测试
  • ❌ 不要将分块大小设置得过大
  • ❌ 不要悄无声息地忽略错误
  • ❌ 不要忘记为重启设置作业参数

参考文档

评论

支持 Markdown