Java 将Spring批处理Tasklet失败消息传递到报告步骤。

Java 将Spring批处理Tasklet失败消息传递到报告步骤。,java,spring,spring-boot,spring-batch,Java,Spring,Spring Boot,Spring Batch,我正在使用带有OpenCSV的Spring批处理Tasklet来读取我的CSV文件。在问这个问题之前,我知道块,但在后面的步骤中,文件之间存在交叉验证,因此我必须继续使用Tasklet 我试图做的是向我的报告步骤报告丢失的文件或解析错误。我不确定应该采取什么适当的方法将失败报告给我的下一步。我有以下代码 if(!errorMessages.isEmpty()) { chunkContext.getStepContext().getStepExecution().setEx

我正在使用带有OpenCSV的Spring批处理Tasklet来读取我的CSV文件。在问这个问题之前,我知道块,但在后面的步骤中,文件之间存在交叉验证,因此我必须继续使用Tasklet

我试图做的是向我的报告步骤报告丢失的文件或解析错误。我不确定应该采取什么适当的方法将失败报告给我的下一步。我有以下代码

if(!errorMessages.isEmpty()) {
            chunkContext.getStepContext().getStepExecution().setExitStatus(new ExitStatus("INVALID"));
}
读取文件的初始步骤。

public class CsvBatchReader<T> implements Tasklet, StepExecutionListener {

    private final Logger logger = LoggerFactory.getLogger(CsvBatchReader.class);

    private List batch;

    private final Class<T> clazz;

    private Path path;

    public CsvBatchReader(Class<T> clazz, Path path) {
        this.clazz = clazz;
        this.path = path;
    }

    @Override
    public void beforeStep(StepExecution stepExecution) {
        logger.info("Reader initialized - " + clazz.getSimpleName());

        batch = new ArrayList();
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        logger.info("Reader ended - " + clazz.getSimpleName());
        return ExitStatus.COMPLETED;
    }

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws UnexpectedJobExecutionException {
        logger.info("Reader execute - " + clazz.getSimpleName());


        ICsvToBean csvToBean = new CsvToBean(clazz, path);

        try {
            batch = csvToBean.readCsv();
        } catch(IOException ex) {
            // error message being caught from my csvToBean class. 
            throw new UnexpectedJobExecutionException("Invalid file " + ex.getMessage());
        }

        return RepeatStatus.FINISHED;
    }

}
作业配置

@Bean
public Job primaryCareJob(@Qualifier("reportingStep") Step reportingStep, @Qualifier("crossValidationStep") Step crossValidationStep) {
    logger.info("Start PrimaryCare Job");

    return jobs.get("primaryCareJob")
            .start(readPrimaryCareStep()).on("FAILED").to(reportingStep)
            .from(readPrimaryCareStep()).on("*").to(readPrimaryCareDetailStep())

            .from(readPrimaryCareDetailStep()).on("FAILED").to(reportingStep)
            .from(readPrimaryCareDetailStep()).on("*").to(processPrimaryCareStep())

            .from(processPrimaryCareStep()).on("INVALID").to(reportingStep)
            .from(processPrimaryCareStep()).on("*").to(processPrimaryCareDetailStep())

            .from(processPrimaryCareDetailStep()).on("INVALID").to(reportingStep)
            //Other steps

            .from(reportingStep).on("*").end()
            .from(reportingStep).on("*").fail()

            .build()
        .build();
}
我开始将作业模式更改为失败,而不是将其定义为无效,以使异常自动调用失败的步骤。我使用invalid的其他步骤将在我的后续步骤中使用以下代码进行定义

if(!errorMessages.isEmpty()) {
            chunkContext.getStepContext().getStepExecution().setExitStatus(new ExitStatus("INVALID"));
}

如何从读卡器获取CSV异常消息,并将其传递到我的报告步骤中,以便我可以将其作为电子邮件发送?

考虑将
错误消息
设置为bean:

// somewhere convenient...
@Bean
public List<String> errorMessages() {
    return new ArrayList<>();
}
由于
errorMessages
被注入了
CsvBatchReporting
,那么:

public class CsvBatchReporting implements Tasklet, StepExecutionListener {

@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
    logger.info("Reporting execute");

    //Email Error 
    String body = null;
    String subject = "job finished: ";
    if (!errorMessages.isEmpty())  {
       subject += "ERROR";
       body = ... // from errorMessages
    } else {
       subject += "SUCCESS";
    }

    return RepeatStatus.FINISHED;
}

}

考虑将
errorMessages
设置为bean:

// somewhere convenient...
@Bean
public List<String> errorMessages() {
    return new ArrayList<>();
}
由于
errorMessages
被注入了
CsvBatchReporting
,那么:

public class CsvBatchReporting implements Tasklet, StepExecutionListener {

@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
    logger.info("Reporting execute");

    //Email Error 
    String body = null;
    String subject = "job finished: ";
    if (!errorMessages.isEmpty())  {
       subject += "ERROR";
       body = ... // from errorMessages
    } else {
       subject += "SUCCESS";
    }

    return RepeatStatus.FINISHED;
}

}
我不确定如何传递异常消息,或者是否有定义的方法在不使用步骤执行上下文的情况下传递失败消息

您可以从作业执行中访问在上一步中引发的异常。以下是一个例子:

import java.util.List;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
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 MyJob {

    @Autowired
    private JobBuilderFactory jobs;

    @Autowired
    private StepBuilderFactory steps;

    @Bean
    public Step step1() {
        return steps.get("step1")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("hello");
                    throw new Exception("Boom!");
                })
                .build();
    }

    @Bean
    public Step step2() {
        return steps.get("step2")
                .tasklet((contribution, chunkContext) -> {
                    JobExecution jobExecution = chunkContext.getStepContext().getStepExecution().getJobExecution();
                    StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); // TODO properly get the stepExecution of the previous step
                    List<Throwable> failureExceptions = stepExecution.getFailureExceptions();
                    if (!failureExceptions.isEmpty()) {
                        Throwable throwable = failureExceptions.get(0);
                        System.out.println("Looks like step1 has thrown an exception: " + throwable.getMessage());
                    }
                    System.out.println("world");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }

    @Bean
    public Job job() {
        return jobs.get("job")
                    .flow(step1())
                    .on("*").to(step2())
                    .build()
                .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());
    }

}
显然,您需要确保step1在所有情况下都流到step2(因此流定义)

希望这有帮助

我不确定如何传递异常消息,或者是否有定义的方法在不使用步骤执行上下文的情况下传递失败消息

您可以从作业执行中访问在上一步中引发的异常。以下是一个例子:

import java.util.List;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
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 MyJob {

    @Autowired
    private JobBuilderFactory jobs;

    @Autowired
    private StepBuilderFactory steps;

    @Bean
    public Step step1() {
        return steps.get("step1")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("hello");
                    throw new Exception("Boom!");
                })
                .build();
    }

    @Bean
    public Step step2() {
        return steps.get("step2")
                .tasklet((contribution, chunkContext) -> {
                    JobExecution jobExecution = chunkContext.getStepContext().getStepExecution().getJobExecution();
                    StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next(); // TODO properly get the stepExecution of the previous step
                    List<Throwable> failureExceptions = stepExecution.getFailureExceptions();
                    if (!failureExceptions.isEmpty()) {
                        Throwable throwable = failureExceptions.get(0);
                        System.out.println("Looks like step1 has thrown an exception: " + throwable.getMessage());
                    }
                    System.out.println("world");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }

    @Bean
    public Job job() {
        return jobs.get("job")
                    .flow(step1())
                    .on("*").to(step2())
                    .build()
                .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());
    }

}
显然,您需要确保step1在所有情况下都流到step2(因此流定义)


希望这能有所帮助。

虽然我最初的问题是关于将异常从一个步骤传递到下一个步骤,但我想指出另一种使用
exitStatus

在我的reader步骤中,我可以捕获csvToBean异常并创建一个exist状态,如下所示

@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws UnexpectedJobExecutionException {
    logger.info("Reader execute - " + clazz.getSimpleName());

    ICsvToBean csvToBean = new CsvToBean(clazz, path);

    try {
        batch = csvToBean.readCsv();
    } catch(IOException ex) {
        chunkContext.getStepContext().getStepExecution().setExitStatus(new ExitStatus("FAILED", ex.getMessage()));
    }

    return RepeatStatus.FINISHED;
}
@Bean
public Job primaryCareJob(@Qualifier("reportingStep") Step reportingStep, @Qualifier("crossValidationStep") Step crossValidationStep) {
    logger.info("Start PrimaryCare Job");

    return jobs.get("primaryCareJob")
            .start(readPrimaryCareStep()).on("FAILED").to(reportingStep)
            .from(readPrimaryCareStep()).on("*").to(readPrimaryCareDetailStep())

            .from(readPrimaryCareDetailStep()).on("FAILED").to(reportingStep)
            .from(readPrimaryCareDetailStep()).on("*").to(processPrimaryCareStep())

            .from(processPrimaryCareStep()).on("INVALID").to(reportingStep)
            .from(processPrimaryCareStep()).on("*").to(processPrimaryCareDetailStep())

            .from(processPrimaryCareDetailStep()).on("INVALID").to(reportingStep)
            //Other steps

            .from(reportingStep).on("*").end()
            .from(reportingStep).on("*").fail()

            .build()
        .build();
}
然后我会像这样在我的工作中创建条件流

