diff --git a/spring-boot-devtools/pom.xml b/spring-boot-devtools/pom.xml index 0a98a1bc00..f38d55e3ae 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..32b1ed6363 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 { @@ -82,7 +103,7 @@ public class DevToolsDataSourceAutoConfiguration { private boolean dataSourceRequiresShutdown() { return IN_MEMORY_DRIVER_CLASS_NAMES - .contains(this.dataSourceProperties.getDriverClassName()) + .contains(this.dataSourceProperties.determineDriverClassName()) && (!(this.dataSource instanceof EmbeddedDatabase)); } 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 5b0b21e63f..95225a79fd 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; @@ -35,6 +37,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import static org.assertj.core.api.Assertions.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; @@ -48,7 +51,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(); @@ -57,8 +60,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(); @@ -66,7 +69,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); @@ -85,13 +116,40 @@ public class DevToolsDataSourceAutoConfigurationTests { .isEmpty(); } - 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; } @@ -111,7 +169,7 @@ public class DevToolsDataSourceAutoConfigurationTests { static class DataSourceConfiguration { @Bean - public DataSource in() { + public DataSource dataSource() { return mock(DataSource.class); } @@ -121,10 +179,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); + } + } + }