jdbc:script's "separator" and "execution" attributes work nested with embedded-database as well; added "encoding" attribute to jdbc:script element; general revision of DatabasePopulator configuration and execution code

This commit is contained in:
Juergen Hoeller 2011-07-28 14:19:53 +00:00
parent 1baf2a3981
commit bbcf358a9d
19 changed files with 409 additions and 300 deletions

View File

@ -0,0 +1,84 @@
/*
* Copyright 2002-2011 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.jdbc.config;
import java.util.Arrays;
import java.util.List;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.jdbc.datasource.init.CompositeDatabasePopulator;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* @author Juergen Hoeller
* @since 3.1
*/
class DatabasePopulatorConfigUtils {
public static void setDatabasePopulator(Element element, BeanDefinitionBuilder builder) {
List<Element> scripts = DomUtils.getChildElementsByTagName(element, "script");
if (scripts.size() > 0) {
builder.addPropertyValue("databasePopulator", createDatabasePopulator(element, scripts, "INIT"));
builder.addPropertyValue("databaseCleaner", createDatabasePopulator(element, scripts, "DESTROY"));
}
}
static private BeanDefinition createDatabasePopulator(Element element, List<Element> scripts, String execution) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CompositeDatabasePopulator.class);
boolean ignoreFailedDrops = element.getAttribute("ignore-failures").equals("DROPS");
boolean continueOnError = element.getAttribute("ignore-failures").equals("ALL");
ManagedList<BeanMetadataElement> delegates = new ManagedList<BeanMetadataElement>();
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);
delegate.addPropertyValue("continueOnError", continueOnError);
List<String> locations = Arrays.asList(scriptElement.getAttribute("location"));
// Use a factory bean for the resources so they can be given an order if a pattern is used
BeanDefinitionBuilder resourcesFactory = BeanDefinitionBuilder.genericBeanDefinition(SortedResourcesFactoryBean.class);
resourcesFactory.addConstructorArgValue(locations);
delegate.addPropertyValue("scripts", resourcesFactory.getBeanDefinition());
if (StringUtils.hasLength(scriptElement.getAttribute("encoding"))) {
delegate.addPropertyValue("sqlScriptEncoding", scriptElement.getAttribute("encoding"));
}
if (StringUtils.hasLength(scriptElement.getAttribute("separator"))) {
delegate.addPropertyValue("separator", scriptElement.getAttribute("separator"));
}
delegates.add(delegate.getBeanDefinition());
}
builder.addPropertyValue("populators", delegates);
return builder.getBeanDefinition();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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.
@ -16,8 +16,7 @@
package org.springframework.jdbc.config;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@ -27,35 +26,32 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactoryBean;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses {@code embedded-database} element and
* creates a {@link BeanDefinition} for {@link EmbeddedDatabaseFactoryBean}. Picks up nested {@code script} elements and
* configures a {@link ResourceDatabasePopulator} for them.
*
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses an {@code embedded-database}
* element and creates a {@link BeanDefinition} for {@link EmbeddedDatabaseFactoryBean}. Picks up nested
* {@code script} elements and configures a {@link ResourceDatabasePopulator} for them.
*
* @author Oliver Gierke
* @author Juergen Hoeller
* @since 3.0
*/
class EmbeddedDatabaseBeanDefinitionParser extends AbstractBeanDefinitionParser {
private static final String NAME_PROPERTY = "databaseName";
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext context) {
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(EmbeddedDatabaseFactoryBean.class);
setDatabaseType(element, builder);
setDatabasePopulator(element, context, builder);
DatabasePopulatorConfigUtils.setDatabasePopulator(element, builder);
useIdAsDatabaseNameIfGiven(element, builder);
return getSourcedBeanDefinition(builder, element, context);
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
return builder.getBeanDefinition();
}
private void useIdAsDatabaseNameIfGiven(Element element, BeanDefinitionBuilder builder) {
String id = element.getAttribute(ID_ATTRIBUTE);
if (StringUtils.hasText(id)) {
builder.addPropertyValue(NAME_PROPERTY, id);
builder.addPropertyValue("databaseName", id);
}
}
@ -66,34 +62,4 @@ class EmbeddedDatabaseBeanDefinitionParser extends AbstractBeanDefinitionParser
}
}
private void setDatabasePopulator(Element element, ParserContext context, BeanDefinitionBuilder builder) {
List<Element> scripts = DomUtils.getChildElementsByTagName(element, "script");
if (scripts.size() > 0) {
builder.addPropertyValue("databasePopulator", createDatabasePopulator(scripts, context));
}
}
private BeanDefinition createDatabasePopulator(List<Element> scripts, ParserContext context) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ResourceDatabasePopulator.class);
List<String> locations = new ArrayList<String>();
for (Element scriptElement : scripts) {
locations.add(scriptElement.getAttribute("location"));
}
// Use a factory bean for the resources so they can be given an order if a pattern is used
BeanDefinitionBuilder resourcesFactory = BeanDefinitionBuilder
.genericBeanDefinition(SortedResourcesFactoryBean.class);
resourcesFactory.addConstructorArgValue(locations);
builder.addPropertyValue("scripts", resourcesFactory.getBeanDefinition());
return builder.getBeanDefinition();
}
private AbstractBeanDefinition getSourcedBeanDefinition(
BeanDefinitionBuilder builder, Element source, ParserContext context) {
AbstractBeanDefinition definition = builder.getBeanDefinition();
definition.setSource(context.extractSource(source));
return definition;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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.
@ -16,39 +16,35 @@
package org.springframework.jdbc.config;
import java.util.Arrays;
import java.util.List;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.jdbc.datasource.init.CompositeDatabasePopulator;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses an {@code initialize-database} element and
* creates a {@link BeanDefinition} of type {@link DataSourceInitializer}. Picks up nested {@code script} elements and
* configures a {@link ResourceDatabasePopulator} for them.
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses an {@code initialize-database}
* element and creates a {@link BeanDefinition} of type {@link DataSourceInitializer}. Picks up nested
* {@code script} elements and configures a {@link ResourceDatabasePopulator} for them.
*
* @author Dave Syer
* @author Juergen Hoeller
* @since 3.0
*/
public class InitializeDatabaseBeanDefinitionParser extends AbstractBeanDefinitionParser {
class InitializeDatabaseBeanDefinitionParser extends AbstractBeanDefinitionParser {
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext context) {
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(DataSourceInitializer.class);
builder.addPropertyReference("dataSource", element.getAttribute("data-source"));
builder.addPropertyValue("enabled", element.getAttribute("enabled"));
setDatabasePopulator(element, context, builder);
return getSourcedBeanDefinition(builder, element, context);
DatabasePopulatorConfigUtils.setDatabasePopulator(element, builder);
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
return builder.getBeanDefinition();
}
@Override
@ -56,62 +52,4 @@ public class InitializeDatabaseBeanDefinitionParser extends AbstractBeanDefiniti
return true;
}
private void setDatabasePopulator(Element element, ParserContext context, BeanDefinitionBuilder builder) {
List<Element> scripts = DomUtils.getChildElementsByTagName(element, "script");
if (scripts.size() > 0) {
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, String execution) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CompositeDatabasePopulator.class);
boolean ignoreFailedDrops = element.getAttribute("ignore-failures").equals("DROPS");
boolean continueOnError = element.getAttribute("ignore-failures").equals("ALL");
ManagedList<BeanMetadataElement> delegates = new ManagedList<BeanMetadataElement>();
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);
delegate.addPropertyValue("continueOnError", continueOnError);
List<String> locations = Arrays.asList(scriptElement.getAttribute("location"));
// Use a factory bean for the resources so they can be given an order if a pattern is used
BeanDefinitionBuilder resourcesFactory = BeanDefinitionBuilder
.genericBeanDefinition(SortedResourcesFactoryBean.class);
resourcesFactory.addConstructorArgValue(locations);
delegate.addPropertyValue("scripts", resourcesFactory.getBeanDefinition());
if (StringUtils.hasLength(scriptElement.getAttribute("separator"))) {
delegate.addPropertyValue("separator", scriptElement.getAttribute("separator"));
}
delegates.add(delegate.getBeanDefinition());
}
builder.addPropertyValue("populators", delegates);
return builder.getBeanDefinition();
}
private AbstractBeanDefinition getSourcedBeanDefinition(BeanDefinitionBuilder builder, Element source,
ParserContext context) {
AbstractBeanDefinition definition = builder.getBeanDefinition();
definition.setSource(context.extractSource(source));
return definition;
}
}

View File

@ -25,8 +25,8 @@ import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.util.Assert;
/**
@ -69,6 +69,15 @@ public class EmbeddedDatabaseFactory {
this.databaseName = databaseName;
}
/**
* Set the factory to use to create the DataSource instance that connects to the embedded database.
* <p>Defaults to {@link SimpleDriverDataSourceFactory}.
*/
public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
Assert.notNull(dataSourceFactory, "DataSourceFactory is required");
this.dataSourceFactory = dataSourceFactory;
}
/**
* Set the type of embedded database to use. Call this when you wish to configure
* one of the pre-supported types. Defaults to HSQL.
@ -80,33 +89,20 @@ public class EmbeddedDatabaseFactory {
/**
* Set the strategy that will be used to configure the embedded database instance.
* Call this when you wish to use an embedded database type not already supported.
* @param configurer the embedded database configurer
* <p>Call this when you wish to use an embedded database type not already supported.
*/
public void setDatabaseConfigurer(EmbeddedDatabaseConfigurer configurer) {
Assert.notNull(configurer, "EmbeddedDatabaseConfigurer is required");
this.databaseConfigurer = configurer;
}
/**
* Set the strategy that will be used to populate the embedded database. Defaults to null.
* @param populator the database populator
* @see org.springframework.jdbc.datasource.init.DataSourceInitializer#setDatabasePopulator
*/
public void setDatabasePopulator(DatabasePopulator populator) {
Assert.notNull(populator, "DatabasePopulator is required");
this.databasePopulator = populator;
}
/**
* Set the factory to use to create the DataSource instance that connects to the embedded database.
* Defaults to {@link SimpleDriverDataSourceFactory}.
* @param dataSourceFactory the data source factory
*/
public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
Assert.notNull(dataSourceFactory, "DataSourceFactory is required");
this.dataSourceFactory = dataSourceFactory;
}
/**
* Factory method that returns the embedded database instance.
*/
@ -137,7 +133,7 @@ public class EmbeddedDatabaseFactory {
// Now populate the database
if (this.databasePopulator != null) {
try {
populateDatabase();
DatabasePopulatorUtils.execute(this.databasePopulator, this.dataSource);
}
catch (RuntimeException ex) {
// failed to populate, so leave it as not initialized
@ -147,35 +143,6 @@ public class EmbeddedDatabaseFactory {
}
}
private void populateDatabase() {
try {
Connection connection = this.dataSource.getConnection();
try {
this.databasePopulator.populate(connection);
}
finally {
try {
connection.close();
}
catch (SQLException ex) {
// ignore
}
}
}
catch (Exception ex) {
throw new DataAccessResourceFailureException("Failed to populate database", ex);
}
}
/**
* Hook that gets the DataSource that provides the connectivity to the embedded database.
* <p>Returns null if the DataSource has not been initialized or the database has been shut down.
* Subclasses may call to access the datasource instance directly.
*/
protected DataSource getDataSource() {
return this.dataSource;
}
/**
* Hook to shutdown the embedded database. Subclasses may call to force shutdown.
* After calling, {@link #getDataSource()} returns null. Does nothing if no embedded database has been initialized.
@ -187,6 +154,15 @@ public class EmbeddedDatabaseFactory {
}
}
/**
* Hook that gets the DataSource that provides the connectivity to the embedded database.
* <p>Returns <code>null</code> if the DataSource has not been initialized or the database
* has been shut down. Subclasses may call to access the DataSource instance directly.
*/
protected final DataSource getDataSource() {
return this.dataSource;
}
private class EmbeddedDataSourceProxy implements EmbeddedDatabase {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2011 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.
@ -21,6 +21,8 @@ import javax.sql.DataSource;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
/**
* A subclass of {@link EmbeddedDatabaseFactory} that implements {@link FactoryBean} for registration as a Spring bean.
@ -32,15 +34,38 @@ import org.springframework.beans.factory.InitializingBean;
* <p>Implements DisposableBean to shutdown the embedded database when the managing Spring container is shutdown.
*
* @author Keith Donald
* @author Juergen Hoeller
* @since 3.0
*/
public class EmbeddedDatabaseFactoryBean extends EmbeddedDatabaseFactory
implements FactoryBean<DataSource>, InitializingBean, DisposableBean {
private DatabasePopulator databaseCleaner;
/**
* 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
* @see #setDatabasePopulator
* @see org.springframework.jdbc.datasource.init.DataSourceInitializer#setDatabaseCleaner
*/
public void setDatabaseCleaner(DatabasePopulator databaseCleaner) {
this.databaseCleaner = databaseCleaner;
}
public void afterPropertiesSet() {
initDatabase();
}
public void destroy() {
if (this.databaseCleaner != null) {
DatabasePopulatorUtils.execute(this.databaseCleaner, getDataSource());
}
shutdownDatabase();
}
public DataSource getObject() {
return getDataSource();
}
@ -53,8 +78,4 @@ public class EmbeddedDatabaseFactoryBean extends EmbeddedDatabaseFactory
return true;
}
public void destroy() {
shutdownDatabase();
}
}

View File

@ -2,7 +2,6 @@
/**
*
* Provides extensible support for creating embedded database instances.
* HSQL in-memory support provided natively
*
*/
package org.springframework.jdbc.datasource.embedded;

View File

@ -18,38 +18,40 @@ package org.springframework.jdbc.datasource.init;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* {@link DatabasePopulator} implementation that delegates to a list of other implementations, executing alll scripts.
*
* @author Dave Syer
* {@link DatabasePopulator} implementation that delegates to a list of other
* DatabasePopulator implementations, executing all scripts.
*
* @author Dave Syer
* @author Juergen Hoeller
* @since 3.1
*/
public class CompositeDatabasePopulator implements DatabasePopulator {
private List<DatabasePopulator> populators = new ArrayList<DatabasePopulator>();
/**
* @param populators the populators to set
* Specify a list of populators to delegate to.
*/
public void setPopulators(List<DatabasePopulator> populators) {
public void setPopulators(DatabasePopulator... populators) {
this.populators.clear();
this.populators.addAll(populators);
this.populators.addAll(Arrays.asList(populators));
}
/**
* @param populator the populator to add
* Add a populator to the list of delegates.
*/
public void addPopulator(DatabasePopulator populator) {
this.populators.add(populator);
public void addPopulators(DatabasePopulator... populators) {
this.populators.addAll(Arrays.asList(populators));
}
/* (non-Javadoc)
* @see org.springframework.jdbc.datasource.init.DatabasePopulator#populate(java.sql.Connection)
*/
public void populate(Connection connection) throws SQLException {
for (DatabasePopulator populator : populators) {
for (DatabasePopulator populator : this.populators) {
populator.populate(connection);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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.
@ -16,14 +16,10 @@
package org.springframework.jdbc.datasource.init;
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;
/**
* Used to populate a database during initialization.
@ -42,6 +38,7 @@ public class DataSourceInitializer implements InitializingBean, DisposableBean {
private boolean enabled = true;
/**
* The {@link DataSource} to populate when this component is initialized.
* Mandatory with no default.
@ -61,9 +58,8 @@ public class DataSourceInitializer implements InitializingBean, DisposableBean {
}
/**
* 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.
*
* 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) {
@ -78,42 +74,22 @@ public class DataSourceInitializer implements InitializingBean, DisposableBean {
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);
public void afterPropertiesSet() {
if (this.databasePopulator != null && this.enabled) {
DatabasePopulatorUtils.execute(this.databasePopulator, this.dataSource);
}
}
/**
* 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(populator != null, "DatabasePopulator must be provided");
try {
Connection connection = this.dataSource.getConnection();
try {
populator.populate(connection);
} finally {
try {
connection.close();
} catch (SQLException ex) {
// ignore
}
}
} catch (Exception ex) {
throw new DataAccessResourceFailureException("Failed to execute database script", ex);
}
public void destroy() {
if (this.databaseCleaner != null && this.enabled) {
DatabasePopulatorUtils.execute(this.databaseCleaner, this.dataSource);
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2002-2011 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.jdbc.datasource.init;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.util.Assert;
/**
* Utility methods for executing a DatabasePopulator.
*
* @author Juergen Hoeller
* @since 3.1
*/
public abstract class DatabasePopulatorUtils {
/**
* Execute the given DatabasePopulator against the given DataSource.
* @param populator the DatabasePopulator to execute
* @param dataSource the DataSource to execute against
*/
public static void execute(DatabasePopulator populator, DataSource dataSource) {
Assert.notNull(populator, "DatabasePopulator must be provided");
Assert.notNull(dataSource, "DataSource must be provided");
try {
Connection connection = dataSource.getConnection();
try {
populator.populate(connection);
}
finally {
try {
connection.close();
}
catch (SQLException ex) {
// ignore
}
}
}
catch (Exception ex) {
throw new DataAccessResourceFailureException("Failed to execute database script", ex);
}
}
}

View File

@ -55,14 +55,14 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
private String sqlScriptEncoding;
private String separator;
private String commentPrefix = DEFAULT_COMMENT_PREFIX;
private boolean continueOnError = false;
private boolean ignoreFailedDrops = false;
private String separator = null;
/**
* Add a script to execute to populate the database.
@ -90,6 +90,13 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
this.sqlScriptEncoding = sqlScriptEncoding;
}
/**
* Specify the statement separator, if a custom one.
*/
public void setSeparator(String separator) {
this.separator = separator;
}
/**
* Set the line prefix that identifies comments in the SQL script.
* Default is "--".
@ -116,13 +123,6 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
this.ignoreFailedDrops = ignoreFailedDrops;
}
/**
* Specify the statement separator, if a custom one.
*/
public void setSeparator(String separator) {
this.separator = separator;
}
public void populate(Connection connection) throws SQLException {
for (Resource script : this.scripts) {

View File

@ -0,0 +1,8 @@
/**
*
* Provides extensible support for initializing databases through scripts.
*
*/
package org.springframework.jdbc.datasource.init;

View File

@ -18,7 +18,7 @@
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="javax.sql.DataSource" />
<tool:exports type="javax.sql.DataSource"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
@ -26,8 +26,7 @@
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
<xsd:element name="script" type="scriptType"
minOccurs="0" maxOccurs="unbounded">
<xsd:element name="script" type="scriptType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
A SQL script to execute during embedded database initialization.
@ -35,8 +34,7 @@
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="type" type="databaseType"
default="HSQL">
<xsd:attribute name="type" type="databaseType" default="HSQL">
<xsd:annotation>
<xsd:documentation><![CDATA[
The type of embedded database to create, such as HSQL, H2 or Derby. Defaults to HSQL.
@ -50,36 +48,32 @@
<xsd:element name="initialize-database">
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.jdbc.embedded.DataSourceInitializer"><![CDATA[
<xsd:documentation source="java:org.springframework.jdbc.embedded.DataSourceInitializer"><![CDATA[
Initializes a database instance with SQL scripts provided in nested <script/> elements.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="script" type="scriptType" minOccurs="1"
maxOccurs="unbounded">
<xsd:element name="script" type="scriptType" minOccurs="1" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
A SQL script to execute during database initialization.
]]></xsd:documentation>
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="data-source" type="xsd:string"
default="dataSource">
<xsd:attribute name="data-source" type="xsd:string" default="dataSource">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to a data source that should be initialized. Defaults to "dataSource".
]]></xsd:documentation>
A reference to a data source that should be initialized. Defaults to "dataSource".
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref" />
<tool:expected-type type="javax.sql.DataSource" />
<tool:annotation kind="ref"/>
<tool:expected-type type="javax.sql.DataSource"/>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="enabled" type="xsd:string" use="optional"
default="true">
<xsd:attribute name="enabled" type="xsd:string" use="optional" default="true">
<xsd:annotation>
<xsd:documentation>
Is this bean "enabled", meaning the scripts will be executed?
@ -87,8 +81,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ignore-failures" use="optional"
default="NONE">
<xsd:attribute name="ignore-failures" use="optional" default="NONE">
<xsd:annotation>
<xsd:documentation>
Should failed SQL statements be ignored during initialization?
@ -159,4 +152,4 @@
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
</xsd:schema>

View File

@ -12,13 +12,12 @@
<xsd:element name="embedded-database">
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.jdbc.embedded.EmbeddedDataSourceFactoryBean"><![CDATA[
<xsd:documentation source="java:org.springframework.jdbc.embedded.EmbeddedDataSourceFactoryBean"><![CDATA[
Creates an embedded database instance and makes it available to other beans as a javax.sql.DataSource.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="javax.sql.DataSource" />
<tool:exports type="javax.sql.DataSource"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
@ -26,8 +25,7 @@
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
<xsd:element name="script" type="scriptType"
minOccurs="0" maxOccurs="unbounded">
<xsd:element name="script" type="scriptType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
A SQL script to execute during embedded database initialization.
@ -35,8 +33,7 @@
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="type" type="databaseType"
default="HSQL">
<xsd:attribute name="type" type="databaseType" default="HSQL">
<xsd:annotation>
<xsd:documentation><![CDATA[
The type of embedded database to create, such as HSQL, H2 or Derby. Defaults to HSQL.
@ -50,51 +47,43 @@
<xsd:element name="initialize-database">
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.jdbc.embedded.DataSourceInitializer"><![CDATA[
<xsd:documentation source="java:org.springframework.jdbc.embedded.DataSourceInitializer"><![CDATA[
Initializes a database instance with SQL scripts provided in nested <script/> elements.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="script" type="scriptType" minOccurs="1"
maxOccurs="unbounded">
<xsd:element name="script" type="scriptType" minOccurs="1" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
A SQL script to execute during database initialization.
]]></xsd:documentation>
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="data-source" type="xsd:string"
default="dataSource">
<xsd:attribute name="data-source" type="xsd:string" default="dataSource">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to a data source that should be initialized. Defaults to "dataSource".
]]></xsd:documentation>
A reference to a data source that should be initialized. Defaults to "dataSource".
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref" />
<tool:expected-type type="javax.sql.DataSource" />
<tool:annotation kind="ref"/>
<tool:expected-type type="javax.sql.DataSource"/>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="enabled" type="xsd:string" use="optional"
default="true">
<xsd:attribute name="enabled" type="xsd:string" use="optional" default="true">
<xsd:annotation>
<xsd:documentation>
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.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ignore-failures" use="optional"
default="NONE">
<xsd:attribute name="ignore-failures" use="optional" default="NONE">
<xsd:annotation>
<xsd:documentation>
Should failed SQL statements be ignored during
initialization?
Should failed SQL statements be ignored during initialization?
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
@ -134,6 +123,13 @@
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="encoding" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The encoding for SQL scripts, if different from the platform encoding.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="separator" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
@ -144,13 +140,13 @@
<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).
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:enumeration value="INIT"/>
<xsd:enumeration value="DESTROY"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>

View File

@ -1,21 +1,40 @@
package org.springframework.jdbc.config;
/*
* Copyright 2002-2011 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.
*/
import static org.junit.Assert.assertEquals;
package org.springframework.jdbc.config;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcTemplate;
public class InitializeDatabaseIntegrationTest {
import static org.junit.Assert.*;
/**
* @author Dave Syer
*/
public class InitializeDatabaseIntegrationTests {
private String enabled;
private ClassPathXmlApplicationContext context;

View File

@ -1,15 +1,27 @@
package org.springframework.jdbc.config;
/*
* Copyright 2002-2011 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.
*/
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
package org.springframework.jdbc.config;
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;
@ -19,9 +31,17 @@ 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.embedded.EmbeddedDatabaseFactoryBean;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
public class JdbcNamespaceIntegrationTest {
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
/**
* @author Dave Syer
* @author Juergen Hoeller
*/
public class JdbcNamespaceIntegrationTests {
@Rule
public ExpectedException expected = ExpectedException.none();
@ -59,6 +79,14 @@ public class JdbcNamespaceIntegrationTest {
context.close();
}
@Test
public void testCreateWithEndingsNested() throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"org/springframework/jdbc/config/jdbc-initialize-endings-nested-config.xml");
assertCorrectSetup(context, 2, "dataSource");
context.close();
}
@Test
public void testCreateAndDestroy() throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
@ -70,23 +98,38 @@ public class JdbcNamespaceIntegrationTest {
context.getBean(DataSourceInitializer.class).destroy();
expected.expect(BadSqlGrammarException.class); // Table has been dropped
assertEquals(1, template.queryForInt("select count(*) from T_TEST"));
} finally {
}
finally {
context.close();
}
}
@Test
public void testCreateAndDestroyNested() throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"org/springframework/jdbc/config/jdbc-destroy-nested-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(EmbeddedDatabaseFactoryBean.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 {
DefaultListableBeanFactory factory = new XmlBeanFactory(new ClassPathResource(
"org/springframework/jdbc/config/jdbc-config-multiple-datasources.xml"));
assertBeanPropertyValueOf("databaseName", "firstDataSource", factory);
assertBeanPropertyValueOf("databaseName", "secondDataSource", factory);
}
private void assertBeanPropertyValueOf(String propertyName, String expected, DefaultListableBeanFactory factory) {
BeanDefinition bean = factory.getBeanDefinition(expected);
PropertyValue value = bean.getPropertyValues().getPropertyValue(propertyName);
assertThat(value, is(notNullValue()));
@ -98,14 +141,14 @@ public class JdbcNamespaceIntegrationTest {
}
private void assertCorrectSetup(ConfigurableApplicationContext context, int count, String... dataSources) {
try {
for (String dataSourceName : dataSources) {
DataSource dataSource = context.getBean(dataSourceName, DataSource.class);
JdbcTemplate template = new JdbcTemplate(dataSource);
assertEquals(count, template.queryForInt("select count(*) from T_TEST"));
}
} finally {
}
finally {
context.close();
}

View File

@ -0,0 +1,14 @@
<?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: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:embedded-database>
</beans>

View File

@ -14,7 +14,7 @@
<!-- This cache relies on data created in the initialize-database element. It works fine
if the bean definitions are registered in the right order. (Could possibly be fixed later.) -->
<bean class="org.springframework.jdbc.config.InitializeDatabaseIntegrationTest$CacheData">
<bean class="org.springframework.jdbc.config.InitializeDatabaseIntegrationTests$CacheData">
<property name="dataSource" ref="dataSource"/>
</bean>

View File

@ -6,9 +6,9 @@
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"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-schema.sql" encoding="ISO-8859-1"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data-endings.sql" separator="@@"/>
</jdbc:initialize-database>

View File

@ -0,0 +1,13 @@
<?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:script location="classpath:org/springframework/jdbc/config/db-schema.sql" encoding="ISO-8859-1"/>
<jdbc:script location="classpath:org/springframework/jdbc/config/db-test-data-endings.sql" separator="@@"/>
</jdbc:embedded-database>
</beans>