From 8c519369ec0f3028c95648851497b10def2f8d31 Mon Sep 17 00:00:00 2001 From: Ns <397827222@qq.com> Date: Sat, 20 Jun 2026 22:56:25 +0800 Subject: [PATCH 1/2] Lazily resolve JPA fallback bootstrap executor Previously, the `entityManagerFactoryBuilder` bean method injected a `Map` to determine the fallback executor used for background JPA bootstrapping. A `Map` parameter is resolved eagerly, so this forced early initialization of every `AsyncTaskExecutor` bean whenever the builder was created, even when background bootstrapping was not in use. When an `AsyncTaskExecutor` directly or transitively depended on the `EntityManagerFactory`, this resulted in a `BeanCurrentlyInCreationException`. The fallback executor is now resolved lazily. `EntityManagerFactoryBuilder` holds a `Supplier` that is only invoked when background bootstrapping is actually required, and the executor is then looked up from the `BeanFactory` rather than eagerly injected. A new `Supplier`-based constructor is added for this purpose; the existing constructor that accepts an `AsyncTaskExecutor` is retained and delegates to it. The same eager `Map` injection, along with an unused private method, is also removed from `DataJpaRepositoriesAutoConfiguration`. See gh-50801 Signed-off-by: Ns <397827222@qq.com> --- .../DataJpaRepositoriesAutoConfiguration.java | 15 +-------- .../HibernateJpaAutoConfigurationTests.java | 22 +++++++++++++ .../boot/jpa/EntityManagerFactoryBuilder.java | 32 ++++++++++++++++--- .../autoconfigure/JpaBaseConfiguration.java | 11 ++++--- .../jpa/EntityManagerFactoryBuilderTests.java | 29 +++++++++++++++++ 5 files changed, 86 insertions(+), 23 deletions(-) diff --git a/module/spring-boot-data-jpa/src/main/java/org/springframework/boot/data/jpa/autoconfigure/DataJpaRepositoriesAutoConfiguration.java b/module/spring-boot-data-jpa/src/main/java/org/springframework/boot/data/jpa/autoconfigure/DataJpaRepositoriesAutoConfiguration.java index 5bcaed3c0fc5..c4886199a283 100644 --- a/module/spring-boot-data-jpa/src/main/java/org/springframework/boot/data/jpa/autoconfigure/DataJpaRepositoriesAutoConfiguration.java +++ b/module/spring-boot-data-jpa/src/main/java/org/springframework/boot/data/jpa/autoconfigure/DataJpaRepositoriesAutoConfiguration.java @@ -16,12 +16,8 @@ package org.springframework.boot.data.jpa.autoconfigure; -import java.util.Map; - import javax.sql.DataSource; -import org.jspecify.annotations.Nullable; - import org.springframework.boot.LazyInitializationExcludeFilter; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -38,7 +34,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportSelector; -import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.type.AnnotationMetadata; import org.springframework.data.envers.repository.config.EnableEnversRepositories; import org.springframework.data.envers.repository.support.EnversRevisionRepositoryFactoryBean; @@ -83,8 +78,7 @@ public final class DataJpaRepositoriesAutoConfiguration { @Bean @ConditionalOnProperty(name = "spring.data.jpa.repositories.bootstrap-mode", havingValue = "deferred") - EntityManagerFactoryBuilderCustomizer entityManagerFactoryBootstrapExecutorCustomizer( - Map taskExecutors) { + EntityManagerFactoryBuilderCustomizer entityManagerFactoryBootstrapExecutorCustomizer() { return (builder) -> builder.requireBootstrapExecutor(() -> BootstrapExecutorRequiredException .ofProperty("spring.data.jpa.repositories.bootstrap-mode", "deferred")); } @@ -94,13 +88,6 @@ static LazyInitializationExcludeFilter eagerJpaMetamodelCacheCleanup() { return (name, definition, type) -> "org.springframework.data.jpa.util.JpaMetamodelCacheCleanup".equals(name); } - private @Nullable AsyncTaskExecutor determineBootstrapExecutor(Map taskExecutors) { - if (taskExecutors.size() == 1) { - return taskExecutors.values().iterator().next(); - } - return taskExecutors.get(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME); - } - static class JpaRepositoriesImportSelector implements ImportSelector { private static final boolean ENVERS_AVAILABLE = ClassUtils.isPresent( diff --git a/module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java b/module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java index 5bbb2992cf15..5a6329ac679d 100644 --- a/module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java +++ b/module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java @@ -303,6 +303,15 @@ private EntityManagerFactoryBuilderCustomizer bootstrapExecutorCustomizer() { return (builder) -> builder.setBootstrapExecutor(new SimpleAsyncTaskExecutor()); } + @Test + void whenAsyncTaskExecutorIsDefinedInJpaDependentConfigurationDoesNotTriggerABeanCurrentlyInCreationException() { + this.contextRunner.withUserConfiguration(AsyncTaskExecutorDependingOnEntityManagerFactoryConfiguration.class) + .run((context) -> { + assertThat(context).hasNotFailed(); + assertThat(context).hasSingleBean(EntityManagerFactory.class); + }); + } + @Test void customJpaProperties() { this.contextRunner @@ -1474,4 +1483,17 @@ SimpleAsyncTaskExecutor applicationTaskExecutor() { } + @Configuration(proxyBeanMethods = false) + static class AsyncTaskExecutorDependingOnEntityManagerFactoryConfiguration { + + AsyncTaskExecutorDependingOnEntityManagerFactoryConfiguration(EntityManagerFactory entityManagerFactory) { + } + + @Bean + SimpleAsyncTaskExecutor exampleTaskExecutor() { + return new SimpleAsyncTaskExecutor(); + } + + } + } diff --git a/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/EntityManagerFactoryBuilder.java b/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/EntityManagerFactoryBuilder.java index 65a56b7e13fa..46b8f87e00b4 100644 --- a/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/EntityManagerFactoryBuilder.java +++ b/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/EntityManagerFactoryBuilder.java @@ -68,7 +68,7 @@ public class EntityManagerFactoryBuilder { private final @Nullable URL persistenceUnitRootLocation; - private final @Nullable AsyncTaskExecutor fallbackBootstrapExecutor; + private final Supplier fallbackBootstrapExecutor; private @Nullable AsyncTaskExecutor bootstrapExecutor; @@ -105,7 +105,7 @@ public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, Function> jpaPropertiesFactory, @Nullable PersistenceUnitManager persistenceUnitManager, @Nullable URL persistenceUnitRootLocation) { - this(jpaVendorAdapter, jpaPropertiesFactory, persistenceUnitManager, persistenceUnitRootLocation, null); + this(jpaVendorAdapter, jpaPropertiesFactory, persistenceUnitManager, persistenceUnitRootLocation, () -> null); } /** @@ -126,6 +126,29 @@ public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, Function> jpaPropertiesFactory, @Nullable PersistenceUnitManager persistenceUnitManager, @Nullable URL persistenceUnitRootLocation, @Nullable AsyncTaskExecutor fallbackBootstrapExecutor) { + this(jpaVendorAdapter, jpaPropertiesFactory, persistenceUnitManager, persistenceUnitRootLocation, + () -> fallbackBootstrapExecutor); + } + + /** + * Create a new instance passing in the common pieces that will be shared if multiple + * EntityManagerFactory instances are created. + * @param jpaVendorAdapter a vendor adapter + * @param jpaPropertiesFactory the JPA properties to be passed to the persistence + * provider, based on the {@linkplain #dataSource(DataSource) configured data source} + * @param persistenceUnitManager optional source of persistence unit information (can + * be null) + * @param persistenceUnitRootLocation the persistence unit root location to use as a + * fallback or {@code null} + * @param fallbackBootstrapExecutor a supplier of the fallback executor to use when + * background bootstrapping is required but no explicit executor has been set. The + * supplier is only invoked when background bootstrapping is actually required. + * @since 4.1.1 + */ + public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, + Function> jpaPropertiesFactory, + @Nullable PersistenceUnitManager persistenceUnitManager, @Nullable URL persistenceUnitRootLocation, + Supplier fallbackBootstrapExecutor) { this.jpaVendorAdapter = jpaVendorAdapter; this.persistenceUnitManager = persistenceUnitManager; this.jpaPropertiesFactory = jpaPropertiesFactory; @@ -343,8 +366,9 @@ private Map jpaPropertyMap() { return EntityManagerFactoryBuilder.this.bootstrapExecutor; } if (EntityManagerFactoryBuilder.this.requireBootstrapExecutorExceptionSupplier != null) { - if (EntityManagerFactoryBuilder.this.fallbackBootstrapExecutor != null) { - return EntityManagerFactoryBuilder.this.fallbackBootstrapExecutor; + @Nullable AsyncTaskExecutor fallback = EntityManagerFactoryBuilder.this.fallbackBootstrapExecutor.get(); + if (fallback != null) { + return fallback; } RuntimeException ex = EntityManagerFactoryBuilder.this.requireBootstrapExecutorExceptionSupplier.get(); throw (ex != null) ? ex : new IllegalStateException("A bootstrap executor is required"); diff --git a/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/JpaBaseConfiguration.java b/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/JpaBaseConfiguration.java index 6ea282cd6c42..81bd01579362 100644 --- a/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/JpaBaseConfiguration.java +++ b/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/JpaBaseConfiguration.java @@ -28,6 +28,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -123,11 +124,10 @@ public JpaVendorAdapter jpaVendorAdapter() { @ConditionalOnMissingBean public EntityManagerFactoryBuilder entityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, ObjectProvider persistenceUnitManager, - ObjectProvider customizers, - Map taskExecutors) { - AsyncTaskExecutor bootstrapExecutor = determineBootstrapExecutor(taskExecutors); + ObjectProvider customizers, ListableBeanFactory beanFactory) { EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(jpaVendorAdapter, - this::buildJpaProperties, persistenceUnitManager.getIfAvailable(), null, bootstrapExecutor); + this::buildJpaProperties, persistenceUnitManager.getIfAvailable(), null, + () -> determineBootstrapExecutor(beanFactory)); if (this.properties.getBootstrap() == Bootstrap.ASYNC) { builder.requireBootstrapExecutor( () -> BootstrapExecutorRequiredException.ofProperty("spring.jpa.bootstrap", "async")); @@ -136,7 +136,8 @@ public EntityManagerFactoryBuilder entityManagerFactoryBuilder(JpaVendorAdapter return builder; } - private @Nullable AsyncTaskExecutor determineBootstrapExecutor(Map taskExecutors) { + private @Nullable AsyncTaskExecutor determineBootstrapExecutor(ListableBeanFactory beanFactory) { + Map taskExecutors = beanFactory.getBeansOfType(AsyncTaskExecutor.class); return (taskExecutors.size() == 1) ? taskExecutors.values().iterator().next() : taskExecutors.get(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME); } diff --git a/module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/EntityManagerFactoryBuilderTests.java b/module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/EntityManagerFactoryBuilderTests.java index 58b584f27951..0ac3a1616afa 100644 --- a/module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/EntityManagerFactoryBuilderTests.java +++ b/module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/EntityManagerFactoryBuilderTests.java @@ -18,7 +18,9 @@ import java.util.Collections; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; +import java.util.function.Supplier; import javax.sql.DataSource; @@ -125,6 +127,26 @@ void requireBootstrapExecutorWhenSupplierReturnsNullExecutorAndNoFallbackExecuto .withMessage("A bootstrap executor is required"); } + @Test + void requireBootstrapExecutorWhenFallbackExecutorSupplierProvidesExecutorDoesNotThrow() { + EntityManagerFactoryBuilder builder = createEmptyBuilderWithFallbackSupplier(SimpleAsyncTaskExecutor::new); + builder.requireBootstrapExecutor(() -> new IllegalStateException("BAD")); + DataSource dataSource = mock(); + assertThatNoException().isThrownBy(builder.dataSource(dataSource)::build); + } + + @Test + void fallbackExecutorSupplierIsNotInvokedWhenBootstrapExecutorNotRequired() { + AtomicBoolean invoked = new AtomicBoolean(); + EntityManagerFactoryBuilder builder = createEmptyBuilderWithFallbackSupplier(() -> { + invoked.set(true); + return new SimpleAsyncTaskExecutor(); + }); + DataSource dataSource = mock(); + builder.dataSource(dataSource).build(); + assertThat(invoked).isFalse(); + } + private EntityManagerFactoryBuilder createEmptyBuilder() { return createEmptyBuilder(null); } @@ -135,6 +157,13 @@ private EntityManagerFactoryBuilder createEmptyBuilder(@Nullable AsyncTaskExecut fallbackBootstrapExecutor); } + private EntityManagerFactoryBuilder createEmptyBuilderWithFallbackSupplier( + Supplier fallbackBootstrapExecutorSupplier) { + Function> jpaPropertiesFactory = (dataSource) -> Collections.emptyMap(); + return new EntityManagerFactoryBuilder(new TestJpaVendorAdapter(), jpaPropertiesFactory, null, null, + fallbackBootstrapExecutorSupplier); + } + static class TestJpaVendorAdapter extends AbstractJpaVendorAdapter { @Override From d4d20787c17d5f4c2f390a793d01a58d381f57e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 14 Jul 2026 17:45:54 +0200 Subject: [PATCH 2/2] Polish "Lazily resolve JPA fallback bootstrap executor" See gh-50801 --- .../HibernateJpaAutoConfigurationTests.java | 11 ++++++----- .../boot/jpa/EntityManagerFactoryBuilder.java | 6 ++++-- .../jpa/EntityManagerFactoryBuilderTests.java | 16 +++++----------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java b/module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java index 5a6329ac679d..3d8eec846c00 100644 --- a/module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java +++ b/module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java @@ -104,6 +104,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.SQLExceptionTranslator; @@ -304,11 +305,11 @@ private EntityManagerFactoryBuilderCustomizer bootstrapExecutorCustomizer() { } @Test - void whenAsyncTaskExecutorIsDefinedInJpaDependentConfigurationDoesNotTriggerABeanCurrentlyInCreationException() { - this.contextRunner.withUserConfiguration(AsyncTaskExecutorDependingOnEntityManagerFactoryConfiguration.class) + void whenAsyncTaskExecutorIsDefinedInJpaDependentConfigurationDoesNotFail() { + this.contextRunner.withUserConfiguration(TaskExecutorDependingOnEntityManagerFactoryConfiguration.class) .run((context) -> { assertThat(context).hasNotFailed(); - assertThat(context).hasSingleBean(EntityManagerFactory.class); + assertThat(context).hasSingleBean(EntityManagerFactory.class).hasSingleBean(AsyncTaskExecutor.class); }); } @@ -1484,9 +1485,9 @@ SimpleAsyncTaskExecutor applicationTaskExecutor() { } @Configuration(proxyBeanMethods = false) - static class AsyncTaskExecutorDependingOnEntityManagerFactoryConfiguration { + static class TaskExecutorDependingOnEntityManagerFactoryConfiguration { - AsyncTaskExecutorDependingOnEntityManagerFactoryConfiguration(EntityManagerFactory entityManagerFactory) { + TaskExecutorDependingOnEntityManagerFactoryConfiguration(EntityManagerFactory entityManagerFactory) { } @Bean diff --git a/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/EntityManagerFactoryBuilder.java b/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/EntityManagerFactoryBuilder.java index 46b8f87e00b4..4d241563f623 100644 --- a/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/EntityManagerFactoryBuilder.java +++ b/module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/EntityManagerFactoryBuilder.java @@ -121,7 +121,10 @@ public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, * @param fallbackBootstrapExecutor the fallback executor to use when background * bootstrapping is required but no explicit executor has been set * @since 4.1.0 + * @deprecated since 4.1.1 for removal in 4.3.0 in favor of + * {@link #EntityManagerFactoryBuilder(JpaVendorAdapter, Function, PersistenceUnitManager, URL, Supplier)} */ + @Deprecated(since = "4.1.1", forRemoval = true) public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, Function> jpaPropertiesFactory, @Nullable PersistenceUnitManager persistenceUnitManager, @Nullable URL persistenceUnitRootLocation, @@ -141,8 +144,7 @@ public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, * @param persistenceUnitRootLocation the persistence unit root location to use as a * fallback or {@code null} * @param fallbackBootstrapExecutor a supplier of the fallback executor to use when - * background bootstrapping is required but no explicit executor has been set. The - * supplier is only invoked when background bootstrapping is actually required. + * background bootstrapping is required, but no explicit executor has been set. * @since 4.1.1 */ public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter, diff --git a/module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/EntityManagerFactoryBuilderTests.java b/module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/EntityManagerFactoryBuilderTests.java index 0ac3a1616afa..33bcd614190b 100644 --- a/module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/EntityManagerFactoryBuilderTests.java +++ b/module/spring-boot-jpa/src/test/java/org/springframework/boot/jpa/EntityManagerFactoryBuilderTests.java @@ -104,7 +104,7 @@ void requireBootstrapExecutorWhenExecutorProvidedDoesNotThrow() { @Test void requireBootstrapExecutorWhenFallbackExecutorProvidesExecutorDoesNotThrow() { - EntityManagerFactoryBuilder builder = createEmptyBuilder(new SimpleAsyncTaskExecutor()); + EntityManagerFactoryBuilder builder = createEmptyBuilder(SimpleAsyncTaskExecutor::new); builder.requireBootstrapExecutor(() -> new IllegalStateException("BAD")); DataSource dataSource = mock(); assertThatNoException().isThrownBy(builder.dataSource(dataSource)::build); @@ -129,7 +129,7 @@ void requireBootstrapExecutorWhenSupplierReturnsNullExecutorAndNoFallbackExecuto @Test void requireBootstrapExecutorWhenFallbackExecutorSupplierProvidesExecutorDoesNotThrow() { - EntityManagerFactoryBuilder builder = createEmptyBuilderWithFallbackSupplier(SimpleAsyncTaskExecutor::new); + EntityManagerFactoryBuilder builder = createEmptyBuilder(SimpleAsyncTaskExecutor::new); builder.requireBootstrapExecutor(() -> new IllegalStateException("BAD")); DataSource dataSource = mock(); assertThatNoException().isThrownBy(builder.dataSource(dataSource)::build); @@ -138,7 +138,7 @@ void requireBootstrapExecutorWhenFallbackExecutorSupplierProvidesExecutorDoesNot @Test void fallbackExecutorSupplierIsNotInvokedWhenBootstrapExecutorNotRequired() { AtomicBoolean invoked = new AtomicBoolean(); - EntityManagerFactoryBuilder builder = createEmptyBuilderWithFallbackSupplier(() -> { + EntityManagerFactoryBuilder builder = createEmptyBuilder(() -> { invoked.set(true); return new SimpleAsyncTaskExecutor(); }); @@ -148,16 +148,10 @@ void fallbackExecutorSupplierIsNotInvokedWhenBootstrapExecutorNotRequired() { } private EntityManagerFactoryBuilder createEmptyBuilder() { - return createEmptyBuilder(null); + return createEmptyBuilder(() -> null); } - private EntityManagerFactoryBuilder createEmptyBuilder(@Nullable AsyncTaskExecutor fallbackBootstrapExecutor) { - Function> jpaPropertiesFactory = (dataSource) -> Collections.emptyMap(); - return new EntityManagerFactoryBuilder(new TestJpaVendorAdapter(), jpaPropertiesFactory, null, null, - fallbackBootstrapExecutor); - } - - private EntityManagerFactoryBuilder createEmptyBuilderWithFallbackSupplier( + private EntityManagerFactoryBuilder createEmptyBuilder( Supplier fallbackBootstrapExecutorSupplier) { Function> jpaPropertiesFactory = (dataSource) -> Collections.emptyMap(); return new EntityManagerFactoryBuilder(new TestJpaVendorAdapter(), jpaPropertiesFactory, null, null,