Spring Batch 多写:分类写与组合写

Spring Batch 多写:分类写与组合写

在本篇文章中,我们将实现 Spring Batch 的多写(Multi-Write),分别介绍 ClassifierCompositeItemWriterCompositeItemWriter 两种方式,并提供示例代码。下一篇文章中,我们将为这些实现补充单元测试。

注意 :项目中不要忘记添加 @EnableBatchProcessing 注解,否则 Spring Batch 可能无法正常工作。

1 多写方式简介

1.1 分类写(ClassifierCompositeItemWriter)

ClassifierCompositeItemWriter 允许根据不同的分类器(Classifier)将数据分派到不同的 ItemWriter

适用场景

  • 例如账务系统需要将不同渠道的交易数据输出到不同的平台,以便对账。

关键点

  • 通过 Classifier 进行分类,将数据映射到不同的 Writer
  • 若数据未匹配到特定 Writer,则使用默认 Writer 进行处理。
  • 通过 CompositeItemStream 让 Spring 管理 Bean,避免 writer must be open before it can be written to 相关错误。
  • writerMap 通过数据库动态获取渠道列表,实现可扩展性,新增渠道无需修改代码。

1.2 组合写(CompositeItemWriter)

CompositeItemWriter 允许将数据同时输出到多个 ItemWriter

适用场景

  • 例如在数据同步场景下,需要同时写入多个目标位置。

关键点

  • CompositeItemWriter 可以同时委派多个 ItemWriter,实现数据同步写入多个目标。
  • 由于 CompositeItemWriter 实现了 ItemStream 接口,无需在 Step 中额外注册 Stream

2 示例代码

2.1 分类写示例(ClassifierCompositeItemWriter)

java 复制代码
@Configuration
public class MultiWriteJobConfig {

   public static final String DEFAULT = "default";

   @Bean
   @StepScope
   public JdbcPagingItemReader<OrderEntity> myReader() {
       JdbcPagingItemReaderBuilder<OrderEntity> reader = new JdbcPagingItemReaderBuilder<>();
       reader.pageSize(100);
       reader.rowMapper(new BeanPropertyRowMapper<>(OrderEntity.class));

       MySqlPagingQueryProvider provider = new MySqlPagingQueryProvider();
       provider.setSelectClause("id, channel_id, order_no, amount");
       provider.setFromClause("from order_table");
       provider.setSortKeys(Collections.singletonMap("id", Order.ASCENDING));

       reader.queryProvider(provider);
       return reader.build();
   }

   @Bean
   @StepScope
   public ClassifierCompositeItemWriter<OrderEntity> classifierCompositeItemWriter(
           Classifier<OrderEntity, ItemWriter<? super OrderEntity>> classifier) {
       ClassifierCompositeItemWriter<OrderEntity> writer = new ClassifierCompositeItemWriter<>();
       writer.setClassifier(classifier);
       return writer;
   }

   @Bean
   @StepScope
   public Classifier<OrderEntity, ItemWriter<? super OrderEntity>> classifier(Map<String, FlatFileItemWriter<OrderEntity>> writerMap) {
       return orderEntity -> writerMap.getOrDefault(orderEntity.getChannelId(), writerMap.get(DEFAULT));
   }

   @Bean
   @StepScope
   public Map<String, FlatFileItemWriter<OrderEntity>> writerMap() {
       Map<String, FlatFileItemWriter<OrderEntity>> writerMap = new HashMap<>();
       getAllChannelIdsFromDB().forEach(channelId -> writerMap.put(channelId, createWriter(channelId)));
       writerMap.put(DEFAULT, createWriter(DEFAULT));
       return writerMap;
   }

   @Bean
   @StepScope
   public CompositeItemStream compositeItemStream(Map<String, FlatFileItemWriter<OrderEntity>> writerMap) {
       CompositeItemStream compositeItemStream = new CompositeItemStream();
       compositeItemStream.setStreams(writerMap.values().toArray(new ItemStream[0]));
       return compositeItemStream;
   }

