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
pull/14676/merge
Andy Wilkinson 6 years ago
parent 7cbee701cd
commit b4638b82d0

@ -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);
};
}
}
}

Loading…
Cancel
Save