From 99e6c7c45d75c41e0ba0ee649565a70272ba0af8 Mon Sep 17 00:00:00 2001 From: David Syer Date: Mon, 6 Jun 2011 07:28:25 +0000 Subject: [PATCH] SPR-6717: Added support for database destroy scripts git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4470 50f2f4bb-b051-0410-bef5-90022cba6387 --- ...nitializeDatabaseBeanDefinitionParser.java | 13 ++++- .../init/DataSourceInitializer.java | 47 ++++++++++++++----- .../jdbc/config/spring-jdbc-3.1.xsd | 28 +++++++++-- .../config/JdbcNamespaceIntegrationTest.java | 29 ++++++++++-- .../jdbc/config/jdbc-destroy-config.xml | 16 +++++++ 5 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 org.springframework.jdbc/src/test/resources/org/springframework/jdbc/config/jdbc-destroy-config.xml diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/config/InitializeDatabaseBeanDefinitionParser.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/config/InitializeDatabaseBeanDefinitionParser.java index ce28adbcd5b..2956aa40e50 100644 --- a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/config/InitializeDatabaseBeanDefinitionParser.java +++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/config/InitializeDatabaseBeanDefinitionParser.java @@ -59,11 +59,12 @@ public class InitializeDatabaseBeanDefinitionParser extends AbstractBeanDefiniti private void setDatabasePopulator(Element element, ParserContext context, BeanDefinitionBuilder builder) { List scripts = DomUtils.getChildElementsByTagName(element, "script"); if (scripts.size() > 0) { - builder.addPropertyValue("databasePopulator", createDatabasePopulator(element, scripts, context)); + builder.addPropertyValue("databasePopulator", createDatabasePopulator(element, scripts, context, "INIT")); + builder.addPropertyValue("databaseCleaner", createDatabasePopulator(element, scripts, context, "DESTROY")); } } - private BeanDefinition createDatabasePopulator(Element element, List scripts, ParserContext context) { + private BeanDefinition createDatabasePopulator(Element element, List scripts, ParserContext context, String execution) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CompositeDatabasePopulator.class); boolean ignoreFailedDrops = element.getAttribute("ignore-failures").equals("DROPS"); @@ -72,6 +73,14 @@ public class InitializeDatabaseBeanDefinitionParser extends AbstractBeanDefiniti ManagedList delegates = new ManagedList(); for (Element scriptElement : scripts) { + + String executionAttr = scriptElement.getAttribute("execution"); + if (!StringUtils.hasText(executionAttr)) { + executionAttr = "INIT"; + } + if (!execution.equals(executionAttr)) { + continue; + } BeanDefinitionBuilder delegate = BeanDefinitionBuilder.genericBeanDefinition(ResourceDatabasePopulator.class); delegate.addPropertyValue("ignoreFailedDrops", ignoreFailedDrops); diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/DataSourceInitializer.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/DataSourceInitializer.java index 1e8ae1fbbe8..d7dfd350fd8 100644 --- a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/DataSourceInitializer.java +++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/datasource/init/DataSourceInitializer.java @@ -20,6 +20,7 @@ import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; +import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.util.Assert; @@ -31,14 +32,15 @@ import org.springframework.util.Assert; * @since 3.0 * @see DatabasePopulator */ -public class DataSourceInitializer implements InitializingBean { +public class DataSourceInitializer implements InitializingBean, DisposableBean { private DataSource dataSource; private DatabasePopulator databasePopulator; - private boolean enabled = true; + private DatabasePopulator databaseCleaner; + private boolean enabled = true; /** * The {@link DataSource} to populate when this component is initialized. @@ -58,6 +60,16 @@ public class DataSourceInitializer implements InitializingBean { this.databasePopulator = databasePopulator; } + /** + * Set a script execution to be run in the bean destruction callback, cleaning up the database and leaving it in + * a known state for others. + * + * @param databaseCleaner the database script executor to run on destroy + */ + public void setDatabaseCleaner(DatabasePopulator databaseCleaner) { + this.databaseCleaner = databaseCleaner; + } + /** * Flag to explicitly enable or disable the database populator. * @param enabled true if the database populator will be called on startup @@ -66,30 +78,41 @@ public class DataSourceInitializer implements InitializingBean { this.enabled = enabled; } - /** * Use the populator to set up data in the data source. */ public void afterPropertiesSet() throws Exception { + if (this.databasePopulator != null) { + execute(this.databasePopulator); + } + } + + /** + * Use the populator to clean up data in the data source. + */ + public void destroy() throws Exception { + if (this.databaseCleaner != null) { + execute(this.databaseCleaner); + } + } + + private void execute(DatabasePopulator populator) throws Exception { if (this.enabled) { Assert.state(this.dataSource != null, "DataSource must be provided"); - Assert.state(this.databasePopulator != null, "DatabasePopulator must be provided"); + Assert.state(populator != null, "DatabasePopulator must be provided"); try { Connection connection = this.dataSource.getConnection(); try { - this.databasePopulator.populate(connection); - } - finally { + populator.populate(connection); + } finally { try { connection.close(); - } - catch (SQLException ex) { + } catch (SQLException ex) { // ignore } } - } - catch (Exception ex) { - throw new DataAccessResourceFailureException("Failed to populate database", ex); + } catch (Exception ex) { + throw new DataAccessResourceFailureException("Failed to execute database script", ex); } } } diff --git a/org.springframework.jdbc/src/main/resources/org/springframework/jdbc/config/spring-jdbc-3.1.xsd b/org.springframework.jdbc/src/main/resources/org/springframework/jdbc/config/spring-jdbc-3.1.xsd index 1ba8f9a82a7..39365eb33c3 100644 --- a/org.springframework.jdbc/src/main/resources/org/springframework/jdbc/config/spring-jdbc-3.1.xsd +++ b/org.springframework.jdbc/src/main/resources/org/springframework/jdbc/config/spring-jdbc-3.1.xsd @@ -5,8 +5,10 @@ targetNamespace="http://www.springframework.org/schema/jdbc" elementFormDefault="qualified" attributeFormDefault="unqualified"> - - + + @@ -80,8 +82,10 @@ default="true"> - Is this bean "enabled", meaning the scripts will be executed? - Defaults to true, but can be used to switch on and off the initialization depending on the environment. + Is this bean "enabled", meaning the scripts will + be executed? + Defaults to true, but can be used to switch on and off + the initialization depending on the environment. @@ -89,7 +93,8 @@ default="NONE"> - Should failed SQL statements be ignored during initialization? + Should failed SQL statements be ignored during + initialization? @@ -136,6 +141,19 @@ ]]> + + + + + + + + + + + diff --git a/org.springframework.jdbc/src/test/java/org/springframework/jdbc/config/JdbcNamespaceIntegrationTest.java b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/config/JdbcNamespaceIntegrationTest.java index 0512e91ae3d..08d6d6bfc24 100644 --- a/org.springframework.jdbc/src/test/java/org/springframework/jdbc/config/JdbcNamespaceIntegrationTest.java +++ b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/config/JdbcNamespaceIntegrationTest.java @@ -7,7 +7,9 @@ import static org.junit.Assert.assertThat; import javax.sql.DataSource; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.beans.PropertyValue; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -15,10 +17,15 @@ import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.ClassPathResource; +import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.init.DataSourceInitializer; public class JdbcNamespaceIntegrationTest { + @Rule + public ExpectedException expected = ExpectedException.none(); + @Test public void testCreateEmbeddedDatabase() throws Exception { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( @@ -52,6 +59,22 @@ public class JdbcNamespaceIntegrationTest { context.close(); } + @Test + public void testCreateAndDestroy() throws Exception { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( + "org/springframework/jdbc/config/jdbc-destroy-config.xml"); + try { + DataSource dataSource = context.getBean(DataSource.class); + JdbcTemplate template = new JdbcTemplate(dataSource); + assertEquals(1, template.queryForInt("select count(*) from T_TEST")); + context.getBean(DataSourceInitializer.class).destroy(); + expected.expect(BadSqlGrammarException.class); // Table has been dropped + assertEquals(1, template.queryForInt("select count(*) from T_TEST")); + } finally { + context.close(); + } + } + @Test public void testMultipleDataSourcesHaveDifferentDatabaseNames() throws Exception { @@ -73,14 +96,14 @@ public class JdbcNamespaceIntegrationTest { private void assertCorrectSetup(ConfigurableApplicationContext context, String... dataSources) { assertCorrectSetup(context, 1, dataSources); } - + private void assertCorrectSetup(ConfigurableApplicationContext context, int count, String... dataSources) { try { for (String dataSourceName : dataSources) { DataSource dataSource = context.getBean(dataSourceName, DataSource.class); - JdbcTemplate t = new JdbcTemplate(dataSource); - assertEquals(count, t.queryForInt("select count(*) from T_TEST")); + JdbcTemplate template = new JdbcTemplate(dataSource); + assertEquals(count, template.queryForInt("select count(*) from T_TEST")); } } finally { context.close(); diff --git a/org.springframework.jdbc/src/test/resources/org/springframework/jdbc/config/jdbc-destroy-config.xml b/org.springframework.jdbc/src/test/resources/org/springframework/jdbc/config/jdbc-destroy-config.xml new file mode 100644 index 00000000000..e8cd36088c7 --- /dev/null +++ b/org.springframework.jdbc/src/test/resources/org/springframework/jdbc/config/jdbc-destroy-config.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + +