mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Lazily resolve JPA fallback bootstrap executor
Previously, the `entityManagerFactoryBuilder` bean method injected a `Map<String, AsyncTaskExecutor>` 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<String, AsyncTaskExecutor>` injection, along with an unused private method, is also removed from `DataJpaRepositoriesAutoConfiguration`. See gh-50801 Signed-off-by: Ns <397827222@qq.com>
This commit is contained in:
+1
-14
@@ -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.boot.jpa.autoconfigure.EntityManagerFactoryBuilderCus
|
||||
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<String, AsyncTaskExecutor> taskExecutors) {
|
||||
EntityManagerFactoryBuilderCustomizer entityManagerFactoryBootstrapExecutorCustomizer() {
|
||||
return (builder) -> builder.requireBootstrapExecutor(() -> BootstrapExecutorRequiredException
|
||||
.ofProperty("spring.data.jpa.repositories.bootstrap-mode", "deferred"));
|
||||
}
|
||||
@@ -94,13 +88,6 @@ public final class DataJpaRepositoriesAutoConfiguration {
|
||||
return (name, definition, type) -> "org.springframework.data.jpa.util.JpaMetamodelCacheCleanup".equals(name);
|
||||
}
|
||||
|
||||
private @Nullable AsyncTaskExecutor determineBootstrapExecutor(Map<String, AsyncTaskExecutor> 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(
|
||||
|
||||
+22
@@ -303,6 +303,15 @@ class HibernateJpaAutoConfigurationTests {
|
||||
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 @@ class HibernateJpaAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class AsyncTaskExecutorDependingOnEntityManagerFactoryConfiguration {
|
||||
|
||||
AsyncTaskExecutorDependingOnEntityManagerFactoryConfiguration(EntityManagerFactory entityManagerFactory) {
|
||||
}
|
||||
|
||||
@Bean
|
||||
SimpleAsyncTaskExecutor exampleTaskExecutor() {
|
||||
return new SimpleAsyncTaskExecutor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+28
-4
@@ -68,7 +68,7 @@ public class EntityManagerFactoryBuilder {
|
||||
|
||||
private final @Nullable URL persistenceUnitRootLocation;
|
||||
|
||||
private final @Nullable AsyncTaskExecutor fallbackBootstrapExecutor;
|
||||
private final Supplier<? extends @Nullable AsyncTaskExecutor> fallbackBootstrapExecutor;
|
||||
|
||||
private @Nullable AsyncTaskExecutor bootstrapExecutor;
|
||||
|
||||
@@ -105,7 +105,7 @@ public class EntityManagerFactoryBuilder {
|
||||
public EntityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter,
|
||||
Function<DataSource, Map<String, ?>> jpaPropertiesFactory,
|
||||
@Nullable PersistenceUnitManager persistenceUnitManager, @Nullable URL persistenceUnitRootLocation) {
|
||||
this(jpaVendorAdapter, jpaPropertiesFactory, persistenceUnitManager, persistenceUnitRootLocation, null);
|
||||
this(jpaVendorAdapter, jpaPropertiesFactory, persistenceUnitManager, persistenceUnitRootLocation, () -> null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,6 +126,29 @@ public class EntityManagerFactoryBuilder {
|
||||
Function<DataSource, Map<String, ?>> 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<DataSource, Map<String, ?>> jpaPropertiesFactory,
|
||||
@Nullable PersistenceUnitManager persistenceUnitManager, @Nullable URL persistenceUnitRootLocation,
|
||||
Supplier<? extends @Nullable AsyncTaskExecutor> fallbackBootstrapExecutor) {
|
||||
this.jpaVendorAdapter = jpaVendorAdapter;
|
||||
this.persistenceUnitManager = persistenceUnitManager;
|
||||
this.jpaPropertiesFactory = jpaPropertiesFactory;
|
||||
@@ -343,8 +366,9 @@ public class EntityManagerFactoryBuilder {
|
||||
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");
|
||||
|
||||
+6
-5
@@ -28,6 +28,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
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 abstract class JpaBaseConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
public EntityManagerFactoryBuilder entityManagerFactoryBuilder(JpaVendorAdapter jpaVendorAdapter,
|
||||
ObjectProvider<PersistenceUnitManager> persistenceUnitManager,
|
||||
ObjectProvider<EntityManagerFactoryBuilderCustomizer> customizers,
|
||||
Map<String, AsyncTaskExecutor> taskExecutors) {
|
||||
AsyncTaskExecutor bootstrapExecutor = determineBootstrapExecutor(taskExecutors);
|
||||
ObjectProvider<EntityManagerFactoryBuilderCustomizer> 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 abstract class JpaBaseConfiguration {
|
||||
return builder;
|
||||
}
|
||||
|
||||
private @Nullable AsyncTaskExecutor determineBootstrapExecutor(Map<String, AsyncTaskExecutor> taskExecutors) {
|
||||
private @Nullable AsyncTaskExecutor determineBootstrapExecutor(ListableBeanFactory beanFactory) {
|
||||
Map<String, AsyncTaskExecutor> taskExecutors = beanFactory.getBeansOfType(AsyncTaskExecutor.class);
|
||||
return (taskExecutors.size() == 1) ? taskExecutors.values().iterator().next()
|
||||
: taskExecutors.get(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
|
||||
}
|
||||
|
||||
+29
@@ -18,7 +18,9 @@ package org.springframework.boot.jpa;
|
||||
|
||||
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 @@ class EntityManagerFactoryBuilderTests {
|
||||
.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 @@ class EntityManagerFactoryBuilderTests {
|
||||
fallbackBootstrapExecutor);
|
||||
}
|
||||
|
||||
private EntityManagerFactoryBuilder createEmptyBuilderWithFallbackSupplier(
|
||||
Supplier<? extends @Nullable AsyncTaskExecutor> fallbackBootstrapExecutorSupplier) {
|
||||
Function<DataSource, Map<String, ?>> jpaPropertiesFactory = (dataSource) -> Collections.emptyMap();
|
||||
return new EntityManagerFactoryBuilder(new TestJpaVendorAdapter(), jpaPropertiesFactory, null, null,
|
||||
fallbackBootstrapExecutorSupplier);
|
||||
}
|
||||
|
||||
static class TestJpaVendorAdapter extends AbstractJpaVendorAdapter {
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user