@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws UnexpectedJobExecutionException {
    logger.info("Reader execute - " + clazz.getSimpleName());

    ICsvToBean csvToBean = new CsvToBean(clazz, path);

    try {
        batch = csvToBean.readCsv();
    } catch(IOException ex) {
        chunkContext.getStepContext().getStepExecution().setExitStatus(new ExitStatus("FAILED", ex.getMessage()));
    }

    return RepeatStatus.FINISHED;
}
@Bean
public Job primaryCareJob(@Qualifier("reportingStep") Step reportingStep, @Qualifier("crossValidationStep") Step crossValidationStep) {
    logger.info("Start PrimaryCare Job");

    return jobs.get("primaryCareJob")
            .start(readPrimaryCareStep()).on("FAILED").to(reportingStep)
            .from(readPrimaryCareStep()).on("*").to(readPrimaryCareDetailStep())

            .from(readPrimaryCareDetailStep()).on("FAILED").to(reportingStep)
            .from(readPrimaryCareDetailStep()).on("*").to(processPrimaryCareStep())

            .from(processPrimaryCareStep()).on("INVALID").to(reportingStep)
            .from(processPrimaryCareStep()).on("*").to(processPrimaryCareDetailStep())

            .from(processPrimaryCareDetailStep()).on("INVALID").to(reportingStep)
            //Other steps

            .from(reportingStep).on("*").end()
            .from(reportingStep).on("*").fail()

            .build()
        .build();
}
最后,在我的reader tasklet步骤中,我将像这样检索existStatus

