简体   繁体   English

将Spring Batch Tasklet失败消息传递给报告步骤。

[英]Pass Spring Batch Tasklet Failure Message to Reporting Step.

I'm using a Spring Batch Tasklet with OpenCSV to read my CSV files. 我正在使用带有OpenCSV的Spring Batch Tasklet来读取我的CSV文件。 Before the question is asked, I'm aware of chunks, but there is cross validation between files in a later step, so I must continue to use the Tasklet. 在问这个问题之前,我知道很多块,但是在以后的步骤中文件之间会进行交叉验证,因此我必须继续使用Tasklet。

What I'm trying to do is report a missing file or parsing error to my reporting step. 我想做的是向我的报告步骤报告丢失的文件或解析错误。 I'm not sure what the proper approach should be to report the failure to my next step. 我不确定应该向下一步报告失败的正确方法。 I have the following code. 我有以下代码。

Initial step to read file. 读取文件的第一步。

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;
    }

}

Reporting Step 报告步骤

I'm not sure how to pass in the exception message or if there is defined method to passing in the failure message without using the Step Execution Context. 我不确定如何传递异常消息,或者是否有定义的方法来传递故障消息而不使用步骤执行上下文。

public class CsvBatchReporting implements Tasklet, StepExecutionListener {

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

    private List errorMessages;
    private List skippedInserts;

    @Override
    public void beforeStep(StepExecution stepExecution) {
        logger.info("Reporting initialized");

        ExecutionContext executionContext = stepExecution
                .getJobExecution()
                .getExecutionContext();

        System.out.println("description " + stepExecution.getStatus());


    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        logger.info("Reporting ended");
        return ExitStatus.COMPLETED;
    }

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

        //Email Error 

        return RepeatStatus.FINISHED;
    }

}

Job Config 作业配置

@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();
}

I started changing my job pattern to fail rather than defining it as invalid to get the exception to automatically call the failed step. 我开始将工作模式更改为失败,而不是将其定义为无效,以获取异常以自动调用失败的步骤。 Other steps where I'm using invalid are being defined in my afterStep using the following code. 在我的afterStep中,使用以下代码定义了我使用无效的其他步骤。

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

How do I get the CSV exception message from reader to be passed into my reporting step so that I can send it as an email? 我如何从阅读器获取CSV异常消息以传递到我的报告步骤中,以便可以将其作为电子邮件发送?

I'm not sure how to pass in the exception message or if there is defined method to passing in the failure message without using the Step Execution Context. 我不确定如何传递异常消息,或者是否有定义的方法来传递故障消息而不使用步骤执行上下文。

You can get access to the exception thrown in a previous step from the job execution. 您可以访问作业执行中上一步中引发的异常。 Here is an example: 这是一个例子:

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());
    }

}

This sample prints: 此样本打印:

hello
Looks like step1 has thrown an exception: Boom!
world

Obviously, you need to make sure step1 flows to step2 in all cases (Hence the flow definition). 显然,您需要确保在所有情况下步骤1都流至步骤2(因此定义了流程)。

Hope this helps. 希望这可以帮助。

Consider making errorMessages a bean: 考虑使errorMessages为bean:

// somewhere convenient...
@Bean
public List<String> errorMessages() {
    return new ArrayList<>();
}

and inject errorMessages into both tasklets. 并将errorMessages注入到两个Tasklet中。

In the CsvBatchReader tasklet, inspect for any exceptions thrown by execute(...) , and update errorMessages as needed: CsvBatchReader ,检查execute(...)引发的任何异常,并根据需要更新errorMessages

public class CsvBatchReader<T> implements Tasklet, StepExecutionListener {

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        List<Throwable> failures = stepExecution.getFailureExceptions();
        if (!failures.isEmpty())  {
            errorMessages.add(...);
        }

        logger.info("Reader ended - " + clazz.getSimpleName());
        return ExitStatus.COMPLETED;  // return INVALID if !failures.isEmpty() ?
    }
}

And since errorMessages was injected into CsvBatchReporting , then: 而且由于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;
}

}

Although my original question was in reference to passing exceptions from one step to the next, I would like to point out an alternative approach using exitStatus 尽管我最初的问题是关于将异常从一个步骤传递到另一个步骤,但是我想指出一种使用exitStatus的替代方法

In my reader step I can catch my csvToBean exception and create an exist status like so. 在阅读器步骤中,我可以捕获csvToBean异常并像这样创建一个存在状态。

@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;
}

I would then create conditional flow with in my job like so. 然后,我将像这样在我的工作中创建条件流。

@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();
}

Lastly in my reader tasklet step I would retrieve the existStatus like so 最后,在阅读器的tasklet步骤中,我将像这样检索存在状态

@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);
    });
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM