Improve Step/Job builder APIs to guide users to set mandatory properties

Before this commit, the user had to manually set the job repository and transaction
manager on `JobBuilder` and `StepBuilder` instances with a chained call to
`.repository(jobRepository)` and `.transactionManager(transactionManager)`.
This is error prone and can lead to runtime errors if these properties are not set.

This commit introduces new APIs to guide the user to set these properties at
builder creation time.

Resolves #4192
This commit is contained in:
Mahmoud Ben Hassine
2022-09-17 22:31:12 +02:00
parent d11b5b2c3a
commit f39f07075a
63 changed files with 520 additions and 302 deletions

View File

@@ -45,14 +45,13 @@ public class RetrySampleConfiguration {
@Bean
public Job retrySample(JobRepository jobRepository) {
return new JobBuilder("retrySample").repository(jobRepository).start(step(jobRepository)).build();
return new JobBuilder("retrySample", jobRepository).start(step(jobRepository)).build();
}
@Bean
protected Step step(JobRepository jobRepository) {
return new StepBuilder("step").repository(jobRepository).<Trade, Object>chunk(1)
.transactionManager(this.transactionManager).reader(reader()).writer(writer()).faultTolerant()
.retry(Exception.class).retryLimit(3).build();
return new StepBuilder("step", jobRepository).<Trade, Object>chunk(1, this.transactionManager).reader(reader())
.writer(writer()).faultTolerant().retry(Exception.class).retryLimit(3).build();
}
@Bean

View File

@@ -25,6 +25,7 @@ import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
public class Job1Configuration {
@@ -36,24 +37,24 @@ public class Job1Configuration {
}
@Bean
public Job job1(JobRepository jobRepository) {
return new JobBuilder("job1").repository(jobRepository).start(step1(jobRepository)).next(step2(jobRepository))
.build();
public Job job1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new JobBuilder("job1", jobRepository).start(step1(jobRepository, transactionManager))
.next(step2(jobRepository, transactionManager)).build();
}
@Bean
public Step step1(JobRepository jobRepository) {
return new StepBuilder("step1").repository(jobRepository).tasklet((contribution, chunkContext) -> {
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("step1", jobRepository).tasklet((contribution, chunkContext) -> {
System.out.println("hello");
// simulate processing time
Thread.sleep(random.nextInt(3000));
return RepeatStatus.FINISHED;
}).build();
}, transactionManager).build();
}
@Bean
public Step step2(JobRepository jobRepository) {
return new StepBuilder("step2").repository(jobRepository).tasklet((contribution, chunkContext) -> {
public Step step2(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("step2", jobRepository).tasklet((contribution, chunkContext) -> {
System.out.println("world");
// simulate step failure
int nextInt = random.nextInt(3000);
@@ -62,7 +63,7 @@ public class Job1Configuration {
throw new Exception("Boom!");
}
return RepeatStatus.FINISHED;
}).build();
}, transactionManager).build();
}
}

View File

@@ -29,6 +29,7 @@ import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
public class Job2Configuration {
@@ -40,14 +41,14 @@ public class Job2Configuration {
}
@Bean
public Job job2(JobRepository jobRepository) {
return new JobBuilder("job2").repository(jobRepository).start(step(jobRepository)).build();
public Job job2(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new JobBuilder("job2", jobRepository).start(step(jobRepository, transactionManager)).build();
}
@Bean
public Step step(JobRepository jobRepository) {
return new StepBuilder("step1").repository(jobRepository).<Integer, Integer>chunk(3).reader(itemReader())
.writer(itemWriter()).build();
public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("step1", jobRepository).<Integer, Integer>chunk(3, transactionManager)
.reader(itemReader()).writer(itemWriter()).build();
}
@Bean

View File

@@ -32,6 +32,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.transaction.PlatformTransactionManager;
import static org.springframework.data.mongodb.core.query.Criteria.where;
@@ -60,15 +61,15 @@ public class DeletionJobConfiguration {
}
@Bean
public Step deletionStep(JobRepository jobRepository, MongoItemReader<Person> mongoPersonReader,
MongoItemWriter<Person> mongoPersonRemover) {
return new StepBuilder("step").repository(jobRepository).<Person, Person>chunk(2).reader(mongoPersonReader)
.writer(mongoPersonRemover).build();
public Step deletionStep(JobRepository jobRepository, PlatformTransactionManager transactionManager,
MongoItemReader<Person> mongoPersonReader, MongoItemWriter<Person> mongoPersonRemover) {
return new StepBuilder("step", jobRepository).<Person, Person>chunk(2, transactionManager)
.reader(mongoPersonReader).writer(mongoPersonRemover).build();
}
@Bean
public Job deletionJob(JobRepository jobRepository, Step deletionStep) {
return new JobBuilder("deletionJob").repository(jobRepository).start(deletionStep).build();
return new JobBuilder("deletionJob", jobRepository).start(deletionStep).build();
}
}

View File

@@ -31,6 +31,7 @@ import org.springframework.batch.item.data.builder.MongoItemWriterBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.transaction.PlatformTransactionManager;
/**
* This job will copy documents from collection "person_in" into collection "person_out"
@@ -55,15 +56,15 @@ public class InsertionJobConfiguration {
}
@Bean
public Step step(JobRepository jobRepository, MongoItemReader<Person> mongoItemReader,
MongoItemWriter<Person> mongoItemWriter) {
return new StepBuilder("step").repository(jobRepository).<Person, Person>chunk(2).reader(mongoItemReader)
.writer(mongoItemWriter).build();
public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager,
MongoItemReader<Person> mongoItemReader, MongoItemWriter<Person> mongoItemWriter) {
return new StepBuilder("step", jobRepository).<Person, Person>chunk(2, transactionManager)
.reader(mongoItemReader).writer(mongoItemWriter).build();
}
@Bean
public Job insertionJob(JobRepository jobRepository, Step step) {
return new JobBuilder("insertionJob").repository(jobRepository).start(step).build();
return new JobBuilder("insertionJob", jobRepository).start(step).build();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,10 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.MongoTransactionManager;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
@Configuration
@PropertySource("classpath:/mongodb-sample.properties")
@@ -38,10 +41,24 @@ public class MongoDBConfiguration {
private String mongodbDatabase;
@Bean
public MongoTemplate mongoTemplate() {
public MongoClient mongoClient() {
String connectionString = "mongodb://" + this.mongodbHost + ":" + this.mongodbPort + "/" + this.mongodbDatabase;
MongoClient mongoClient = MongoClients.create(connectionString);
return MongoClients.create(connectionString);
}
@Bean
public MongoTemplate mongoTemplate(MongoClient mongoClient) {
return new MongoTemplate(mongoClient, "test");
}
@Bean
public MongoDatabaseFactory mongoDatabaseFactory(MongoClient mongoClient) {
return new SimpleMongoClientDatabaseFactory(mongoClient, "test");
}
@Bean
public MongoTransactionManager transactionManager(MongoDatabaseFactory mongoDatabaseFactory) {
return new MongoTransactionManager(mongoDatabaseFactory);
}
}

View File

@@ -112,7 +112,7 @@ public class ManagerConfiguration {
@Bean
public Job remoteChunkingJob(JobRepository jobRepository) {
return new JobBuilder("remoteChunkingJob").repository(jobRepository).start(managerStep()).build();
return new JobBuilder("remoteChunkingJob", jobRepository).start(managerStep()).build();
}
}

View File

@@ -94,7 +94,7 @@ public class ManagerConfiguration {
@Bean
public Job remotePartitioningJob(JobRepository jobRepository) {
return new JobBuilder("remotePartitioningJob").repository(jobRepository).start(managerStep()).build();
return new JobBuilder("remotePartitioningJob", jobRepository).start(managerStep()).build();
}
}

View File

@@ -87,7 +87,7 @@ public class WorkerConfiguration {
@Bean
public Step workerStep(PlatformTransactionManager transactionManager) {
return this.workerStepBuilderFactory.get("workerStep").inputChannel(requests()).outputChannel(replies())
.tasklet(tasklet(null)).transactionManager(transactionManager).build();
.tasklet(tasklet(null), transactionManager).build();
}
@Bean

View File

@@ -80,7 +80,7 @@ public class ManagerConfiguration {
@Bean
public Job remotePartitioningJob(JobRepository jobRepository) {
return new JobBuilder("remotePartitioningJob").repository(jobRepository).start(managerStep()).build();
return new JobBuilder("remotePartitioningJob", jobRepository).start(managerStep()).build();
}
}

View File

@@ -72,8 +72,8 @@ public class WorkerConfiguration {
*/
@Bean
public Step workerStep(PlatformTransactionManager transactionManager) {
return this.workerStepBuilderFactory.get("workerStep").inputChannel(requests()).tasklet(tasklet(null))
.transactionManager(transactionManager).build();
return this.workerStepBuilderFactory.get("workerStep").inputChannel(requests())
.tasklet(tasklet(null), transactionManager).build();
}
@Bean

View File

@@ -83,14 +83,14 @@ public class SkippableExceptionDuringProcessSample {
@Bean
public Step step(JobRepository jobRepository) {
return new StepBuilder("step").repository(jobRepository).<Integer, Integer>chunk(3)
.transactionManager(this.transactionManager).reader(itemReader()).processor(itemProcessor())
.writer(itemWriter()).faultTolerant().skip(IllegalArgumentException.class).skipLimit(3).build();
return new StepBuilder("step", jobRepository).<Integer, Integer>chunk(3, this.transactionManager)
.reader(itemReader()).processor(itemProcessor()).writer(itemWriter()).faultTolerant()
.skip(IllegalArgumentException.class).skipLimit(3).build();
}
@Bean
public Job job(JobRepository jobRepository) {
return new JobBuilder("job").repository(jobRepository).start(step(jobRepository)).build();
return new JobBuilder("job", jobRepository).start(step(jobRepository)).build();
}
}

View File

@@ -83,14 +83,14 @@ public class SkippableExceptionDuringReadSample {
@Bean
public Step step(JobRepository jobRepository) {
return new StepBuilder("step").repository(jobRepository).<Integer, Integer>chunk(3)
.transactionManager(this.transactionManager).reader(itemReader()).processor(itemProcessor())
.writer(itemWriter()).faultTolerant().skip(IllegalArgumentException.class).skipLimit(3).build();
return new StepBuilder("step", jobRepository).<Integer, Integer>chunk(3, this.transactionManager)
.reader(itemReader()).processor(itemProcessor()).writer(itemWriter()).faultTolerant()
.skip(IllegalArgumentException.class).skipLimit(3).build();
}
@Bean
public Job job(JobRepository jobRepository) {
return new JobBuilder("job").repository(jobRepository).start(step(jobRepository)).build();
return new JobBuilder("job", jobRepository).start(step(jobRepository)).build();
}
}

View File

@@ -83,14 +83,14 @@ public class SkippableExceptionDuringWriteSample {
@Bean
public Step step(JobRepository jobRepository) {
return new StepBuilder("step").repository(jobRepository).<Integer, Integer>chunk(3)
.transactionManager(this.transactionManager).reader(itemReader()).processor(itemProcessor())
.writer(itemWriter()).faultTolerant().skip(IllegalArgumentException.class).skipLimit(3).build();
return new StepBuilder("step", jobRepository).<Integer, Integer>chunk(3, this.transactionManager)
.reader(itemReader()).processor(itemProcessor()).writer(itemWriter()).faultTolerant()
.skip(IllegalArgumentException.class).skipLimit(3).build();
}
@Bean
public Job job(JobRepository jobRepository) {
return new JobBuilder("job").repository(jobRepository).start(step(jobRepository)).build();
return new JobBuilder("job", jobRepository).start(step(jobRepository)).build();
}
}

View File

@@ -66,14 +66,13 @@ public class ValidationSampleConfiguration {
@Bean
public Step step(JobRepository jobRepository) throws Exception {
return new StepBuilder("step").repository(jobRepository).<Person, Person>chunk(1)
.transactionManager(transactionManager(dataSource())).reader(itemReader()).processor(itemValidator())
.writer(itemWriter()).build();
return new StepBuilder("step", jobRepository).<Person, Person>chunk(1, transactionManager(dataSource()))
.reader(itemReader()).processor(itemValidator()).writer(itemWriter()).build();
}
@Bean
public Job job(JobRepository jobRepository) throws Exception {
return new JobBuilder("job").repository(jobRepository).start(step(jobRepository)).build();
return new JobBuilder("job", jobRepository).start(step(jobRepository)).build();
}
@Bean

View File

@@ -90,14 +90,13 @@ class JsonSupportIntegrationTests {
@Bean
public Step step(JobRepository jobRepository) {
return new StepBuilder("step").repository(jobRepository).<Trade, Trade>chunk(2)
.transactionManager(transactionManager(dataSource())).reader(itemReader()).writer(itemWriter())
.build();
return new StepBuilder("step", jobRepository).<Trade, Trade>chunk(2, transactionManager(dataSource()))
.reader(itemReader()).writer(itemWriter()).build();
}
@Bean
public Job job(JobRepository jobRepository) {
return new JobBuilder("job").repository(jobRepository).start(step(jobRepository)).build();
return new JobBuilder("job", jobRepository).start(step(jobRepository)).build();
}
@Bean