-
Notifications
You must be signed in to change notification settings - Fork 41.9k
Lazily resolve JPA fallback bootstrap executor #50801
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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> persistenceUnitManager, | ||
| ObjectProvider<EntityManagerFactoryBuilderCustomizer> customizers, | ||
| Map<String, AsyncTaskExecutor> taskExecutors) { | ||
| @Nullable 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 EntityManagerFactoryBuilder entityManagerFactoryBuilder(JpaVendorAdapter | |
| return builder; | ||
| } | ||
|
|
||
| private @Nullable AsyncTaskExecutor determineBootstrapExecutor(Map<String, AsyncTaskExecutor> taskExecutors) { | ||
| private @Nullable AsyncTaskExecutor determineBootstrapExecutor(ListableBeanFactory beanFactory) { | ||
| Map<String, AsyncTaskExecutor> taskExecutors = beanFactory.getBeansOfType(AsyncTaskExecutor.class); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good fix moving this behind a Supplier. One minor question: getBeansOfType(AsyncTaskExecutor.class) — does this also trigger eager initialization of any AsyncTaskExecutor beans that are themselves lazy-init? Or does getBeansOfType respect @lazy on individual beans?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. The original regression came from resolving the executors eagerly as a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a clear distinction — thanks! Makes sense that deferring when it's called is the actual fix, not changing what getBeansOfType does internally. |
||
| return (taskExecutors.size() == 1) ? taskExecutors.values().iterator().next() | ||
| : taskExecutors.get(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is exactly the right test to have — verifies the core behavior change (lazy invocation). Good coverage. |
||
| 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<? 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 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice approach wrapping the old AsyncTaskExecutor param into a Supplier for backward compatibility. Just confirming — since this old constructor still accepts a concrete AsyncTaskExecutor (not a Supplier), doesn't that mean any caller using this old constructor still eagerly resolves the executor before passing it in? The laziness benefit only applies to callers using the new Supplier-based constructor, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right — a caller using the
AsyncTaskExecutorconstructor still resolves the executor itself before passing it in, so the laziness only benefits callers of the newSupplier-based constructor. That's intentional: it's the released@since 4.1.0public API, and a caller of it already holds a concrete executor instance, so there is nothing to defer there.The regression wasn't caused by this constructor, but by
JpaBaseConfigurationinjecting aMap<String, AsyncTaskExecutor>method parameter, which forced eager creation of every executor bean while theentityManagerFactoryBuilderbean was being created. The actual fix is that the auto-configuration now uses the newSupplier-based constructor and resolves the executor lazily; the old constructor is retained purely for backward compatibility.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense now—thanks for explaining it clearly.