   @Bean
   public Step step1(StepBuilderFactory stepBuilderFactory, JdbcPagingItemReader<OrderEntity> myReader,
                     ClassifierCompositeItemWriter<OrderEntity> classifierCompositeItemWriter,
                     CompositeItemStream compositeItemStream) {
       return stepBuilderFactory.get("step1")
               .<OrderEntity, OrderEntity>chunk(100)
               .reader(myReader)
               .writer(classifierCompositeItemWriter)
               .stream(compositeItemStream)
               .build();
   }

   @Bean
   public Job job1(JobBuilderFactory jobBuilderFactory, Step step1) {
       return jobBuilderFactory.get("job1")
               .incrementer(new RunIdIncrementer())
               .start(step1)
               .build();
   }

   private FlatFileItemWriter<OrderEntity> createWriter(String channelId) {
       return new FlatFileItemWriterBuilder<OrderEntity>()
               .name("orderItemWriter")
               .resource(new FileSystemResource("E:\\output\\orders_" + channelId + ".csv"))
               .delimited()
               .delimiter(",")
               .names("id", "channelId", "amount")
               .build();
   }

   private List<String> getAllChannelIdsFromDB() {
       return Arrays.asList("WEIXIN", "ZHIFUBAO");
   }
}

2.2 组合写示例(CompositeItemWriter)

java 复制代码
@Configuration
public class MultiWriteJobConfig {

   @Bean
   @StepScope
   public CompositeItemWriter<OrderEntity> compositeItemWriter() {
       CompositeItemWriter<OrderEntity> writer = new CompositeItemWriter<>();
       writer.setDelegates(getAllChannelIdsFromDB().stream()
               .map(this::createWriter)
               .collect(Collectors.toList()));
       return writer;
   }

   @Bean
   public Step step2(StepBuilderFactory stepBuilderFactory, JdbcPagingItemReader<OrderEntity> myReader,
                     CompositeItemWriter<OrderEntity> compositeItemWriter) {
       return stepBuilderFactory.get("step2")
               .<OrderEntity, OrderEntity>chunk(100)
               .reader(myReader)
               .writer(compositeItemWriter)
               .build();
   }

   @Bean
   public Job job2(JobBuilderFactory jobBuilderFactory, Step step2) {
       return jobBuilderFactory.get("job2")
               .incrementer(new RunIdIncrementer())
               .start(step2)
               .build();
   }
}

下一篇文章,我们将基于Junit实现Job的单元测试。

相关推荐
BS30813_vx8 小时前
基于 Express + uni-app 的养老院管理系统设计与实现
后端·uni-app·express
明月_清风8 小时前
DeepSeek Harness 安全审计实录:四个 PoC 揭露的 Agent 运行时"信任危机"
后端·安全·agent
苏三说技术8 小时前
再见了EasyExcel,我决定用Apache Fesod
后端
XR1234567888 小时前
医院移动医护零漫游无线网络选型指南
java·后端·struts
慧一居士8 小时前
Mybatis Plus中 LambdaQueryWrapper、QueryWrapper 区别对比
spring boot
干到60岁退休的码农8 小时前
13.构建登录接口响应数据
java·spring boot·mybatis
水巷石子9 小时前
学习langChain4j的第二天,体验springBoot中的starter
java·spring boot·学习·spring·langchain4j
ly76899 小时前
Spring Bean生命周期全流程:从BeanDefinition到销毁
java·后端·spring·bean生命周期·beandefinition·初始化回调
leeyi9 小时前
在 Go 里跑不信任的代码:WASM 沙箱的四道墙(第107篇)
后端
摇滚侠9 小时前
《Spring Boot 3:高级与架构设计》第 1 章 BeanDefinitionRegistry 阅读笔记 3
spring boot·笔记·后端