Backend Development 10 min read

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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Spring Batch in Spring Boot 2.7: From Setup to Advanced Config

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>&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-batch&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
&lt;/dependency&gt;</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&lt;User&gt; userReader(JobOperator jobOperator) throws Exception {
  JpaPagingItemReaderBuilder&lt;User&gt; builder = new JpaPagingItemReaderBuilder&lt;&gt;();
  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&lt;User&gt; userWriter() {
  // JDBC batch writer
  JdbcBatchItemWriterBuilder&lt;User&gt; builder = new JdbcBatchItemWriterBuilder&lt;&gt;();
  DataSource dataSource = dataSource();
  builder.dataSource(dataSource);
  builder.sql("insert into st (id, name, sex, mobile, age, birthday) values (?, ?, ?, ?, ?, ?)");
  builder.itemPreparedStatementSetter(new ItemPreparedStatementSetter&lt;User&gt;() {
    @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&lt;User, User&gt; userProcessor() {
  return new ItemProcessor&lt;User, User&gt;() {
    @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&lt;User&gt; userReader, ItemProcessor&lt;User, User&gt; userProcessor, ItemWriter&lt;User&gt; 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&lt;User&gt; userReader, ItemProcessor&lt;User, User&gt; userProcessor, ItemWriter&lt;User&gt; 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&lt;User&gt; userReader, ItemProcessor&lt;User, User&gt; userProcessor, ItemWriter&lt;User&gt; 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&lt;String, JobParameter&gt; parameters = new HashMap&lt;&gt;();
  // 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 BootmultithreadingAsynchronous ExecutionSpring BatchJob ConfigurationJob Restart
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.