diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java index ebc24e6e1f..cc4a0c591b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.java @@ -82,6 +82,12 @@ class DataSourceInitializedPublisher implements BeanPostProcessor { if (bean instanceof HibernateProperties) { this.hibernateProperties = (HibernateProperties) bean; } + if (bean instanceof LocalContainerEntityManagerFactoryBean) { + LocalContainerEntityManagerFactoryBean factory = (LocalContainerEntityManagerFactoryBean) bean; + if (factory.getBootstrapExecutor() == null) { + publishEventIfRequired(factory.getNativeEntityManagerFactory()); + } + } return bean; } @@ -195,9 +201,6 @@ class DataSourceInitializedPublisher implements BeanPostProcessor { bootstrapExecutor.execute(() -> DataSourceInitializedPublisher.this .publishEventIfRequired(emf)); } - else { - DataSourceInitializedPublisher.this.publishEventIfRequired(emf); - } } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java index 3fb394edbe..975c980b9f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.orm.jpa; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; @@ -46,9 +47,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceSchemaCreatedEvent; import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfigurationTests.JpaUsingApplicationListenerConfiguration.EventCapturingApplicationListener; import org.springframework.boot.autoconfigure.orm.jpa.mapping.NonAnnotatedEntity; import org.springframework.boot.autoconfigure.orm.jpa.test.City; import org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration; @@ -57,10 +60,13 @@ import org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform; import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; @@ -383,6 +389,52 @@ public class HibernateJpaAutoConfigurationTests .run((context) -> assertThat(context).doesNotHaveBean(City.class)); } + @Test + public void withSyncBootstrappingAnApplicationListenerThatUsesJpaDoesNotTriggerABeanCurrentlyInCreationException() { + contextRunner() + .withUserConfiguration(JpaUsingApplicationListenerConfiguration.class) + .withPropertyValues("spring.datasource.initialization-mode=never") + .run((context) -> { + assertThat(context).hasNotFailed(); + assertThat(context + .getBean(EventCapturingApplicationListener.class).events + .stream() + .filter(DataSourceSchemaCreatedEvent.class::isInstance)) + .hasSize(1); + }); + } + + @Test + public void withAsyncBootstrappingAnApplicationListenerThatUsesJpaDoesNotTriggerABeanCurrentlyInCreationException() { + contextRunner() + .withUserConfiguration(AsyncBootstrappingConfiguration.class, + JpaUsingApplicationListenerConfiguration.class) + .withPropertyValues("spring.datasource.initialization-mode=never") + .run((context) -> { + assertThat(context).hasNotFailed(); + EventCapturingApplicationListener listener = context + .getBean(EventCapturingApplicationListener.class); + long end = System.currentTimeMillis() + 30000; + while ((System.currentTimeMillis() < end) + && !dataSourceSchemaCreatedEventReceived(listener)) { + Thread.sleep(100); + } + assertThat(listener.events.stream() + .filter(DataSourceSchemaCreatedEvent.class::isInstance)) + .hasSize(1); + }); + } + + private boolean dataSourceSchemaCreatedEventReceived( + EventCapturingApplicationListener listener) { + for (ApplicationEvent event : listener.events) { + if (event instanceof DataSourceSchemaCreatedEvent) { + return true; + } + } + return false; + } + @Configuration @TestAutoConfigurationPackage(City.class) static class TestInitializedJpaConfiguration { @@ -506,4 +558,45 @@ public class HibernateJpaAutoConfigurationTests } + @org.springframework.context.annotation.Configuration + static class JpaUsingApplicationListenerConfiguration { + + @Bean + public EventCapturingApplicationListener jpaUsingApplicationListener( + EntityManagerFactory emf) { + return new EventCapturingApplicationListener(); + } + + static class EventCapturingApplicationListener + implements ApplicationListener { + + private final List events = new ArrayList<>(); + + @Override + public void onApplicationEvent(ApplicationEvent event) { + this.events.add(event); + } + + } + + } + + @Configuration + static class AsyncBootstrappingConfiguration { + + @Bean + ThreadPoolTaskExecutor ThreadPoolTaskExecutor() { + return new ThreadPoolTaskExecutor(); + } + + @Bean + public EntityManagerFactoryBuilderCustomizer asyncBoostrappingCustomizer( + ThreadPoolTaskExecutor executor) { + return (builder) -> { + builder.setBootstrapExecutor(executor); + }; + } + + } + }