From 40ffe4169b18304e4c04bdf3db6f09d74aef405f Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 2 Mar 2016 10:51:53 +0000 Subject: [PATCH] Improve DevTools non-embedded in-memory DB shutdown handling Shutdown handling has been improved so that it will run after any EntityManagerFactory beans have been closed. This ensures that Hibernate can, if configured to do so, drop its schema during restart processing. Without this change, a benign exception could be logged if the database was shutdown before Hibernate. Closes gh-5305 --- spring-boot-devtools/pom.xml | 10 +++ .../DevToolsDataSourceAutoConfiguration.java | 21 +++++ ...ToolsDataSourceAutoConfigurationTests.java | 85 +++++++++++++++++-- 3 files changed, 107 insertions(+), 9 deletions(-) diff --git a/spring-boot-devtools/pom.xml b/spring-boot-devtools/pom.xml index 4a3a73e458..db189e46ec 100644 --- a/spring-boot-devtools/pom.xml +++ b/spring-boot-devtools/pom.xml @@ -44,6 +44,16 @@ spring-jdbc true + + org.springframework + spring-orm + true + + + org.hibernate + hibernate-entitymanager + true + org.springframework spring-web diff --git a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java index 1a5c712918..0e76695220 100644 --- a/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java @@ -27,6 +27,8 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.AllNestedConditions; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.data.jpa.EntityManagerFactoryDependsOnPostProcessor; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration.DevToolsDataSourceCondition; @@ -34,6 +36,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; /** * {@link EnableAutoConfiguration Auto-configuration} for DevTools-specific @@ -54,6 +58,23 @@ public class DevToolsDataSourceAutoConfiguration { dataSourceProperties); } + /** + * Additional configuration to ensure that + * {@link javax.persistence.EntityManagerFactory} beans depend on the + * {@code inMemoryDatabaseShutdownExecutor} bean. + */ + @Configuration + @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class) + @ConditionalOnBean(AbstractEntityManagerFactoryBean.class) + static class DatabaseShutdownExecutorJpaDependencyConfiguration + extends EntityManagerFactoryDependsOnPostProcessor { + + DatabaseShutdownExecutorJpaDependencyConfiguration() { + super("inMemoryDatabaseShutdownExecutor"); + } + + } + static final class NonEmbeddedInMemoryDatabaseShutdownExecutor implements DisposableBean { diff --git a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfigurationTests.java b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfigurationTests.java index 2371550a63..700f39509e 100644 --- a/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfigurationTests.java +++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfigurationTests.java @@ -20,9 +20,11 @@ import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; +import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import org.junit.Test; +import org.mockito.InOrder; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -36,6 +38,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -49,7 +52,7 @@ public class DevToolsDataSourceAutoConfigurationTests { @Test public void embeddedDatabaseIsNotShutDown() throws SQLException { - ConfigurableApplicationContext context = createContext("org.h2.Driver", + ConfigurableApplicationContext context = createContextWithDriver("org.h2.Driver", EmbeddedDatabaseConfiguration.class); DataSource dataSource = context.getBean(DataSource.class); context.close(); @@ -58,8 +61,8 @@ public class DevToolsDataSourceAutoConfigurationTests { @Test public void externalDatabaseIsNotShutDown() throws SQLException { - ConfigurableApplicationContext context = createContext("org.postgresql.Driver", - DataSourceConfiguration.class); + ConfigurableApplicationContext context = createContextWithDriver( + "org.postgresql.Driver", DataSourceConfiguration.class); DataSource dataSource = context.getBean(DataSource.class); context.close(); verify(dataSource, times(0)).getConnection(); @@ -67,7 +70,35 @@ public class DevToolsDataSourceAutoConfigurationTests { @Test public void nonEmbeddedInMemoryDatabaseIsShutDown() throws SQLException { - ConfigurableApplicationContext context = createContext("org.h2.Driver", + ConfigurableApplicationContext context = createContextWithDriver("org.h2.Driver", + DataSourceConfiguration.class); + DataSource dataSource = context.getBean(DataSource.class); + Connection connection = mock(Connection.class); + given(dataSource.getConnection()).willReturn(connection); + Statement statement = mock(Statement.class); + given(connection.createStatement()).willReturn(statement); + context.close(); + verify(statement).execute("SHUTDOWN"); + } + + @Test + public void nonEmbeddedInMemoryDatabaseConfiguredWithDriverIsShutDown() + throws SQLException { + ConfigurableApplicationContext context = createContextWithDriver("org.h2.Driver", + DataSourceConfiguration.class); + DataSource dataSource = context.getBean(DataSource.class); + Connection connection = mock(Connection.class); + given(dataSource.getConnection()).willReturn(connection); + Statement statement = mock(Statement.class); + given(connection.createStatement()).willReturn(statement); + context.close(); + verify(statement).execute("SHUTDOWN"); + } + + @Test + public void nonEmbeddedInMemoryDatabaseConfiguredWithUrlIsShutDown() + throws SQLException { + ConfigurableApplicationContext context = createContextWithUrl("jdbc:h2:mem:test", DataSourceConfiguration.class); DataSource dataSource = context.getBean(DataSource.class); Connection connection = mock(Connection.class); @@ -87,13 +118,40 @@ public class DevToolsDataSourceAutoConfigurationTests { is(0)); } - private ConfigurableApplicationContext createContext(String driver, + @Test + public void entityManagerFactoryIsClosedBeforeDatabaseIsShutDown() + throws SQLException { + ConfigurableApplicationContext context = createContextWithUrl("jdbc:h2:mem:test", + DataSourceConfiguration.class, EntityManagerFactoryConfiguration.class); + DataSource dataSource = context.getBean(DataSource.class); + Connection connection = mock(Connection.class); + given(dataSource.getConnection()).willReturn(connection); + Statement statement = mock(Statement.class); + given(connection.createStatement()).willReturn(statement); + EntityManagerFactory entityManagerFactory = context + .getBean(EntityManagerFactory.class); + context.close(); + InOrder inOrder = inOrder(statement, entityManagerFactory); + inOrder.verify(statement).execute("SHUTDOWN"); + inOrder.verify(entityManagerFactory).close(); + } + + private ConfigurableApplicationContext createContextWithDriver(String driver, + Class... classes) { + return createContext("spring.datasource.driver-class-name:" + driver, classes); + } + + private ConfigurableApplicationContext createContextWithUrl(String url, + Class... classes) { + return createContext("spring.datasource.url:" + url, classes); + } + + private ConfigurableApplicationContext createContext(String property, Class... classes) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(classes); context.register(DevToolsDataSourceAutoConfiguration.class); - EnvironmentTestUtils.addEnvironment(context, - "spring.datasource.driver-class-name:" + driver); + EnvironmentTestUtils.addEnvironment(context, property); context.refresh(); return context; } @@ -113,7 +171,7 @@ public class DevToolsDataSourceAutoConfigurationTests { static class DataSourceConfiguration { @Bean - public DataSource in() { + public DataSource dataSource() { return mock(DataSource.class); } @@ -123,10 +181,19 @@ public class DevToolsDataSourceAutoConfigurationTests { static class NoDataSourcePropertiesConfiguration { @Bean - public DataSource in() { + public DataSource dataSource() { return mock(DataSource.class); } } + @Configuration + static class EntityManagerFactoryConfiguration { + + @Bean + public EntityManagerFactory entityManagerFactory() { + return mock(EntityManagerFactory.class); + } + } + }