Home > Software engineering >  Error creating bean with name 'batchDataSource': Requested bean is currently in creation:
Error creating bean with name 'batchDataSource': Requested bean is currently in creation:

Time:09-30

I have a batch configuration. I saw the batch process is default using InMemoryMap. Instead I need to use the MySQL to send all the execution details by Batch. But when I use the following code I am getting the following error,

Error creating bean with name 'batchDataSource': Requested bean is currently in creation: Is there an unresolvable circular reference?

@Configuration
@EnableBatchProcessing
public class BatchProcess extends DefaultBatchConfigurer {

    private @Autowired Environment env;

    @Bean
    @StepScope
    public ItemReader reader() {
        ...
    }

    @Bean
    @StepScope
    public ItemProcessor processor() {
        ...
    }

    @Bean
    @StepScope
    public ItemWriter writer() {
        ...
    }

    @Bean
    @Primary
    public DataSource batchDataSource() {
        HikariDataSource hikari = new HikariDataSource();
        hikari.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
        hikari.setJdbcUrl(env.getProperty("spring.datasource.url"));
        hikari.setUsername(env.getProperty("spring.datasource.username"));
        hikari.setPassword(env.getProperty("spring.datasource.password"));
        return hikari;
    }

    public JobRepository getJobRepository() {
        JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
        factory.setDataSource(batchDataSource());
        factory.setTransactionManager(manager());
        factory.afterPropertiesSet();
        return factory.getObject();
    }

    public PlatformTransactionManager manager() {
        return new ResourcelessTransactionManager();
    }

    @Bean
    public Step step() {
        return stepBuilderFactory.get("step")
                .chunk(1000)
                .reader(reader())
                .processor(processor())
                .writer(writer())
                .build();
    }

    @Bean
    public Job job() {
        return jobBuilderFactory.get("job")
                .flow(step())
                .end()
                .build();
    }

    @Bean
    public JobLauncher getJobLauncher() {
        SimpleJobLauncher launcher = new SimpleJobLauncher();
        launcher.setJobRepository(createJobRepository());
        return launcher;
    }
}

In property file I am using,

spring.batch.job.enabled=false
spring.batch.initialize-schema=always

So what I missed? I am using JPA. And even why it is not using available JPA datasource? How can I force the Spring batch to use default MySQL instead InMemoryMap?

CodePudding user response:

First of all, explicitly define the mysql-connector dependency in the pom.xml and remove anything related to in-memory map from the project.

  <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
   </dependency>

If you want to define your own configuration with beans manually, then you can't use the AutoConfiguration classes because they create the required beans for you on startup automatically and that might cause an issue if you are defining own custom DB configuration classes. Therefore, you have to exclude DataSourceAutoConfiguration, HibernateJpaAutoConfiguration and DataSourceTransactionManagerAutoConfiguration to resolve the issue.

Just update the @SpringBootApplication class :

@SpringBootApplication(
      exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
        DataSourceTransactionManagerAutoConfiguration.class
      }
    )
    public class App {
    
      public static void main(String[] args) {
        SpringApplication.run(App.class, args);
      }
    }

CodePudding user response:

The error message you are receiving may not be the clearest, but it should point you in the right direction. You appear to have a circular dependency within your code.

This happens when you have two (or more) beans that mutually depend upon one another, preventing the creation of one without the existence of the other (and vice versa) - the proverbial chicken and egg problem. You can generally avoid this with setter injection and some kind of post-construction initialization.

I think you have created this situation by extending DefaultBatchConfigurer and then defining the @Bean annotated method getJobLauncher() which directly calls DefaultBatchConfigurer's createJobRepository() method without ensuring that the DataSource is first set within DefaultBatchConfigurer.

This is entirely unnecessary, because DefaultBatchConfigurer already creates JobRepository, JobExplorer, and JobLauncher for you in the proper order.

From DefaultBatchConfigurer:

@PostConstruct
    public void initialize() {
        try {
            this.jobRepository = createJobRepository();
            this.jobExplorer = createJobExplorer();
            this.jobLauncher = createJobLauncher();
        } catch (Exception e) {
            throw new BatchConfigurationException(e);
        }
    }

If you are going to extend DefaultBatchConfigurer, then I suggest you eliminate the following methods from your code:

  • getJobRepository()
  • manager()
  • getJobLauncher()

From your code sample, it appears that you are already setting the following properties (in your application.properties file?):

spring.datasource.jdbcUrl=...
spring.datasource.username=...
spring.datasource.password=...
spring.datasource.driverClassName=...

That should be sufficient to allow Spring's AutoConfiguration to create an Hikari DataSource for you automatically, and this is the approach I usually take. The Spring Bean name will be dataSource, and this will be autowired into DefaultBatchConfigurer via setDataSource().

However, in your code sample, you have also defined a @Bean annotated method named batchDataSource(), which looks no different to what you should receive from Spring AutoConfiguration. As long as you have the spring.datasource properties mentioned earlier configured, you should be able to eliminate batchDataSource() as well, but I don't think that's necessary, so your choice.

If you still want to manually configure your DataSource, then I suggest that you not extend DefaultBatchConfigurer, but instead define a custom bean for it in a configuration class where you can directly pass in your custom DataSource (based on what I currently know of your use case).

@Bean
public BatchConfigurer batchConfigurer(){
    return new DefaultBatchConfigurer( batchDataSource() );
}
  • Related