Master Spring Batch in Spring Boot 2.7: From Setup to Advanced Config
This tutorial walks through configuring Spring Batch on Spring Boot 2.7.16 to read data from one database, process it, and write to another, covering dependency setup, job and step beans, asynchronous execution, multithreading, job restart, and repeated launches with code examples.
Environment: Spring Boot 2.7.16.
The article demonstrates how to use Spring Batch to read data from one database, process it, and write it to another.
1. Environment Preparation
1.1 Add dependencies
<code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency></code>2.2 Configure Job
Job launcher bean:
<code>@Bean
JobLauncher userJobLauncher(JobRepository userJobRepository) {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(userJobRepository);
return jobLauncher;
}</code>Job repository bean:
<code>@Bean
JobRepository userJobRepository(DataSource dataSource, PlatformTransactionManager transactionManager) {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDatabaseType("mysql");
factory.setTransactionManager(transactionManager);
factory.setDataSource(dataSource);
try {
factory.afterPropertiesSet();
return factory.getObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}</code>ItemReader configuration:
<code>@Bean
ItemReader<User> userReader(JobOperator jobOperator) throws Exception {
JpaPagingItemReaderBuilder<User> builder = new JpaPagingItemReaderBuilder<>();
builder.entityManagerFactory(entityManagerFactory);
// each page size
builder.pageSize(10);
builder.queryString("select u from User u where u.uid <= 50");
builder.saveState(true);
builder.name("userReader");
return builder.build();
}</code>DataSource for writing:
<code>public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/testjpa?serverTimezone=GMT%2B8&useSSL=false");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("xxxooo");
return dataSource;
}</code>ItemWriter configuration:
<code>@Bean
ItemWriter<User> userWriter() {
// JDBC batch writer
JdbcBatchItemWriterBuilder<User> builder = new JdbcBatchItemWriterBuilder<>();
DataSource dataSource = dataSource();
builder.dataSource(dataSource);
builder.sql("insert into st (id, name, sex, mobile, age, birthday) values (?, ?, ?, ?, ?, ?)");
builder.itemPreparedStatementSetter(new ItemPreparedStatementSetter<User>() {
@Override
public void setValues(User item, PreparedStatement ps) throws SQLException {
ps.setInt(1, item.getUid());
ps.setString(2, item.getName());
ps.setString(3, item.getSex());
ps.setString(4, item.getMobile());
ps.setInt(5, item.getAge());
ps.setObject(6, item.getBirthday());
}
});
return builder.build();
}</code>ItemProcessor configuration:
<code>@Bean
ItemProcessor<User, User> userProcessor() {
return new ItemProcessor<User, User>() {
@Override
public User process(User item) throws Exception {
System.out.printf("%s - 开始处理数据:%s%n", Thread.currentThread().getName(), item.toString());
// simulate time‑consuming work
TimeUnit.SECONDS.sleep(1);
return item;
}
};
}</code>Step linking reader, processor, writer:
<code>@Bean
Step userStep1(ItemReader<User> userReader, ItemProcessor<User, User> userProcessor, ItemWriter<User> userWriter) {
return steps.get("userStep1")
.<User, User>chunk(5)
.reader(userReader)
.processor(userProcessor)
.writer(userWriter)
.build();
}</code>Job definition (container for steps):
<code>@Bean
Job userJob(Step userStep1, Step userStep2) {
return jobs.get("userJob").start(userStep1).build();
}</code>2. Advanced Configuration
2.1 Start Job via Controller
<code>@RequestMapping("/userJob")
public class UserJobController {
@Resource
private JobLauncher userJobLauncher;
@GetMapping("/start")
public Object start() throws Exception {
JobParameters jobParameters = new JobParameters();
this.userJobLauncher.run(userJob, jobParameters);
return "started";
}
}</code>Calling run blocks until the job finishes (FINISHED or FAILED). To return immediately, configure asynchronous launch.
2.2 Asynchronous Job Launch
<code>@Bean
TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setThreadNamePrefix("spring_batch_launcher");
taskExecutor.setCorePoolSize(10);
taskExecutor.setMaxPoolSize(10);
taskExecutor.initialize();
return taskExecutor;
}
@Bean
JobLauncher userJobLauncher(JobRepository userJobRepository) {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(userJobRepository);
jobLauncher.setTaskExecutor(taskExecutor());
return jobLauncher;
}</code>Now the job starts asynchronously and returns a JobExecution immediately.
2.3 Restarting a Job
If a job is interrupted, the batch_job_execution and batch_step_execution tables show status STARTED with END_TIME null. Change the status to STOPPED and set a dummy END_TIME value, then the controller can launch the job again.
2.4 Multithreaded Step Execution
<code>@Bean
Step userStep1(ItemReader<User> userReader, ItemProcessor<User, User> userProcessor, ItemWriter<User> userWriter) {
return steps.get("userStep1")
.<User, User>chunk(5)
.reader(userReader)
.processor(userProcessor)
.writer(userWriter)
// configure thread pool
.taskExecutor(taskExecutor())
.build();
}</code>Note: Any pooled resources used in a step (e.g., DataSource) must have a size at least equal to the number of concurrent threads.
With the thread pool, four threads run by default. Adjust concurrency with .throttleLimit(10) to match your DB connection pool size.
<code>@Bean
Step userStep1(ItemReader<User> userReader, ItemProcessor<User, User> userProcessor, ItemWriter<User> userWriter) {
return steps.get("userStep1")
// ... other config ...
.throttleLimit(10)
.build();
}</code>2.5 Repeating Job Execution
Provide different JobParameters for each launch, for example using a path variable:
<code>@GetMapping("/start/{page}")
public Object start(@PathVariable("page") Long page) throws Exception {
Map<String, JobParameter> parameters = new HashMap<>();
// each call uses a distinct parameter value
parameters.put("page", new JobParameter(page));
JobParameters jobParameters = new JobParameters(parameters);
this.userJobLauncher.run(userJob, jobParameters);
return "started";
}</code>The article concludes with the full source code and screenshots illustrating asynchronous launch, multithreaded execution, and job status tables.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.