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