Java Springboot批量读取/验证多个不同格式的csv文件
我正在评估一个特定项目的springbatch,在网上搜索了很多之后,我还没有找到一个满足我需求的springbatch解决方案 我想知道SpringBatch是否能够在一个作业中读取由不同格式组成的多个CSV文件?例如,假设Person.csv和Address.csv都由不同的格式组成,但相互依赖 我需要阅读、处理数据更正(如toUpperCase等),并验证每个记录 如果出现验证错误,我需要将错误记录到某种类型的对象数组中,在验证完成后,该数组将在稍后提供,并通过电子邮件发送给最终用户进行更正 一旦两个文件中的所有数据都已验证,并且两个文件中均未发生验证错误,请继续使用批处理编写器。如果两个文件中的任何一个出现错误,我需要停止整个作业。如果写入程序在发生错误时已开始写入数据库,则无论错误是否存在于相反的文件中,都需要回滚整个作业 如果两个CSV文件中的任何一个存在任何类型的验证错误,我将无法插入其中任何一个。必须将错误通知最终用户。这些错误将用于在重新处理文件之前进行任何必要的更正 SpringBoot 2中的Spring批处理是否能够实现这种行为 示例 Person.csvJava Springboot批量读取/验证多个不同格式的csv文件,java,spring,spring-boot,spring-batch,Java,Spring,Spring Boot,Spring Batch,我正在评估一个特定项目的springbatch,在网上搜索了很多之后,我还没有找到一个满足我需求的springbatch解决方案 我想知道SpringBatch是否能够在一个作业中读取由不同格式组成的多个CSV文件?例如,假设Person.csv和Address.csv都由不同的格式组成,但相互依赖 我需要阅读、处理数据更正(如toUpperCase等),并验证每个记录 如果出现验证错误,我需要将错误记录到某种类型的对象数组中,在验证完成后,该数组将在稍后提供,并通过电子邮件发送给最终用户进行更
BatchId, personId, firstName, lastName
Address.csv
BatchId, personId, address1
在上面的示例中,两个文件之间的关系是batchId和personId。如果两个文件中的任何一个存在任何类型的验证错误,我必须使整个批处理失败。我想完成对这两个文件的验证,这样我就可以对所有错误做出响应,但不能写入数据库
我想知道SpringBatch是否能够在一个作业中读取由不同格式组成的多个CSV文件
是的,您可以有一个包含多个步骤的单个作业,每个步骤处理给定类型的文件。关键是如何设计作业。可以应用的一种技术是使用暂存表。批处理作业可以创建临时暂存表,在其中加载所需的所有数据,然后在完成后将其删除
在您的情况下,可以通过两个步骤将每个文件加载到特定的暂存表中。每个步骤都可以应用特定于每个文件的验证逻辑。如果这些步骤中有一个失败了,那么你的工作就失败了。暂存表可以有一个无效记录的标记列(这对于报告很有用)
完成这两个准备步骤后,您可以在另一个步骤中从两个暂存表读取数据,并对连接的数据应用交叉验证规则(例如,从两个表中选择并通过BatchId
和PersonId
)连接)。如果此步骤失败,则表示作业失败。否则,您将在适当的位置写入数据
这种技术的优点是,在整个作业期间,数据可以在临时表中使用。因此,每当验证步骤失败时,可以使用流将失败的步骤重定向到“报告步骤”(读取无效数据并发送报告),然后使作业失败。下面是一个您可以使用的独立示例:
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.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableBatchProcessing
public class FlowJobSample {
@Autowired
private JobBuilderFactory jobs;
@Autowired
private StepBuilderFactory steps;
@Bean
public Step personLoadingStep() {
return steps.get("personLoadingStep")
.tasklet((contribution, chunkContext) -> {
System.out.println("personLoadingStep");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step addressLoadingStep() {
return steps.get("addressLoadingStep")
.tasklet((contribution, chunkContext) -> {
System.out.println("addressLoadingStep");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step crossValidationStep() {
return steps.get("crossValidationStep")
.tasklet((contribution, chunkContext) -> {
System.out.println("crossValidationStep");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step reportingStep() {
return steps.get("reportingStep")
.tasklet((contribution, chunkContext) -> {
System.out.println("reportingStep");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Job job() {
return jobs.get("job")
.start(personLoadingStep()).on("INVALID").to(reportingStep())
.from(personLoadingStep()).on("*").to(addressLoadingStep())
.from(addressLoadingStep()).on("INVALID").to(reportingStep())
.from(addressLoadingStep()).on("*").to(crossValidationStep())
.from(crossValidationStep()).on("INVALID").to(reportingStep())
.from(crossValidationStep()).on("*").end()
.from(reportingStep()).on("*").fail()
.build()
.build();
}
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(FlowJobSample.class);
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
Job job = context.getBean(Job.class);
jobLauncher.run(job, new JobParameters());
}
}
要使其中一个步骤失败,请将退出状态设置为无效,例如:
@Bean
public Step personLoadingStep() {
return steps.get("personLoadingStep")
.tasklet((contribution, chunkContext) -> {
System.out.println("personLoadingStep");
chunkContext.getStepContext().getStepExecution().setExitStatus(new ExitStatus("INVALID"));
return RepeatStatus.FINISHED;
})
.build();
}
我希望这能有所帮助。文件之间如何相互依赖?你能显示一些记录以及域模型来查看它们之间的关系吗?@MahmoudBenHassine当然,我在上面提供了一个CSV示例,其中address.CSV包含personId和batchId,必须包含在person.CSV中。如果有如此多的必填字段,如名字或地址,我必须使整个批处理失败,并回滚任何db提交。感谢澄清,我添加了一个带有示例的答案。希望有帮助。顺便说一句,如果由我决定,作业将在linux/unix操作系统上运行,我将通过
BatchId
和PersonId
在SystemCommandTasklet
中使用join
命令加入文件。这将极大地简化作业的设计。非常感谢,我实际上已经开始考虑这一点,但是有了临时文件,我更喜欢你的临时表想法。谢谢你的例子。您能否详细介绍SystemCommandTasklet,以及它将如何简化作业的设计?这是一个新项目,我可以自由做出任何最合适的设计决策。ThanksA第一步是执行命令的SystemCommandTasklet
:join person.csv address.csv
(您需要查看join的选项)。其思想是,SystemCommandTasklet
是一个准备步骤,它输出一个格式为:BatchId、personId、firstName、lastName、address
的文件。然后,您可以执行第二个面向块的步骤,读取此文件并将其映射到域对象Person
,该域对象拥有应用验证规则所需的一切(在处理器中实现)。这样,您只有两个步骤,不需要使用暂存表。