🐞 Bug report
This used to work on version 4.0.6, but immediately caused an error on plain upgrade to 4.1.0.
✏️ Minimal example
To be reproduced with a simple project, but I think I have understood the rationale behind.
I have a Spring Batch project that heavily depends on task partitioning and sub-partitioning. Long story short, to avoid deadlocks between level-1 partitioned tasks waiting for completion of level-2 partition tasks, I have created a number of AsyncTaskExecutors.
All my Batch Configuration classes depend on JobRepository, and instead of injecting as a bean parameter, I inject it as a field of the configuration class itself
❌NO:
@Configuration
class MyJobConfiguration {
protected AsyncTaskExecutor myAsyncTaskExecutor(){...}
public Job myJob(JobRepository jobRepository, Step myStep...) {
return new JobBuilder("myJob", jobRepository)
.....
.build();
}
}
✅YES
@RequiredArgsConstructor
@Configuration
class MyJobConfiguration {
private final JobRepository jobRepository;
protected AsyncTaskExecutor myAsyncTaskExecutor(){...}
public Job myJob(Step myStep...) {
return new JobBuilder("myJob", jobRepository)
.....
.build();
}
}
The issue occurs only in JPA-powered projects, and if and only if one:
- Use @EnableJdbcJobRepositories somewhere (e.g. in
@SpringBootApplication)
- Declare a bean of type AsyncTaskExecutor in the same
Configuration that depends on JobRepository
This causes an error starting the application like the following (in a junit test):
Failed to resolve parameter [final org.springframework.batch.test.JobRepositoryTestUtils jobRepositoryTestUtils] in constructor [public com.acme.example.tnaimport.TnaImportStepIntegrationTest(org.springframework.batch.test.JobRepositoryTestUtils,org.springframework.batch.core.job.Job,org.springframework.batch.test.JobOperatorTestUtils)]: Failed to load ApplicationContext for [redacted]
org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [final org.springframework.batch.test.JobRepositoryTestUtils jobRepositoryTestUtils] in constructor [public com.acme.example.tnaimport.TnaImportStepIntegrationTest(org.springframework.batch.test.JobRepositoryTestUtils,org.springframework.batch.core.job.Job,org.springframework.batch.test.JobOperatorTestUtils)]: Failed to load ApplicationContext for [redacted]
Caused by: java.lang.IllegalStateException: Failed to load ApplicationContext for [redacted]
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/hibernate/autoconfigure/HibernateJpaConfiguration.class]: Unsatisfied dependency expressed through method 'entityManagerFactory' parameter 0: Error creating bean with name 'entityManagerFactoryBuilder' defined in class path resource [org/springframework/boot/hibernate/autoconfigure/HibernateJpaConfiguration.class]: Unsatisfied dependency expressed through method 'entityManagerFactoryBuilder' parameter 3: Error creating bean with name 'exampleJobConfiguration' defined in file [exampleJobConfiguration.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'jobRepository': Cannot resolve reference to bean 'transactionManager' while setting bean property 'transactionManager'
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'entityManagerFactoryBuilder' defined in class path resource [org/springframework/boot/hibernate/autoconfigure/HibernateJpaConfiguration.class]: Unsatisfied dependency expressed through method 'entityManagerFactoryBuilder' parameter 3: Error creating bean with name 'exampleJobConfiguration' defined in file [exampleJobConfiguration.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'jobRepository': Cannot resolve reference to bean 'transactionManager' while setting bean property 'transactionManager'
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'exampleJobConfiguration' defined in file [exampleJobConfiguration.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'jobRepository': Cannot resolve reference to bean 'transactionManager' while setting bean property 'transactionManager'
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobRepository': Cannot resolve reference to bean 'transactionManager' while setting bean property 'transactionManager'
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager' defined in class path resource [org/springframework/boot/hibernate/autoconfigure/HibernateJpaConfiguration.class]: Error creating bean with name 'entityManagerFactory': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency?
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'entityManagerFactory': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency?
🔬Root cause analysis
It looks like to me that the implementation of JpaBaseConfiguration has introduced this behaviour since 4.1.0.
|
@Bean |
|
@ConditionalOnMissingBean |
|
public EntityManagerFactoryBuilder entityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, |
|
ObjectProvider<PersistenceUnitManager> persistenceUnitManager, |
|
ObjectProvider<EntityManagerFactoryBuilderCustomizer> customizers, |
|
Map<String, AsyncTaskExecutor> taskExecutors) { |
|
@Nullable AsyncTaskExecutor bootstrapExecutor = determineBootstrapExecutor(taskExecutors); |
|
EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(jpaVendorAdapter, |
|
this::buildJpaProperties, persistenceUnitManager.getIfAvailable(), null, bootstrapExecutor); |
|
if (this.properties.getBootstrap() == Bootstrap.ASYNC) { |
|
builder.requireBootstrapExecutor( |
|
() -> BootstrapExecutorRequiredException.ofProperty("spring.jpa.bootstrap", "async")); |
|
} |
|
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); |
|
return builder; |
|
} |
A new parameter has been added since the previous version, Map<String, AsyncTaskExecutor> requesting the ApplicationContext for all beans of type AsyncTaskExecutor along with their name.
The circular dependency is then caused by entityManagerFactoryBuilder depending on the AsyncTaskExecutor declared from implementor code, which is declared in a bean depending on JobRepository. But since the project uses JDBC Batch repositories, that bean still depends on entityManagerFactory.
💭Discussion
Before providing a MRE (I could do by early next week now that I have a clearer picture), I would like to understand from your feedback if this is really an issue with Spring 4.1, or if I am wrong by autowiring the JobRepository in all my Configuration classes creating such hard dependency.
Solutions can possibly be at multiple levels. I'll temporarily stop the upgrade in my project
🐞 Bug report
This used to work on version
4.0.6, but immediately caused an error on plain upgrade to4.1.0.✏️ Minimal example
To be reproduced with a simple project, but I think I have understood the rationale behind.
I have a Spring Batch project that heavily depends on task partitioning and sub-partitioning. Long story short, to avoid deadlocks between level-1 partitioned tasks waiting for completion of level-2 partition tasks, I have created a number of
AsyncTaskExecutors.All my Batch
Configurationclasses depend onJobRepository, and instead of injecting as a bean parameter, I inject it as a field of the configuration class itself❌NO:
✅YES
The issue occurs only in JPA-powered projects, and if and only if one:
@SpringBootApplication)Configurationthat depends on JobRepositoryThis causes an error starting the application like the following (in a junit test):
🔬Root cause analysis
It looks like to me that the implementation of
JpaBaseConfigurationhas introduced this behaviour since 4.1.0.spring-boot/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/JpaBaseConfiguration.java
Lines 122 to 137 in f71e797
A new parameter has been added since the previous version,
Map<String, AsyncTaskExecutor>requesting theApplicationContextfor all beans of typeAsyncTaskExecutoralong with their name.The circular dependency is then caused by
entityManagerFactoryBuilderdepending on theAsyncTaskExecutordeclared from implementor code, which is declared in a bean depending onJobRepository. But since the project uses JDBC Batch repositories, that bean still depends onentityManagerFactory.💭Discussion
Before providing a MRE (I could do by early next week now that I have a clearer picture), I would like to understand from your feedback if this is really an issue with Spring 4.1, or if I am wrong by autowiring the JobRepository in all my Configuration classes creating such hard dependency.
Solutions can possibly be at multiple levels. I'll temporarily stop the upgrade in my project