Prevent ApplicationListener that depends on EMF from creating a cycle
Previously, when an EntityManagerFactory was being initialized synchronously, the DataSourceSchemaCreatedEvent would be published during its initialization. This meant that an application listener that depends on the EntityManagerFactory would create a dependency cycle if it was a potential recipient of the event. For the synchronous case, this commit moves the publication of the event so that it occurs after the initialisation of the entity manager factory. This allows an application listener that is a potential recipient of the DataSourceSchemaCreatedEvent to depend on the EntityManagerFactory without creating a cycle. Closes gh-14651
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<ApplicationEvent> {
|
||||
|
||||
private final List<ApplicationEvent> 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);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user