Spring boot 如何为每个区块创建多个文件(csv)?

Spring boot 如何为每个区块创建多个文件(csv)?,spring-boot,spring-batch,Spring Boot,Spring Batch,嗨,我是SpringBatch的新手,我想为每个处理的区块创建多个文件(csv)。文件名类似于timestamp.csv。 知道我该怎么做吗?基本上,它是将一个大文件拆分为更小的文件 谢谢大家! CSV文件基本上是文本文件,末尾有一个新行字符 因此,就将一个大的CSV文件拆分为较小的文件而言,您只需在Java中逐行读取大文件,,当您的读取行数达到阈值计数/每个小文件的最大计数(10、100、1000等),您就可以根据需要创建一个具有命名约定的新文件,并在其中转储数据 BufferedReade

嗨,我是SpringBatch的新手,我想为每个处理的区块创建多个文件(csv)。文件名类似于timestamp.csv。 知道我该怎么做吗?基本上,它是将一个大文件拆分为更小的文件


谢谢大家!

CSV文件基本上是文本文件,末尾有一个新行字符

因此,就将一个大的CSV文件拆分为较小的文件而言,您只需在Java中逐行读取大文件,当您的读取行数达到阈值计数/每个小文件的最大计数(10、100、1000等),您就可以根据需要创建一个具有命名约定的新文件,并在其中转储数据

BufferedReader
是逐行读取文本文件的主类

实现此逻辑与Spring批处理无关,但可以使用Java或操作系统级命令

因此,您有两个不同的逻辑部分,逐行读取大文件并创建csv…您可以将这两个部分作为单独的组件开发,并根据您的业务需求在适当的位置将其插入Spring Batch Framework

有一个java库可以轻松地处理CSV文件&您可能会喜欢使用它,这取决于所涉及的复杂性

<dependency>
        <groupId>com.opencsv</groupId>
        <artifactId>opencsv</artifactId>
        <version>4.6</version>
</dependency>

com.opencsv
opencsv
4.6

在spring batch中使用
分区器
,有关实施细节,请查看


  • 查看API文档

    我会使用命令行实用程序,比如
    split
    命令(或等效命令),或者尝试使用纯Java来实现(请参阅)

    但是,如果您真的想使用Spring批处理,那么您可以使用以下方法:

    import java.time.LocalDateTime;
    
    import org.springframework.batch.core.Job;
    import org.springframework.batch.core.JobParameters;
    import org.springframework.batch.core.Step;
    import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
    import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
    import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
    import org.springframework.batch.core.launch.JobLauncher;
    import org.springframework.batch.item.ExecutionContext;
    import org.springframework.batch.item.ItemWriter;
    import org.springframework.batch.item.file.FlatFileItemReader;
    import org.springframework.batch.item.file.FlatFileItemWriter;
    import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
    import org.springframework.batch.item.file.mapping.PassThroughLineMapper;
    import org.springframework.batch.item.file.transform.PassThroughLineAggregator;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.FileSystemResource;
    
    @Configuration
    @EnableBatchProcessing
    public class MyJob {
    
        private final JobBuilderFactory jobBuilderFactory;
    
        private final StepBuilderFactory stepBuilderFactory;
    
        public MyJob(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory) {
            this.jobBuilderFactory = jobBuilderFactory;
            this.stepBuilderFactory = stepBuilderFactory;
        }
    
        @Bean
        public FlatFileItemReader<String> itemReader() {
            return new FlatFileItemReaderBuilder<String>()
                    .name("flatFileReader")
                    .resource(new FileSystemResource("foos.txt"))
                    .lineMapper(new PassThroughLineMapper())
                    .build();
        }
    
        @Bean
        public ItemWriter<String> itemWriter() {
            final FlatFileItemWriter<String> writer = new FlatFileItemWriter<>();
            writer.setLineAggregator(new PassThroughLineAggregator<>());
            writer.setName("chunkFileItemWriter");
            return items -> {
                writer.setResource(new FileSystemResource("foos" + getTimestamp() + ".txt"));
                writer.open(new ExecutionContext());
                writer.write(items);
                writer.close();
            };
        }
    
        private String getTimestamp() {
            // TODO tested on unix/linux systems, update as needed to not contain illegal characters for a file name on MS windows
            return LocalDateTime.now().toString();
        }
    
        @Bean
        public Step step() {
            return stepBuilderFactory.get("step")
                    .<String, String>chunk(3)
                    .reader(itemReader())
                    .writer(itemWriter())
                    .build();
        }
    
        @Bean
        public Job job() {
            return jobBuilderFactory.get("job")
                    .start(step())
                    .build();
        }
    
        public static void main(String[] args) throws Exception {
            ApplicationContext context = new AnnotationConfigApplicationContext(MyJob.class);
            JobLauncher jobLauncher = context.getBean(JobLauncher.class);
            Job job = context.getBean(Job.class);
            jobLauncher.run(job, new JobParameters());
        }
    
    }
    
    该示例将每个区块写入一个带有时间戳的单独文件中:

    文件1
    foos2019-11-28009:23:47.769.txt

    foo1
    foo2
    foo3
    
    foo4
    foo5
    foo6
    
    文件2
    foos2019-11-28009:23:47.779.txt

    foo1
    foo2
    foo3
    
    foo4
    foo5
    foo6
    
    顺便说一句,我认为最好使用数字而不是时间戳


    注意:对于这样的用例,我不太关心可重启性。

    因此,在启动区块(读->处理->写)逻辑之前,您希望将大文件拆分为小文件&一个小文件被输入step的区块处理器?Hi@SabirKhan,1个文件(实际上是xlsx),包含大约600k-800k条记录。由于文件太大,无法处理,我必须首先将其拆分为包含100k条记录的csv文件(块)。因此,这部分问题被问及(文件拆分)看起来更像是一个作业预处理,而不是实际的作业逻辑,所以您可以很好地在中编写自定义拆分器逻辑,然后在所有文件上为拆分文件的目录设置实际作业。为了高效执行,分区在这里似乎是一个用例。您好,只是一个简单的问题-传递给call-
    writer.open(new ExecutionContext())
    ExecutionContext
    的目的是什么?是否可以重复使用或每次都需要创建新的?此外,Windows上通过调用创建的文件名存在问题-
    LocalDateTime.now()
    ,因为它包含不允许的字符冒号,
    java.nio.file.InvalidPathException:索引处的非法字符
    可以重用执行上下文。对于时间戳,是的,如果它在windows上导致文件名包含非法字符的值,则可能会出现问题(在发布答案之前,我在mac os上对其进行了测试)。我相应地更新了答案,并将此详细信息留给了用户。Hi@MahmoudBenHassine我得到的错误原因是:java.lang.IllegalArgumentException:必须设置资源。实际上现在我只是在这里用ClassifierCompositeItemWriter做了一些解决方案,@Lp.Don不,你不应该,确保你的资源不是
    null
    。我用一个完整的例子更新了答案。这种步骤可以用作分区步骤的准备任务(与使用带有非可移植操作系统特定命令的
    SystemCommandTasklet
    相比,这实际上是一种可移植的文件拆分方法)@Sabir Khan我应该提到,如果你想重新使用执行上下文,你需要在每次迭代后清除它,但这不是什么大问题,它无论如何都会被GCed。