spring-bootspring-batch

Spring batch wiht separated datasource and job not running at application startup


I'm developing a Spring Boot 3.3.5 application using Spring Batch. I need to separate the Spring Batch database from the main application database. Additionally, I need to ensure that jobs do not run at application startup, as I want to execute them explicitly and programmatically. I tried using the property spring.batch.job.enabled=false, but the jobs still run at runtime. Perhaps something in my configuration is causing this issue.

Here is the configuration for multiple data sources:

@Configuration
@Profile({"!test"})
public class DataSourceFactory {

  @Bean
  @BatchDataSource
  public DataSource h2Datasource() {
    return new EmbeddedDatabaseBuilder()
          .setType(EmbeddedDatabaseType.H2)
          .addScript("classpath:org/springframework/batch/core/schema-drop-h2.sql")
          .addScript("/org/springframework/batch/core/schema-h2.sql")
          .build();
  }

  @Bean
  @BatchTransactionManager
  public PlatformTransactionManager batchTransactionManager() {
    return new JdbcTransactionManager(h2Datasource());
  }

  @Bean
  @Primary
  @ConfigurationProperties("spring.datasource")
  public DataSourceProperties oracleDatasourceProperties() {
    return new DataSourceProperties();
  }

  @Bean
  @Primary
  @ConfigurationProperties("spring.datasource.hikari")
  public DataSource oracleDatasource() {
    return oracleDatasourceProperties().initializeDataSourceBuilder().build();
  }

  @Bean
  @Primary
  public PlatformTransactionManager transactionManager() {
    return new JdbcTransactionManager(oracleDatasource());
  }

}

This the job:

@Configuration
@RequiredArgsConstructor
public class NotificationJobConfig {
    private final JobRepository jobRepository;
    ...
    
    @Bean
    public Job notificationJob() {
    return new JobBuilder(NOTIFICATION_JOB, jobRepository)
          ...
          .build();
    }
}

The is the service running the job:

@Service
@Slf4j
public class JobServiceImpl implements JobService {

  @Autowired
  private JobLauncher jobLauncher;

  @Autowired
  @Qualifier(NotificationJobConfig.NOTIFICATION_JOB)
  private Job notificationJob;

  @Override
  public void runNotification() {
    try {
      JobParameters jobParameters = new JobParametersBuilder().addLocalDateTime("timestamp", LocalDateTime.now()).toJobParameters();
      jobLauncher.run(notificationJob, jobParameters);
    } catch (Exception e) {
      log.error("Error starting notification job: {}", e.getMessage(), e);
    }
  }
}

I made a second solution adding a class configuration with @EnableBatchProcessing:

@Configuration
@EnableBatchProcessing
public class BatchConfig {
    
}

but in this case spring complains with this message:

Field jobLauncher in it.coop.coopitalia.rs.service.JobServiceImpl required a bean named 'dataSource' that could not be found.

At the end I tried to use DefaultBatchConfiguration instead of @EnableBatchProcessing like this:

@Configuration
public class BatchConfig extends DefaultBatchConfiguration {

  @Autowired
  @BatchDataSource
  private DataSource batchDatasource;

  @Autowired
  @BatchTransactionManager
  private PlatformTransactionManager batchTransactionManager;

  @Override
  protected DataSource getDataSource() {
    return batchDatasource;
  }

  @Override
  protected PlatformTransactionManager getTransactionManager() {
    return this.batchTransactionManager;
  }

  @Bean
  public JobLauncher jobLauncher() {
    TaskExecutorJobLauncher jobLauncher = new TaskExecutorJobLauncher();
    try {
      jobLauncher.setJobRepository(jobRepository());
      jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
      jobLauncher.afterPropertiesSet();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return jobLauncher;
  }

}

But again the problem is that job automatically run at application start-up.

So, there is something I'm doing wrong... any ideas?


Solution

  • It seems like the spring.batch.job.enabled=false property is not taken into account in your case. I gave that a quick test and things seem to work as expected:

    import org.springframework.batch.core.Job;
    import org.springframework.batch.core.Step;
    import org.springframework.batch.core.job.builder.JobBuilder;
    import org.springframework.batch.core.repository.JobRepository;
    import org.springframework.batch.core.step.builder.StepBuilder;
    import org.springframework.batch.repeat.RepeatStatus;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.batch.BatchDataSource;
    import org.springframework.boot.autoconfigure.batch.BatchTransactionManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Primary;
    import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
    import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
    import org.springframework.jdbc.support.JdbcTransactionManager;
    
    import javax.sql.DataSource;
    
    @SpringBootApplication
    public class SO79492302Application {
    
        public static void main(String[] args) {
            SpringApplication.run(SO79492302Application.class, args);
        }
    
        @Bean
        public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager) {
            Step myStep = new StepBuilder("myStep", jobRepository)
                    .tasklet((contribution, chunkContext)
                            -> { System.out.println("Hello world!");
                        return RepeatStatus.FINISHED;
                    }, transactionManager)
                    .build();
            return new JobBuilder("myJob", jobRepository)
                    .start(myStep)
                    .build();
        }
        
        // batch infrastructure
    
        @Bean
        @BatchDataSource
        public DataSource batchDataSource() {
            return new EmbeddedDatabaseBuilder()
                    .setType(EmbeddedDatabaseType.H2)
                    .addScript("/org/springframework/batch/core/schema-h2.sql")
                    .build();
        }
    
        @Bean
        @BatchTransactionManager
        public JdbcTransactionManager batchTransactionManager(@Qualifier("batchDataSource") DataSource batchDataSource) {
            return new JdbcTransactionManager(batchDataSource);
        }
        
        // business infrastructure
    
        @Bean
        @Primary
        public DataSource datasource() {
            return new EmbeddedDatabaseBuilder()
                    .setType(EmbeddedDatabaseType.H2)
                    .build();
        }
    
    }
    

    The pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>3.4.3</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>spring-batch-resourceles-job-repository</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>spring-batch-resourceles-job-repository</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>17</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-batch</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.h2database</groupId>
                <artifactId>h2</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    The application.properties file:

    spring.batch.job.enabled=false
    

    prints:

     :: Spring Boot ::                (v3.4.3)
    
    2025-03-07T08:26:54.089-05:00  INFO 21002 --- [           main] c.e.s.SO79492302Application              : Starting SO79492302Application using Java 21.0.2 with PID 21002
    2025-03-07T08:26:54.090-05:00  INFO 21002 --- [           main] c.e.s.SO79492302Application              : No active profile set, falling back to 1 default profile: "default"
    2025-03-07T08:26:54.333-05:00  INFO 21002 --- [           main] o.s.j.d.e.EmbeddedDatabaseFactory        : Starting embedded database: url='jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'
    2025-03-07T08:26:54.430-05:00  INFO 21002 --- [           main] o.s.j.d.e.EmbeddedDatabaseFactory        : Starting embedded database: url='jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'
    2025-03-07T08:26:54.543-05:00  INFO 21002 --- [           main] c.e.s.SO79492302Application              : Started SO79492302Application in 0.628 seconds (process running for 0.797)
    2025-03-07T08:26:54.546-05:00  INFO 21002 --- [ionShutdownHook] o.s.j.d.e.EmbeddedDatabaseFactory        : Shutting down embedded database: url='jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false'
    2025-03-07T08:26:54.547-05:00  INFO 21002 --- [ionShutdownHook] o.s.j.d.e.EmbeddedDatabaseFactory        : Shutting down embedded database: url='jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false'
    

    The job did not run on startup as expected.