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
pull/5489/head
Andy Wilkinson 9 years ago
parent c6c001457a
commit 40ffe4169b

@ -44,6 +44,16 @@
<artifactId>spring-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>

@ -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 {

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

Loading…
Cancel
Save