From a8f4708ffb50066a9a1a032d6c5ac65443c7ff0b Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 11 Feb 2016 15:55:25 +0000 Subject: [PATCH] Shut down in-memory database when DevTools restarts the context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- spring-boot-devtools/pom.xml | 14 +++ .../DevToolsDataSourceAutoConfiguration.java | 88 ++++++++++++++ .../main/resources/META-INF/spring.factories | 1 + ...ToolsDataSourceAutoConfigurationTests.java | 111 ++++++++++++++++++ 4 files changed, 214 insertions(+) create mode 100644 spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java create mode 100644 spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfigurationTests.java diff --git a/spring-boot-devtools/pom.xml b/spring-boot-devtools/pom.xml index 9ba0d885ac..2910d050f2 100644 --- a/spring-boot-devtools/pom.xml +++ b/spring-boot-devtools/pom.xml @@ -39,6 +39,11 @@ log4j-core true + + org.springframework + spring-jdbc + true + org.springframework spring-web @@ -71,6 +76,11 @@ true + + com.h2database + h2 + test + org.springframework spring-webmvc @@ -92,6 +102,10 @@ ${jetty.version} test + + org.postgresql + postgresql + org.springframework.boot spring-boot-starter-thymeleaf 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 new file mode 100644 index 0000000000..549e6e14de --- /dev/null +++ b/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfiguration.java @@ -0,0 +1,88 @@ +/* + * 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 IN_MEMORY_DRIVER_CLASS_NAMES = new HashSet( + 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)); + } + + } + +} diff --git a/spring-boot-devtools/src/main/resources/META-INF/spring.factories b/spring-boot-devtools/src/main/resources/META-INF/spring.factories index fc66a3dcdb..6afcc57299 100644 --- a/spring-boot-devtools/src/main/resources/META-INF/spring.factories +++ b/spring-boot-devtools/src/main/resources/META-INF/spring.factories @@ -8,6 +8,7 @@ org.springframework.boot.devtools.restart.RestartApplicationListener # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration,\ org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration,\ org.springframework.boot.devtools.autoconfigure.RemoteDevToolsAutoConfiguration 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 new file mode 100644 index 0000000000..57c5a340d4 --- /dev/null +++ b/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/DevToolsDataSourceAutoConfigurationTests.java @@ -0,0 +1,111 @@ +/* + * 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); + } + + } + +}