javaspring-bootspring-batch

Why does it run twice when using @EnableScheduling and @Scheduled?


When I use Spring Batch with @EnableScheduling and @Scheduled(fixedRate = 60000, zone = “Asia/Shanghai”), after starting the project I get [ main] and [ scheduling-1] executing simultaneously. But at the same time the [ main] execution does not access the following code but simply executes the exportStep, which results in empty JobParameters.

@Scheduled(fixedRate = 60000, zone = "Asia/Shanghai")
public void runBatchJob()

I'd like to be able to leave [ main] unexecuted at startup, or also call the corresponding method to get JobParameters when it executes, I'd prefer to be able to achieve the first effect because [ scheduling-1] can replace him and not have to perform the same operation twice at startup.

----- Supplement Content -----

Clarify the role of the [main] thread in your Spring Batch configuration, as it seems to be running tasks unintentionally at startup.

For my feature, I don't want the [main] thread to do anything, the things to be done should be left to the [scheduling-1] thread, but the [main] thread performs what the [scheduling-1] thread is supposed to do all over again on startup (and that's not done completely)

whether you want to prevent the main thread from triggering the scheduled task at startup or ensure that the scheduled task executes only once.

I want to prevent the main thread from triggering the scheduled task at startup

A clear description of the job flow and any relevant Spring Batch configuration would help refine the solution.

Here's the code I think is important

@Configuration
@EnableBatchProcessing
public class BatchConfig {
    private final DataSource dataSource;
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    public BatchConfig(DataSource dataSource, JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory) {
        this.dataSource = dataSource;
        this.jobBuilderFactory = jobBuilderFactory;
        this.stepBuilderFactory = stepBuilderFactory;
    }

    @Bean
    public Job transactionExportJob(Step exportStep, BatchJobCompletionListener listener) {
        return jobBuilderFactory.get("transactionExportJob")
                .incrementer(new RunIdIncrementer())
                .start(exportStep)
                .listener(listener)
                .build();
    }

    @Bean
    public Step exportStep(ItemReader<Transaction> reader, ExcelWriter excelWriter, PlatformTransactionManager transactionManager) {
        return stepBuilderFactory.get("exportStep")
                .<Transaction, Transaction>chunk(5000)
                .reader(reader)
                .processor(new TransactionProcessor()) // handle data
                .writer(excelWriter)
                .faultTolerant() // enabling fault tolerance
                .retryLimit(3) 
                .retry(Exception.class)
                .skip(Exception.class)
                .skipLimit(100)
                .listener(new CustomSkipListener()) // listing skip
                .transactionManager(transactionManager)
                .build();
    }

    @Bean
    public JdbcPagingItemReader<Transaction> reader(PagingQueryProvider transactionQueryProvider) {
        JdbcPagingItemReader<Transaction> reader = new JdbcPagingItemReader<>();
        reader.setDataSource(dataSource);
        reader.setFetchSize(5000);
        reader.setRowMapper(new TransactionRowMapper());
        reader.setQueryProvider(transactionQueryProvider);

        return reader;
    }
}
@Component
public class ExcelWriter implements ItemWriter<Transaction> {
    private final String filePath;
    private final Workbook workbook;
    private final Sheet sheet;
    private int rowCount = 1;

    public ExcelWriter() {
        this.filePath = "transactions_" + UUID.randomUUID().toString() + ".xlsx";
        this.workbook = new XSSFWorkbook();
        this.sheet = workbook.createSheet("Transactions");
        createHeaderRow();
    }

    @Override
    public void write(List<? extends Transaction> items) throws Exception {
        System.out.println("filePath: " + filePath);
        for (Transaction transaction : items) {
            Row row = sheet.createRow(rowCount++);
            row.createCell(0).setCellValue(transaction.getUniqueTxId());
            row.createCell(1).setCellValue(transaction.getCreatedTime().toString());
        }
        try (FileOutputStream out = new FileOutputStream(filePath)) {
            workbook.write(out);
        } catch (IOException e) {
            System.err.println("Error closing Excel file: " + e.getMessage());
            throw new IOException("Failed to close workbook", e);
        }
    }

    private void createHeaderRow() {
        Row header = sheet.createRow(0);
        header.createCell(0).setCellValue("Unique Tx Id");
        header.createCell(1).setCellValue("Created Time");
    }

    public void closeWorkbook() throws IOException {
        workbook.close();
    }
}
@Component
@EnableScheduling
public class BatchScheduler {
    private final JobLauncher jobLauncher;
    private final Job transactionExportJob;
    private final JobParametersHolder jobParametersHolder;


    public BatchScheduler(JobLauncher jobLauncher, Job transactionExportJob, JobParametersHolder jobParametersHolder) {
        this.jobLauncher = jobLauncher;
        this.transactionExportJob = transactionExportJob;
        this.jobParametersHolder = jobParametersHolder;
    }

    @Scheduled(fixedRate = 60000, initialDelay = 60000, zone = "Asia/Shanghai")
    public void runBatchJob() {
        try {
            ExcelWriter excelWriter = new ExcelWriter();
            LocalDateTime startTime = LocalDateTime.now().minusDays(1).withHour(22);
            LocalDateTime endTime = LocalDateTime.now().withHour(22);

            JobParameters jobParameters = new JobParametersBuilder()
                    .addDate("startTime", Date.from(startTime.atZone(ZoneId.of("Asia/Shanghai")).toInstant()))
                    .addDate("endTime", Date.from(endTime.atZone(ZoneId.of("Asia/Shanghai")).toInstant()))
                    .toJobParameters();

            System.out.println("jobParameters: " + jobParameters);
            jobParametersHolder.setJobParameters(jobParameters);

            System.out.println("Batch Job started");
            JobExecution execution = jobLauncher.run(transactionExportJob, jobParameters);
            System.out.println("Batch job completed with status: {}" + execution.getStatus());
            excelWriter.closeWorkbook();
        } catch (Exception e) {
            System.out.println("Batch job execution failed: {}" + e.getMessage() + e);
        }
    }
}

Solution

  • Thanks a lot @rahulP, the default parameter true for spring.batch.job.enabled caused the Batch task to start executing as soon as the application was started, and I wanted the opposite, so I changed the default parameter to false and it worked.