Spring Batch输出数据通过ItemWriter接口的实现类来完成,包括FlatFileItemWriter文本数据输出、StaxEventItemWriter XML文件数据输出、JsonItemWriter JSON文件数据输出、JdbcBatchItemWriter数据库数据插入等实现,更多可用的实现可以参考:https://docs.spring.io/spring-batch/docs/4.2.x/reference/html/appendix.html#itemWritersAppendix,本文只介绍这四种比较常用的输出数据方式。

框架搭建

新建一个Spring Boot项目,版本为2.2.4.RELEASE,artifactId为spring-batch-itemwriter,项目结构如下图所示:

Spring Batch输出数据 - 图1

剩下的数据库层的准备,项目配置,依赖引入和Spring Batch入门文章中的框架搭建步骤一致,这里就不再赘述。

在介绍Spring Batch数据输出之前,我们先准备个简单的数据读取源。在cc.mrbird.batch包下新建entity包,然后在该包下新建TestData实体类:

  1. public class TestData {
  2. private int id;
  3. private String field1;
  4. private String field2;
  5. private String field3;
  6. // get,set,toString略
  7. }

接着在cc.mrbird.batch包下新建reader包,然后在该包下创建ItemReaderConfigure

  1. @Configuration
  2. public class ItemReaderConfigure {
  3. @Bean
  4. public ListItemReader<TestData> simpleReader() {
  5. List<TestData> data = new ArrayList<>();
  6. TestData testData1 = new TestData();
  7. testData1.setId(1);
  8. testData1.setField1("11");
  9. testData1.setField2("12");
  10. testData1.setField3("13");
  11. data.add(testData1);
  12. TestData testData2 = new TestData();
  13. testData2.setId(2);
  14. testData2.setField1("21");
  15. testData2.setField2("22");
  16. testData2.setField3("23");
  17. data.add(testData2);
  18. return new ListItemReader<>(data);
  19. }
  20. }

上面注册了一个ItemReader类型的Bean,后续都用它作为读取数据的来源。

输出文本数据

在cc.mrbird.batch包下新建job包,然后在该包下新建FileItemWriterDemo,用于测试Spring Batch输出数据到文本文件:

  1. @Component
  2. public class FileItemWriterDemo {
  3. @Autowired
  4. private JobBuilderFactory jobBuilderFactory;
  5. @Autowired
  6. private StepBuilderFactory stepBuilderFactory;
  7. @Autowired
  8. private ListItemReader<TestData> simpleReader;
  9. @Bean
  10. public Job fileItemWriterJob() throws Exception {
  11. return jobBuilderFactory.get("fileItemWriterJob")
  12. .start(step())
  13. .build();
  14. }
  15. private Step step() throws Exception {
  16. return stepBuilderFactory.get("step")
  17. .<TestData, TestData>chunk(2)
  18. .reader(simpleReader)
  19. .writer(fileItemWriter())
  20. .build();
  21. }
  22. private FlatFileItemWriter<TestData> fileItemWriter() throws Exception {
  23. FlatFileItemWriter<TestData> writer = new FlatFileItemWriter<>();
  24. FileSystemResource file = new FileSystemResource("/Users/mrbird/Desktop/file");
  25. Path path = Paths.get(file.getPath());
  26. if (!Files.exists(path)) {
  27. Files.createFile(path);
  28. }
  29. // 设置输出文件路径
  30. writer.setResource(file);
  31. // 把读到的每个TestData对象转换为JSON字符串
  32. LineAggregator<TestData> aggregator = item -> {
  33. try {
  34. ObjectMapper mapper = new ObjectMapper();
  35. return mapper.writeValueAsString(item);
  36. } catch (JsonProcessingException e) {
  37. e.printStackTrace();
  38. }
  39. return "";
  40. };
  41. writer.setLineAggregator(aggregator);
  42. writer.afterPropertiesSet();
  43. return writer;
  44. }
  45. }

上面代码中,Step中的Reader使用的是我们上面创建的simpleReader,文本数据输出使用的是FlatFileItemWriterfileItemWriter()方法的代码较为简单,这里就不赘述了。

启动项目后,在/Users/mrbird/Desktop目录下(也就是我的电脑桌面上)会多出个file文件:

Spring Batch输出数据 - 图2

输出xml数据

同样的,xml格式数据输出需要借助spring-oxm框架,在pom中引入相关依赖:

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-oxm</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.thoughtworks.xstream</groupId>
  7. <artifactId>xstream</artifactId>
  8. <version>1.4.11.1</version>
  9. </dependency>

然后在job包下新建XmlFileItemWriterDemo,用于测试Spring Batch输出数据到xml文件:

  1. @Component
  2. public class XmlFileItemWriterDemo {
  3. @Autowired
  4. private JobBuilderFactory jobBuilderFactory;
  5. @Autowired
  6. private StepBuilderFactory stepBuilderFactory;
  7. @Autowired
  8. private ListItemReader<TestData> simpleReader;
  9. @Bean
  10. public Job xmlFileItemWriterJob() throws Exception {
  11. return jobBuilderFactory.get("xmlFileItemWriterJob")
  12. .start(step())
  13. .build();
  14. }
  15. private Step step() throws Exception {
  16. return stepBuilderFactory.get("step")
  17. .<TestData, TestData>chunk(2)
  18. .reader(simpleReader)
  19. .writer(xmlFileItemWriter())
  20. .build();
  21. }
  22. private StaxEventItemWriter<TestData> xmlFileItemWriter() throws IOException {
  23. StaxEventItemWriter<TestData> writer = new StaxEventItemWriter<>();
  24. // 通过XStreamMarshaller将TestData转换为xml
  25. XStreamMarshaller marshaller = new XStreamMarshaller();
  26. Map<String,Class<TestData>> map = new HashMap<>(1);
  27. map.put("test", TestData.class);
  28. marshaller.setAliases(map); // 设置xml标签
  29. writer.setRootTagName("tests"); // 设置根标签
  30. writer.setMarshaller(marshaller);
  31. FileSystemResource file = new FileSystemResource("/Users/mrbird/Desktop/file.xml");
  32. Path path = Paths.get(file.getPath());
  33. if (!Files.exists(path)) {
  34. Files.createFile(path);
  35. }
  36. writer.setResource(file); // 设置目标文件路径
  37. return writer;
  38. }
  39. }

xml类型文件输出使用的是StaxEventItemWriter

启动项目后,在/Users/mrbird/Desktop目录下会多出个file.xml文件:

Spring Batch输出数据 - 图3

输出JSON数据

在job包下新建JsonFileItemWriterDemo,用于测试Spring Batch输出数据到json文件:

  1. @Component
  2. public class JsonFileItemWriterDemo {
  3. @Autowired
  4. private JobBuilderFactory jobBuilderFactory;
  5. @Autowired
  6. private StepBuilderFactory stepBuilderFactory;
  7. @Autowired
  8. private ListItemReader<TestData> simpleReader;
  9. @Bean
  10. public Job jsonFileItemWriterJob() throws Exception {
  11. return jobBuilderFactory.get("jsonFileItemWriterJob")
  12. .start(step())
  13. .build();
  14. }
  15. private Step step() throws Exception {
  16. return stepBuilderFactory.get("step")
  17. .<TestData, TestData>chunk(2)
  18. .reader(simpleReader)
  19. .writer(jsonFileItemWriter())
  20. .build();
  21. }
  22. private JsonFileItemWriter<TestData> jsonFileItemWriter() throws IOException {
  23. // 文件输出目标地址
  24. FileSystemResource file = new FileSystemResource("/Users/mrbird/Desktop/file.json");
  25. Path path = Paths.get(file.getPath());
  26. if (!Files.exists(path)) {
  27. Files.createFile(path);
  28. }
  29. // 将对象转换为json
  30. JacksonJsonObjectMarshaller<TestData> marshaller = new JacksonJsonObjectMarshaller<>();
  31. JsonFileItemWriter<TestData> writer = new JsonFileItemWriter<>(file, marshaller);
  32. // 设置别名
  33. writer.setName("testDatasonFileItemWriter");
  34. return writer;
  35. }
  36. }

json类型文件输出使用的是JsonFileItemWriter

启动项目后,在/Users/mrbird/Desktop目录下会多出个file.json文件:

Spring Batch输出数据 - 图4

输出数据到数据库

