From 830da28b8ccfe8babce441291716adf3ba161f4a Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 7 Jan 2019 14:54:59 +0100 Subject: [PATCH] Fix JPA bootstrap executor detection with multiple candidates Spring Boot provides two separate auto-configurations that can potentially expose an `AsyncTaskExecutor` implementation so relying on the presence of a single instance is too weak. This commit fixes the detection of the AsyncTaskExecutor that can be used to bootstrap JPA so that a single instance is used and, in the case more than one exists, the one named `applicationTaskExecutor`. Closes gh-15447 --- .../jpa/JpaRepositoriesAutoConfiguration.java | 24 +++++-- ...JpaRepositoriesAutoConfigurationTests.java | 65 ++++++++++++++++--- .../main/asciidoc/spring-boot-features.adoc | 3 +- 3 files changed, 78 insertions(+), 14 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java index 2538e04b5ce..4c7400684d6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,10 @@ package org.springframework.boot.autoconfigure.data.jpa; +import java.util.Map; + import javax.sql.DataSource; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; @@ -72,8 +73,23 @@ public class JpaRepositoriesAutoConfiguration { @Bean @Conditional(BootstrapExecutorCondition.class) public EntityManagerFactoryBuilderCustomizer entityManagerFactoryBootstrapExecutorCustomizer( - ObjectProvider taskExecutor) { - return (builder) -> builder.setBootstrapExecutor(taskExecutor.getIfAvailable()); + Map taskExecutors) { + return (builder) -> { + AsyncTaskExecutor bootstrapExecutor = determineBootstrapExecutor( + taskExecutors); + if (bootstrapExecutor != null) { + builder.setBootstrapExecutor(bootstrapExecutor); + } + }; + } + + private AsyncTaskExecutor determineBootstrapExecutor( + Map taskExecutors) { + if (taskExecutors.size() == 1) { + return taskExecutors.values().iterator().next(); + } + return taskExecutors + .get(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME); } private static final class BootstrapExecutorCondition extends AnyNestedCondition { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfigurationTests.java index 65da36a2e9f..b3e74a1e8ef 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,12 +31,17 @@ import org.springframework.boot.autoconfigure.data.jpa.city.CityRepository; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; +import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.Import; +import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.transaction.PlatformTransactionManager; import static org.assertj.core.api.Assertions.assertThat; @@ -52,8 +57,7 @@ public class JpaRepositoriesAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(HibernateJpaAutoConfiguration.class, JpaRepositoriesAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, - TaskExecutionAutoConfiguration.class)) + PropertyPlaceholderAutoConfiguration.class)) .withUserConfiguration(EmbeddedDataSourceConfiguration.class); @Test @@ -87,33 +91,76 @@ public class JpaRepositoriesAutoConfigurationTests { } @Test - public void whenBootstrappingModeIsLazyBootstrapExecutorIsConfigured() { - this.contextRunner.withUserConfiguration(TestConfiguration.class) + public void whenBootstrappingModeIsLazyWithMultipleAsyncExecutorBootstrapExecutorIsConfigured() { + this.contextRunner + .withUserConfiguration(MultipleAsyncTaskExecutorConfiguration.class) + .withConfiguration( + AutoConfigurations.of(TaskExecutionAutoConfiguration.class, + TaskSchedulingAutoConfiguration.class)) .withPropertyValues("spring.data.jpa.repositories.bootstrap-mode=lazy") .run((context) -> assertThat( context.getBean(LocalContainerEntityManagerFactoryBean.class) - .getBootstrapExecutor()).isNotNull()); + .getBootstrapExecutor()).isEqualTo( + context.getBean("applicationTaskExecutor"))); + } + + @Test + public void whenBootstrappingModeIsLazyWithSingleAsyncExecutorBootstrapExecutorIsConfigured() { + this.contextRunner + .withUserConfiguration(SingleAsyncTaskExecutorConfiguration.class) + .withPropertyValues("spring.data.jpa.repositories.bootstrap-mode=lazy") + .run((context) -> assertThat( + context.getBean(LocalContainerEntityManagerFactoryBean.class) + .getBootstrapExecutor()).isEqualTo( + context.getBean("testAsyncTaskExecutor"))); } @Test public void whenBootstrappingModeIsDeferredBootstrapExecutorIsConfigured() { - this.contextRunner.withUserConfiguration(TestConfiguration.class) + this.contextRunner + .withUserConfiguration(MultipleAsyncTaskExecutorConfiguration.class) + .withConfiguration( + AutoConfigurations.of(TaskExecutionAutoConfiguration.class, + TaskSchedulingAutoConfiguration.class)) .withPropertyValues( "spring.data.jpa.repositories.bootstrap-mode=deferred") .run((context) -> assertThat( context.getBean(LocalContainerEntityManagerFactoryBean.class) - .getBootstrapExecutor()).isNotNull()); + .getBootstrapExecutor()).isEqualTo( + context.getBean("applicationTaskExecutor"))); } @Test public void whenBootstrappingModeIsDefaultBootstrapExecutorIsNotConfigured() { - this.contextRunner.withUserConfiguration(TestConfiguration.class) + this.contextRunner + .withUserConfiguration(MultipleAsyncTaskExecutorConfiguration.class) + .withConfiguration( + AutoConfigurations.of(TaskExecutionAutoConfiguration.class, + TaskSchedulingAutoConfiguration.class)) .withPropertyValues("spring.data.jpa.repositories.bootstrap-mode=default") .run((context) -> assertThat( context.getBean(LocalContainerEntityManagerFactoryBean.class) .getBootstrapExecutor()).isNull()); } + @Configuration + @EnableScheduling + @Import(TestConfiguration.class) + protected static class MultipleAsyncTaskExecutorConfiguration { + + } + + @Configuration + @Import(TestConfiguration.class) + protected static class SingleAsyncTaskExecutorConfiguration { + + @Bean + public SimpleAsyncTaskExecutor testAsyncTaskExecutor() { + return new SimpleAsyncTaskExecutor(); + } + + } + @Configuration @TestAutoConfigurationPackage(City.class) protected static class TestConfiguration { diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 319e6154d46..8a85a429b14 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3840,7 +3840,8 @@ Spring Data JPA repositories support three different modes of bootstrapping: def deferred, and lazy. To enable deferred or lazy bootstrapping, set the `spring.data.jpa.repositories.bootstrap-mode` to `deferred` or `lazy` respectively. When using deferred or lazy bootstrapping, the auto-configured `EntityManagerFactoryBuilder` -will use the context's async task executor, if any, as the bootstrap executor. +will use the context's `AsyncTaskExecutor`, if any, as the bootstrap executor. If more +than one exists, the one named `applicationTaskExecutor` will be used. TIP: We have barely scratched the surface of Spring Data JPA. For complete details, see the https://docs.spring.io/spring-data/jpa/docs/current/reference/html/[Spring Data JPA