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
This commit is contained in:
David Syer 2011-06-06 07:28:25 +00:00
parent fff185d975
commit 99e6c7c45d
5 changed files with 111 additions and 22 deletions

View File

@ -59,11 +59,12 @@ public class InitializeDatabaseBeanDefinitionParser extends AbstractBeanDefiniti
private void setDatabasePopulator(Element element, ParserContext context, BeanDefinitionBuilder builder) { private void setDatabasePopulator(Element element, ParserContext context, BeanDefinitionBuilder builder) {
List<Element> scripts = DomUtils.getChildElementsByTagName(element, "script"); List<Element> scripts = DomUtils.getChildElementsByTagName(element, "script");
if (scripts.size() > 0) { 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<Element> scripts, ParserContext context) { private BeanDefinition createDatabasePopulator(Element element, List<Element> scripts, ParserContext context, String execution) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CompositeDatabasePopulator.class); BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CompositeDatabasePopulator.class);
boolean ignoreFailedDrops = element.getAttribute("ignore-failures").equals("DROPS"); boolean ignoreFailedDrops = element.getAttribute("ignore-failures").equals("DROPS");
@ -73,6 +74,14 @@ public class InitializeDatabaseBeanDefinitionParser extends AbstractBeanDefiniti
for (Element scriptElement : scripts) { 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); BeanDefinitionBuilder delegate = BeanDefinitionBuilder.genericBeanDefinition(ResourceDatabasePopulator.class);
delegate.addPropertyValue("ignoreFailedDrops", ignoreFailedDrops); delegate.addPropertyValue("ignoreFailedDrops", ignoreFailedDrops);
delegate.addPropertyValue("continueOnError", continueOnError); delegate.addPropertyValue("continueOnError", continueOnError);

View File

@ -20,6 +20,7 @@ import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -31,14 +32,15 @@ import org.springframework.util.Assert;
* @since 3.0 * @since 3.0
* @see DatabasePopulator * @see DatabasePopulator
*/ */
public class DataSourceInitializer implements InitializingBean { public class DataSourceInitializer implements InitializingBean, DisposableBean {
private DataSource dataSource; private DataSource dataSource;
private DatabasePopulator databasePopulator; private DatabasePopulator databasePopulator;
private boolean enabled = true; private DatabasePopulator databaseCleaner;
private boolean enabled = true;
/** /**
* The {@link DataSource} to populate when this component is initialized. * The {@link DataSource} to populate when this component is initialized.
@ -58,6 +60,16 @@ public class DataSourceInitializer implements InitializingBean {
this.databasePopulator = databasePopulator; 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. * Flag to explicitly enable or disable the database populator.
* @param enabled true if the database populator will be called on startup * @param enabled true if the database populator will be called on startup
@ -66,30 +78,41 @@ public class DataSourceInitializer implements InitializingBean {
this.enabled = enabled; this.enabled = enabled;
} }
/** /**
* Use the populator to set up data in the data source. * Use the populator to set up data in the data source.
*/ */
public void afterPropertiesSet() throws Exception { 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) { if (this.enabled) {
Assert.state(this.dataSource != null, "DataSource must be provided"); 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 { try {
Connection connection = this.dataSource.getConnection(); Connection connection = this.dataSource.getConnection();
try { try {
this.databasePopulator.populate(connection); populator.populate(connection);
} } finally {
finally {
try { try {
connection.close(); connection.close();
} } catch (SQLException ex) {
catch (SQLException ex) {
// ignore // ignore
} }
} }
} } catch (Exception ex) {
catch (Exception ex) { throw new DataAccessResourceFailureException("Failed to execute database script", ex);
throw new DataAccessResourceFailureException("Failed to populate database", ex);
} }
} }
} }

View File

@ -5,8 +5,10 @@
targetNamespace="http://www.springframework.org/schema/jdbc" targetNamespace="http://www.springframework.org/schema/jdbc"
elementFormDefault="qualified" attributeFormDefault="unqualified"> elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-3.1.xsd" /> <xsd:import namespace="http://www.springframework.org/schema/beans"
<xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-3.1.xsd" /> schemaLocation="http://www.springframework.org/schema/beans/spring-beans-3.1.xsd" />
<xsd:import namespace="http://www.springframework.org/schema/tool"
schemaLocation="http://www.springframework.org/schema/tool/spring-tool-3.1.xsd" />
<xsd:element name="embedded-database"> <xsd:element name="embedded-database">
<xsd:annotation> <xsd:annotation>
@ -80,8 +82,10 @@
default="true"> default="true">
<xsd:annotation> <xsd:annotation>
<xsd:documentation> <xsd:documentation>
Is this bean "enabled", meaning the scripts will be executed? Is this bean "enabled", meaning the scripts will
Defaults to true, but can be used to switch on and off the initialization depending on the environment. be executed?
Defaults to true, but can be used to switch on and off
the initialization depending on the environment.
</xsd:documentation> </xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
@ -89,7 +93,8 @@
default="NONE"> default="NONE">
<xsd:annotation> <xsd:annotation>
<xsd:documentation> <xsd:documentation>
Should failed SQL statements be ignored during initialization? Should failed SQL statements be ignored during
initialization?
</xsd:documentation> </xsd:documentation>
</xsd:annotation> </xsd:annotation>
<xsd:simpleType> <xsd:simpleType>
@ -136,6 +141,19 @@
]]></xsd:documentation> ]]></xsd:documentation>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
<xsd:attribute name="execution">
<xsd:annotation>
<xsd:documentation><![CDATA[
Indicate the timing of the execution of this script. Use INIT to execute on startup (as a bean initialization) and DESTROY to execute on shutdown (as a bean destruction callback).
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="INIT" />
<xsd:enumeration value="DESTROY" />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType> </xsd:complexType>
<xsd:simpleType name="databaseType"> <xsd:simpleType name="databaseType">

View File

@ -7,7 +7,9 @@ import static org.junit.Assert.assertThat;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.PropertyValue; import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; 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.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
public class JdbcNamespaceIntegrationTest { public class JdbcNamespaceIntegrationTest {
@Rule
public ExpectedException expected = ExpectedException.none();
@Test @Test
public void testCreateEmbeddedDatabase() throws Exception { public void testCreateEmbeddedDatabase() throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
@ -52,6 +59,22 @@ public class JdbcNamespaceIntegrationTest {
context.close(); 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 @Test
public void testMultipleDataSourcesHaveDifferentDatabaseNames() throws Exception { public void testMultipleDataSourcesHaveDifferentDatabaseNames() throws Exception {
@ -79,8 +102,8 @@ public class JdbcNamespaceIntegrationTest {
try { try {
for (String dataSourceName : dataSources) { for (String dataSourceName : dataSources) {
DataSource dataSource = context.getBean(dataSourceName, DataSource.class); DataSource dataSource = context.getBean(dataSourceName, DataSource.class);
JdbcTemplate t = new JdbcTemplate(dataSource); JdbcTemplate template = new JdbcTemplate(dataSource);
assertEquals(count, t.queryForInt("select count(*) from T_TEST")); assertEquals(count, template.queryForInt("select count(*) from T_TEST"));
} }
} finally { } finally {
context.close(); context.close();

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd">
<jdbc:embedded-database id="dataSource" type="HSQL"/>
<jdbc:initialize-database data-source="dataSource">
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql" execution="INIT"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data.sql" execution="INIT"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-drops.sql" execution="DESTROY"/>
</jdbc:initialize-database>
</beans>