在job包下新建DatabaseItemWriterDemo,用于测试Spring Batch输出数据到数据库:

  1. @Component
  2. public class DatabaseItemWriterDemo {
  3. @Autowired
  4. private JobBuilderFactory jobBuilderFactory;
  5. @Autowired
  6. private StepBuilderFactory stepBuilderFactory;
  7. @Autowired
  8. private ListItemReader<TestData> simpleReader;
  9. @Autowired
  10. private DataSource dataSource;
  11. @Bean
  12. public Job datasourceItemWriterJob() {
  13. return jobBuilderFactory.get("datasourceItemWriterJob")
  14. .start(step())
  15. .build();
  16. }
  17. private Step step() {
  18. return stepBuilderFactory.get("step")
  19. .<TestData, TestData>chunk(2)
  20. .reader(simpleReader)
  21. .writer(dataSourceItemWriter())
  22. .build();
  23. }
  24. private ItemWriter<TestData> dataSourceItemWriter() {
  25. // ItemWriter的实现类之一,mysql数据库数据写入使用JdbcBatchItemWriter,
  26. // 其他实现:MongoItemWriter,Neo4jItemWriter等
  27. JdbcBatchItemWriter<TestData> writer = new JdbcBatchItemWriter<>();
  28. writer.setDataSource(dataSource); // 设置数据源
  29. String sql = "insert into TEST(id,field1,field2,field3) values (:id,:field1,:field2,:field3)";
  30. writer.setSql(sql); // 设置插入sql脚本
  31. // 映射TestData对象属性到占位符中的属性
  32. BeanPropertyItemSqlParameterSourceProvider<TestData> provider = new BeanPropertyItemSqlParameterSourceProvider<>();
  33. writer.setItemSqlParameterSourceProvider(provider);
  34. writer.afterPropertiesSet(); // 设置一些额外属性
  35. return writer;
  36. }
  37. }

MySQL关系型数据数据写入使用的是JdbcBatchItemWriter。在测试之前,先清空springbatch数据库TEST表数据,然后启动项目,启动后,TEST表记录如下所示:

Spring Batch输出数据 - 图5

多文本输出

多文本输出和上一节介绍的多文本数据读取类似,都是需要通过代理来完成。我们模拟个同时输出xml格式和普通文本格式的例子。

在cc.mrbird.batch包下新建writer包,然后在该包下新建ItemWriterConfigure配置类:

  1. @Configuration
  2. public class ItemWriterConfigure {
  3. @Bean
  4. public FlatFileItemWriter<TestData> fileItemWriter() throws Exception {
  5. FlatFileItemWriter<TestData> writer = new FlatFileItemWriter<>();
  6. FileSystemResource file = new FileSystemResource("/Users/mrbird/Desktop/file");
  7. Path path = Paths.get(file.getPath());
  8. if (!Files.exists(path)) {
  9. Files.createFile(path);
  10. }
  11. writer.setResource(file); // 设置目标文件路径
  12. // 把读到的每个TestData对象转换为字符串
  13. LineAggregator<TestData> aggregator = item -> {
  14. try {
  15. ObjectMapper mapper = new ObjectMapper();
  16. return mapper.writeValueAsString(item);
  17. } catch (JsonProcessingException e) {
  18. e.printStackTrace();
  19. }
  20. return "";
  21. };
  22. writer.setLineAggregator(aggregator);
  23. writer.afterPropertiesSet();
  24. return writer;
  25. }
  26. @Bean
  27. public StaxEventItemWriter<TestData> xmlFileItemWriter() throws Exception {
  28. StaxEventItemWriter<TestData> writer = new StaxEventItemWriter<>();
  29. // 通过XStreamMarshaller将TestData转换为xml
  30. XStreamMarshaller marshaller = new XStreamMarshaller();
  31. Map<String, Class<TestData>> map = new HashMap<>(1);
  32. map.put("test", TestData.class);
  33. marshaller.setAliases(map); // 设置xml标签
  34. writer.setRootTagName("tests"); // 设置根标签
  35. writer.setMarshaller(marshaller);
  36. FileSystemResource file = new FileSystemResource("/Users/mrbird/Desktop/file.xml");
  37. Path path = Paths.get(file.getPath());
  38. if (!Files.exists(path)) {
  39. Files.createFile(path);
  40. }
  41. writer.setResource(file); // 设置目标文件路径
  42. return writer;
  43. }
  44. }