@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
    JobExecution jobExecution = chunkContext.getStepContext().getStepExecution().getJobExecution();

    Collection<StepExecution> stepExecutions = jobExecution.getStepExecutions();

    Map<String, String> result = stepExecutions.stream()
        .filter(x -> x.getExitStatus().getExitCode().equals("FAILED"))
        .collect(Collectors.toMap(StepExecution::getStepName, x -> x.getExitStatus().getExitDescription()));

    result.forEach((k, v) -> {
        System.out.println("Step " + k + " failure " + v);
    });
}
@覆盖
public RepeatStatus execute(StepContribution贡献,ChunkContext ChunkContext)引发异常{
JobExecution JobExecution=chunkContext.getStepContext().getStepExecution().getJobExecution();
Collection stepExecutions=jobExecution.getStepExecutions();
映射结果=stepExecutions.stream()
.filter(x->x.getExitStatus().getExitCode().equals(“失败”))
.collect(Collectors.toMap(StepExecution::getStepName,x->x.getExitStatus().getExitDescription());
结果:forEach((k,v)->{
系统输出打印项次(“步骤”+k+“故障”+v);
});
}

虽然我最初的问题是关于将异常从一个步骤传递到下一个步骤,但我想指出另一种使用
exitStatus

在我的reader步骤中,我可以捕获csvToBean异常并创建一个exist状态,如下所示

@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws UnexpectedJobExecutionException {
    logger.info("Reader execute - " + clazz.getSimpleName());

    ICsvToBean csvToBean = new CsvToBean(clazz, path);

    try {
        batch = csvToBean.readCsv();
    } catch(IOException ex) {
        chunkContext.getStepContext().getStepExecution().setExitStatus(new ExitStatus("FAILED", ex.getMessage()));
    }

    return RepeatStatus.FINISHED;
}
@Bean
public Job primaryCareJob(@Qualifier("reportingStep") Step reportingStep, @Qualifier("crossValidationStep") Step crossValidationStep) {
    logger.info("Start PrimaryCare Job");

    return jobs.get("primaryCareJob")
            .start(readPrimaryCareStep()).on("FAILED").to(reportingStep)
            .from(readPrimaryCareStep()).on("*").to(readPrimaryCareDetailStep())

            .from(readPrimaryCareDetailStep()).on("FAILED").to(reportingStep)
            .from(readPrimaryCareDetailStep()).on("*").to(processPrimaryCareStep())

            .from(processPrimaryCareStep()).on("INVALID").to(reportingStep)
            .from(processPrimaryCareStep()).on("*").to(processPrimaryCareDetailStep())

            .from(processPrimaryCareDetailStep()).on("INVALID").to(reportingStep)
            //Other steps

            .from(reportingStep).on("*").end()
            .from(reportingStep).on("*").fail()

            .build()
        .build();
}
然后我会像这样在我的工作中创建条件流

@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws UnexpectedJobExecutionException {
    logger.info("Reader execute - " + clazz.getSimpleName());

    ICsvToBean csvToBean = new CsvToBean(clazz, path);

    try {
        batch = csvToBean.readCsv();
    } catch(IOException ex) {
        chunkContext.getStepContext().getStepExecution().setExitStatus(new ExitStatus("FAILED", ex.getMessage()));
    }

    return RepeatStatus.FINISHED;
}
@Bean
public Job primaryCareJob(@Qualifier("reportingStep") Step reportingStep, @Qualifier("crossValidationStep") Step crossValidationStep) {
    logger.info("Start PrimaryCare Job");

    return jobs.get("primaryCareJob")
            .start(readPrimaryCareStep()).on("FAILED").to(reportingStep)
            .from(readPrimaryCareStep()).on("*").to(readPrimaryCareDetailStep())

            .from(readPrimaryCareDetailStep()).on("FAILED").to(reportingStep)
            .from(readPrimaryCareDetailStep()).on("*").to(processPrimaryCareStep())

            .from(processPrimaryCareStep()).on("INVALID").to(reportingStep)
            .from(processPrimaryCareStep()).on("*").to(processPrimaryCareDetailStep())

            .from(processPrimaryCareDetailStep()).on("INVALID").to(reportingStep)
            //Other steps

            .from(reportingStep).on("*").end()
            .from(reportingStep).on("*").fail()

            .build()
        .build();
}
最后,在我的reader tasklet步骤中,我将像这样检索existStatus

@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
    JobExecution jobExecution = chunkContext.getStepContext().getStepExecution().getJobExecution();

    Collection<StepExecution> stepExecutions = jobExecution.getStepExecutions();

    Map<String, String> result = stepExecutions.stream()
        .filter(x -> x.getExitStatus().getExitCode().equals("FAILED"))
        .collect(Collectors.toMap(StepExecution::getStepName, x -> x.getExitStatus().getExitDescription()));

    result.forEach((k, v) -> {
        System.out.println("Step " + k + " failure " + v);
    });
}
@覆盖
public RepeatStatus execute(StepContribution贡献,ChunkContext ChunkContext)引发异常{
JobExecution JobExecution=chunkContext.getStepContext().getStepExecution().getJobExecution();
Collection stepExecutions=jobExecution.getStepExecutions();
映射结果=stepExecutions.stream()
.filter(x->x.getExitStatus().getExitCode().equals(“失败”))
.collect(Collectors.toMap(StepExecution::getStepName,x->x.getExitStatus().getExitDescription());
结果:forEach((k,v)->{
系统输出打印项次(“步骤”+k+“故障”+v);
});
}

如果运行使用相同bean的并行作业,那么创建errorMessages bean是否是线程安全的?我没有注意到stepExecution.getFailureExceptions,是否更可取的做法是使用键将故障中的错误消息放在executionContext中,然后将其回调?示例stepExecution.getJobExecution().getExecutionContext().put(键,失败);或者您仍然会注入错误消息bean吗?如果运行并行作业和步骤共享bean,那么它将不是线程安全的。使用上下文是最好的方法。查看下面的Mahmoud Ben Hassine答案,这似乎是在以后的步骤中检索异常的最好方法,而不必将异常存储在bean或上下文中。是的,同意,这会更容易!如果运行使用相同bean的并行作业,那么创建errorMessages bean是否是线程安全的?我没有注意到stepExecution.getFailureExceptions,是否更可取的做法是使用键将故障中的错误消息放在executionContext中,然后将其回调?示例stepExecution.getJobExecution().getExecutionContext().put(键,失败);或者您仍然会注入错误消息bean吗?如果运行并行作业和步骤共享bean,那么它将不是线程安全的。使用上下文是最好的方法。查看下面的Mahmoud Ben Hassine答案,这似乎是在以后的步骤中检索异常的最好方法,而不必将异常存储在bean或上下文中。是的,同意,这会更容易!我必须仔细阅读流定义,因为我不确定使用.start(readPrimaryCareStep()).on(“失败”).to(reportingStep)与flow()之间的区别是什么。。。只要我的上一步是抛出异常的步骤,它会是sa吗