Commit a8f4708f authored by Andy Wilkinson's avatar Andy Wilkinson

Shut down in-memory database when DevTools restarts the context

Previously an in-memory database that wasn’t pooled (an
EmbeddedDatabase) would be shutdown when the context restarted, but
an in-memory database wrapped in a connection pool was not. This meant
that the former would be be wiped clean after each restart, whereas the
latter would not. In addition to being inconsistent, this also
caused problems with schema.sql and data.sql scripts when using
DevTools. If you were using an in-memory database wrapped in a
connection pool, a failure may occur during a restart as the scripts
were not being run against in clean database.

This commit adds an auto-configured bean to DevTools that, when the
context is being closed, will execute “SHUTDOWN” if it identifies that
the DataSource is not an EmbeddedDatabase and is for an in-memory
database.

Closes gh-4699
parent abf9d66e
...@@ -39,6 +39,11 @@ ...@@ -39,6 +39,11 @@
<artifactId>log4j-core</artifactId> <artifactId>log4j-core</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId> <artifactId>spring-web</artifactId>
...@@ -71,6 +76,11 @@ ...@@ -71,6 +76,11 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!-- Test --> <!-- Test -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId> <artifactId>spring-webmvc</artifactId>
...@@ -92,6 +102,10 @@ ...@@ -92,6 +102,10 @@
<version>${jetty.version}</version> <version>${jetty.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId> <artifactId>spring-boot-starter-thymeleaf</artifactId>
......
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.devtools.autoconfigure;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.sql.DataSource;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
/**
* {@link EnableAutoConfiguration Auto-configuration} for DevTools-specific
* {@link DataSource} configuration.
*
* @author Andy Wilkinson
* @since 1.3.3
*/
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@ConditionalOnBean({ DataSource.class, DataSourceProperties.class })
@Configuration
public class DevToolsDataSourceAutoConfiguration {
@Bean
NonEmbeddedInMemoryDatabaseShutdownExecutor inMemoryDatabaseShutdownExecutor(
DataSource dataSource, DataSourceProperties dataSourceProperties) {
return new NonEmbeddedInMemoryDatabaseShutdownExecutor(dataSource,
dataSourceProperties);
}
static final class NonEmbeddedInMemoryDatabaseShutdownExecutor
implements DisposableBean {
private static final Set<String> IN_MEMORY_DRIVER_CLASS_NAMES = new HashSet<String>(
Arrays.asList("org.apache.derby.jdbc.EmbeddedDriver", "org.h2.Driver",
"org.h2.jdbcx.JdbcDataSource", "org.hsqldb.jdbcDriver",
"org.hsqldb.jdbc.JDBCDriver",
"org.hsqldb.jdbc.pool.JDBCXADataSource"));
private final DataSource dataSource;
private final DataSourceProperties dataSourceProperties;
public NonEmbeddedInMemoryDatabaseShutdownExecutor(DataSource dataSource,
DataSourceProperties dataSourceProperties) {
this.dataSource = dataSource;
this.dataSourceProperties = dataSourceProperties;
}
@Override
public void destroy() throws Exception {
if (dataSourceRequiresShutdown()) {
this.dataSource.getConnection().createStatement().execute("SHUTDOWN");
}
}
private boolean dataSourceRequiresShutdown() {
return IN_MEMORY_DRIVER_CLASS_NAMES
.contains(this.dataSourceProperties.getDriverClassName())
&& (!(this.dataSource instanceof EmbeddedDatabase));
}
}
}
...@@ -8,6 +8,7 @@ org.springframework.boot.devtools.restart.RestartApplicationListener ...@@ -8,6 +8,7 @@ org.springframework.boot.devtools.restart.RestartApplicationListener
# Auto Configure # Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration,\
org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration,\ org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration,\
org.springframework.boot.devtools.autoconfigure.RemoteDevToolsAutoConfiguration org.springframework.boot.devtools.autoconfigure.RemoteDevToolsAutoConfiguration
......
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.devtools.autoconfigure;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import org.junit.Test;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link DevToolsDataSourceAutoConfiguration}.
*
* @author Andy Wilkinson
*/
public class DevToolsDataSourceAutoConfigurationTests {
@Test
public void embeddedDatabaseIsNotShutDown() throws SQLException {
ConfigurableApplicationContext context = createContext("org.h2.Driver",
EmbeddedDatabaseConfiguration.class);
DataSource dataSource = context.getBean(DataSource.class);
context.close();
verify(dataSource, times(0)).getConnection();
}
@Test
public void externalDatabaseIsNotShutDown() throws SQLException {
ConfigurableApplicationContext context = createContext("org.postgresql.Driver",
DataSourceConfiguration.class);
DataSource dataSource = context.getBean(DataSource.class);
context.close();
verify(dataSource, times(0)).getConnection();
}
@Test
public void nonEmbeddedInMemoryDatabaseIsShutDown() throws SQLException {
ConfigurableApplicationContext context = createContext("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");
}
private ConfigurableApplicationContext createContext(String driver,
Class<?>... classes) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(classes);
context.register(DevToolsDataSourceAutoConfiguration.class);
EnvironmentTestUtils.addEnvironment(context,
"spring.datasource.driver-class-name:" + driver);
context.refresh();
return context;
}
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
static class EmbeddedDatabaseConfiguration {
@Bean
public EmbeddedDatabase embeddedDatabase() {
return mock(EmbeddedDatabase.class);
}
}
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
static class DataSourceConfiguration {
@Bean
public DataSource in() {
return mock(DataSource.class);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment