Browse Source

Add 'spring.jpa.bootstrap' property

Add a `spring.jpa.bootstrap` property that can be set to `async`
to switch on background bootstrapping in classic JPA setup.

Closes gh-49733
pull/49740/head
Phillip Webb 1 week ago
parent
commit
91f4bc9455
  1. 91
      module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java
  2. 5
      module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/JpaBaseConfiguration.java
  3. 32
      module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/JpaProperties.java

91
module/spring-boot-hibernate/src/test/java/org/springframework/boot/hibernate/autoconfigure/HibernateJpaAutoConfigurationTests.java

@ -67,6 +67,8 @@ import org.springframework.beans.factory.BeanCreationException; @@ -67,6 +67,8 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.diagnostics.FailureAnalyzedException;
import org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration;
import org.springframework.boot.hibernate.SpringImplicitNamingStrategy;
import org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfigurationTests.JpaUsingApplicationListenerConfiguration.EventCapturingApplicationListener;
@ -102,6 +104,7 @@ import org.springframework.context.annotation.Bean; @@ -102,6 +104,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.SQLExceptionTranslator;
import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
@ -247,6 +250,59 @@ class HibernateJpaAutoConfigurationTests { @@ -247,6 +250,59 @@ class HibernateJpaAutoConfigurationTests {
.run((context) -> assertThat(context).doesNotHaveBean(OpenEntityManagerInViewInterceptor.class));
}
@Test
void whenBackgroundBootstrapingAndSingleAsyncTaksExecutorConfiguresBackgroundExecutor() {
this.contextRunner.withPropertyValues("spring.jpa.bootstrap=async")
.withUserConfiguration(SingleAsyncTaskExecutorConfiguration.class)
.run((context) -> assertThat(
context.getBean(LocalContainerEntityManagerFactoryBean.class).getBootstrapExecutor())
.isInstanceOf(SimpleAsyncTaskExecutor.class));
}
@Test
void whenBackgroundBootstrapingAndApplicationTaksExecutorConfiguresBackgroundExecutor() {
this.contextRunner.withPropertyValues("spring.jpa.bootstrap=async")
.withUserConfiguration(MultipleAsyncTaskExecutorsConfiguration.class,
ApplicationTaskExecutorConfiguration.class)
.run((context) -> assertThat(
context.getBean(LocalContainerEntityManagerFactoryBean.class).getBootstrapExecutor())
.isSameAs(context.getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)));
}
@Test
void whenBackgroundBootstrapingAndMissingTaksExecutorThrowsException() {
this.contextRunner.withPropertyValues("spring.jpa.bootstrap=async")
.run((context) -> assertThat(context).getFailure()
.rootCause()
.isInstanceOf(FailureAnalyzedException.class)
.message()
.contains("bootstrap executor is required when 'spring.jpa.bootstrap' is set to 'async'"));
}
@Test
void whenBackgroundBootstrapingAndMultipleTaksExecutorThrowsException() {
this.contextRunner.withPropertyValues("spring.jpa.bootstrap=async")
.withUserConfiguration(MultipleAsyncTaskExecutorsConfiguration.class)
.run((context) -> assertThat(context).getFailure()
.rootCause()
.isInstanceOf(FailureAnalyzedException.class)
.message()
.contains("bootstrap executor is required when 'spring.jpa.bootstrap' is set to 'async'"));
}
@Test
void whenBackgroundBootstrapingAndCustomizedBackgroundExecutorThrowsException() {
this.contextRunner.withPropertyValues("spring.jpa.bootstrap=async")
.withBean(EntityManagerFactoryBuilderCustomizer.class, this::bootstrapExecutorCustomizer)
.run((context) -> assertThat(
context.getBean(LocalContainerEntityManagerFactoryBean.class).getBootstrapExecutor())
.isInstanceOf(SimpleAsyncTaskExecutor.class));
}
private EntityManagerFactoryBuilderCustomizer bootstrapExecutorCustomizer() {
return (builder) -> builder.setBootstrapExecutor(new SimpleAsyncTaskExecutor());
}
@Test
void customJpaProperties() {
this.contextRunner
@ -1383,4 +1439,39 @@ class HibernateJpaAutoConfigurationTests { @@ -1383,4 +1439,39 @@ class HibernateJpaAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class SingleAsyncTaskExecutorConfiguration {
@Bean
SimpleAsyncTaskExecutor exampleTaskExecutor() {
return new SimpleAsyncTaskExecutor();
}
}
@Configuration(proxyBeanMethods = false)
static class MultipleAsyncTaskExecutorsConfiguration {
@Bean
SimpleAsyncTaskExecutor exampleTaskExecutor1() {
return new SimpleAsyncTaskExecutor();
}
@Bean
SimpleAsyncTaskExecutor exampleTaskExecutor2() {
return new SimpleAsyncTaskExecutor();
}
}
@Configuration(proxyBeanMethods = false)
static class ApplicationTaskExecutorConfiguration {
@Bean
SimpleAsyncTaskExecutor applicationTaskExecutor() {
return new SimpleAsyncTaskExecutor();
}
}
}

5
module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/JpaBaseConfiguration.java

@ -40,6 +40,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat @@ -40,6 +40,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jpa.EntityManagerFactoryBuilder;
import org.springframework.boot.jpa.autoconfigure.JpaProperties.Bootstrap;
import org.springframework.boot.persistence.autoconfigure.EntityScanPackages;
import org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizers;
import org.springframework.context.annotation.Bean;
@ -127,6 +128,10 @@ public abstract class JpaBaseConfiguration { @@ -127,6 +128,10 @@ public abstract class JpaBaseConfiguration {
@Nullable AsyncTaskExecutor bootstrapExecutor = determineBootstrapExecutor(taskExecutors);
EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(jpaVendorAdapter,
this::buildJpaProperties, persistenceUnitManager.getIfAvailable(), null, bootstrapExecutor);
if (this.properties.getBootstrap() == Bootstrap.ASYNC) {
builder.requireBootstrapExecutor(
new PropertyBasedRequiredBackgroundBootstrapping("spring.jpa.bootstrap", "async"));
}
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return builder;
}

32
module/spring-boot-jpa/src/main/java/org/springframework/boot/jpa/autoconfigure/JpaProperties.java

@ -77,6 +77,11 @@ public class JpaProperties { @@ -77,6 +77,11 @@ public class JpaProperties {
*/
private @Nullable Boolean openInView;
/**
* Bootstrap method to use.
*/
private Bootstrap bootstrap = Bootstrap.DEFAULT;
public Map<String, String> getProperties() {
return this.properties;
}
@ -129,4 +134,31 @@ public class JpaProperties { @@ -129,4 +134,31 @@ public class JpaProperties {
this.openInView = openInView;
}
public Bootstrap getBootstrap() {
return this.bootstrap;
}
public void setBootstrap(Bootstrap bootstrap) {
this.bootstrap = bootstrap;
}
/**
* Bootstrap methods that can be used with JPA.
*/
public enum Bootstrap {
/**
* Default JPA bootstrapping.
*/
DEFAULT,
/**
* Asynchronous JPA bootstrapping. The ApplicationContext must either have a
* single AsyncTaskExecutor bean, or an AsyncTaskExecutor bean named
* 'applicationTaskExecutor'.
*/
ASYNC
}
}

Loading…
Cancel
Save