上面的配置类中,配置了FlatFileItemWriterStaxEventItemWriter类型的ItemWriter Bean,代码步骤和前面介绍的一致。

然后在job包下新建MultiFileItemWriteDemo,用于测试多文本输出:

  1. @Component
  2. public class MultiFileItemWriteDemo {
  3. @Autowired
  4. private JobBuilderFactory jobBuilderFactory;
  5. @Autowired
  6. private StepBuilderFactory stepBuilderFactory;
  7. @Autowired
  8. private DataSource dataSource;
  9. @Autowired
  10. private ListItemReader<TestData> simpleReader;
  11. @Autowired
  12. private ItemStreamWriter<TestData> fileItemWriter;
  13. @Autowired
  14. private ItemStreamWriter<TestData> xmlFileItemWriter;
  15. @Bean
  16. public Job multiFileItemWriterJob() {
  17. return jobBuilderFactory.get("multiFileItemWriterJob")
  18. .start(step())
  19. .build();
  20. }
  21. private Step step() {
  22. return stepBuilderFactory.get("step")
  23. .<TestData, TestData>chunk(2)
  24. .reader(simpleReader)
  25. .writer(classifierMultiFileItemWriter())
  26. .stream(fileItemWriter)
  27. .stream(xmlFileItemWriter)
  28. .build();
  29. }
  30. // 将数据分类,然后分别输出到对应的文件(此时需要将writer注册到ioc容器,否则报
  31. // WriterNotOpenException: Writer must be open before it can be written to)
  32. private ClassifierCompositeItemWriter<TestData> classifierMultiFileItemWriter() {
  33. ClassifierCompositeItemWriter<TestData> writer = new ClassifierCompositeItemWriter<>();
  34. writer.setClassifier((Classifier<TestData, ItemWriter<? super TestData>>) testData -> {
  35. try {
  36. // id能被2整除则输出到普通文本,否则输出到xml文本
  37. return testData.getId() % 2 == 0 ? fileItemWriter : xmlFileItemWriter;
  38. } catch (Exception e) {
  39. e.printStackTrace();
  40. }
  41. return null;
  42. });
  43. return writer;
  44. }
  45. }

ClassifierCompositeItemWriter可以设置不同条件下使用不同的ItemWriter输出数据,此外在Step中,还需通过StepBuilderFactorystream()方法传入使用到的ItemWriter(这里需要注意的是,注入的时候,类型应选择ItemStreamWriter)。

在启动项目前,先删掉/Users/mrbird/Desktop目录下的文件。删掉后,启动项目,结果如下:

Spring Batch输出数据 - 图6

Spring Batch输出数据 - 图7

如果不想用分类,希望所有数据都输出到对应格式的文本中,则可以使用CompositeItemWriter作为代理输出,修改MultiFileItemWriteDemo

  1. @Component
  2. public class MultiFileItemWriteDemo {
  3. @Autowired
  4. private JobBuilderFactory jobBuilderFactory;
  5. @Autowired
  6. private StepBuilderFactory stepBuilderFactory;
  7. @Autowired
  8. private ListItemReader<TestData> simpleReader;
  9. @Autowired
  10. private ItemStreamWriter<TestData> fileItemWriter;
  11. @Autowired
  12. private ItemStreamWriter<TestData> xmlFileItemWriter;
  13. @Bean
  14. public Job multiFileItemWriterJob() {
  15. return jobBuilderFactory.get("multiFileItemWriterJob2")
  16. .start(step())
  17. .build();
  18. }
  19. private Step step() {
  20. return stepBuilderFactory.get("step")
  21. .<TestData, TestData>chunk(2)
  22. .reader(simpleReader)
  23. .writer(multiFileItemWriter())
  24. .build();
  25. }
  26. // 输出数据到多个文件
  27. private CompositeItemWriter<TestData> multiFileItemWriter() {
  28. // 使用CompositeItemWriter代理
  29. CompositeItemWriter<TestData> writer = new CompositeItemWriter<>();
  30. // 设置具体写代理
  31. writer.setDelegates(Arrays.asList(fileItemWriter, xmlFileItemWriter));
  32. return writer;
  33. }
  34. }

在启动项目前,先删掉/Users/mrbird/Desktop目录下的文件。删掉后,启动项目,结果如下:

Spring Batch输出数据 - 图8