revised embedded database support

This commit is contained in:
Juergen Hoeller 2009-08-08 20:37:47 +00:00
parent 3ac3a72e91
commit e9823b57b4
20 changed files with 366 additions and 309 deletions

View File

@ -0,0 +1,75 @@
/*
* Copyright 2002-2009 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.core.io;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link ResourceLoader} implementation that interprets plain resource paths
* as relative to a given <code>java.lang.Class</code>.
*
* @author Juergen Hoeller
* @since 3.0
* @see java.lang.Class#getResource(String)
* @see ClassPathResource#ClassPathResource(String, Class)
*/
public class ClassRelativeResourceLoader extends DefaultResourceLoader {
private final Class clazz;
/**
* Create a new ClassRelativeResourceLoader for the given class.
* @param clazz the class to load resources through
*/
public ClassRelativeResourceLoader(Class clazz) {
Assert.notNull(clazz, "Class must not be null");
this.clazz = clazz;
setClassLoader(clazz.getClassLoader());
}
protected Resource getResourceByPath(String path) {
return new ClassRelativeContextResource(path, this.clazz);
}
/**
* ClassPathResource that explicitly expresses a context-relative path
* through implementing the ContextResource interface.
*/
private static class ClassRelativeContextResource extends ClassPathResource implements ContextResource {
private final Class clazz;
public ClassRelativeContextResource(String path, Class clazz) {
super(path, clazz);
this.clazz = clazz;
}
public String getPathWithinContext() {
return getPath();
}
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
return new ClassRelativeContextResource(pathToUse, this.clazz);
}
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2008 the original author or authors. * Copyright 2002-2009 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -107,8 +107,8 @@ public class SimpleDriverDataSource extends AbstractDriverBasedDataSource {
* within the SimpleDriverDataSource. * within the SimpleDriverDataSource.
* @see #setDriver * @see #setDriver
*/ */
public void setDriverClass(Class driverClass) { public void setDriverClass(Class<? extends Driver> driverClass) {
this.driver = (Driver) BeanUtils.instantiateClass(driverClass); this.driver = BeanUtils.instantiateClass(driverClass);
} }
/** /**

View File

@ -13,38 +13,39 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
/** /**
* Base class for {@link EmbeddedDatabaseConfigurer} implementations providing common shutdown behaviour. * Base class for {@link EmbeddedDatabaseConfigurer} implementations providing common shutdown behavior.
*
* @author Oliver Gierke * @author Oliver Gierke
* @author Juergen Hoeller
* @since 3.0 * @since 3.0
*/ */
abstract class AbstractEmbeddedDatabaseConfigurer implements EmbeddedDatabaseConfigurer { abstract class AbstractEmbeddedDatabaseConfigurer implements EmbeddedDatabaseConfigurer {
private static final Log logger = LogFactory.getLog(AbstractEmbeddedDatabaseConfigurer.class); protected final Log logger = LogFactory.getLog(getClass());
public void shutdown(DataSource dataSource, String databaseName) { public void shutdown(DataSource dataSource, String databaseName) {
Connection connection = JdbcUtils.getConnection(dataSource);
Statement stmt = null;
try { try {
stmt = connection.createStatement(); Connection connection = dataSource.getConnection();
Statement stmt = connection.createStatement();
stmt.execute("SHUTDOWN"); stmt.execute("SHUTDOWN");
} catch (SQLException e) { }
catch (SQLException ex) {
if (logger.isWarnEnabled()) { if (logger.isWarnEnabled()) {
logger.warn("Could not shutdown embedded database", e); logger.warn("Could not shutdown embedded database", ex);
} }
} finally {
JdbcUtils.closeStatement(stmt);
} }
} }
} }

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.EncodedResource;
@ -20,19 +21,19 @@ import org.springframework.core.io.support.EncodedResource;
/** /**
* Thrown by {@link ResourceDatabasePopulator} if one of its SQL scripts could * Thrown by {@link ResourceDatabasePopulator} if one of its SQL scripts could
* not be read during population. * not be read during population.
*
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
*/ */
@SuppressWarnings("serial")
public class CannotReadScriptException extends RuntimeException { public class CannotReadScriptException extends RuntimeException {
/** /**
* Creates a new cannot read script exception. * Constructor a new CannotReadScriptException.
*
* @param resource the resource that could not be read from * @param resource the resource that could not be read from
* @param cause the underlying cause of the resource access failure * @param cause the underlying cause of the resource access failure
*/ */
public CannotReadScriptException(EncodedResource resource, Throwable cause) { public CannotReadScriptException(EncodedResource resource, Throwable cause) {
super("Cannot read SQL script from " + resource, cause); super("Cannot read SQL script from " + resource, cause);
} }
} }

View File

@ -13,38 +13,43 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import java.sql.Driver;
/** /**
* DataSourceFactory helper that allows essential JDBC connection properties to be configured consistently, * DataSourceFactory helper that allows essential JDBC connection properties to be configured consistently,
* independent of the actual DataSource implementation. * independent of the actual DataSource implementation.
*
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
* @see DataSourceFactory * @see DataSourceFactory
*/ */
public interface ConnectionProperties { public interface ConnectionProperties {
/** /**
* Set the JDBC driver to use to connect to the database. * Set the JDBC driver to use to connect to the database.
* @param driverClass the jdbc driver class * @param driverClass the jdbc driver class
*/ */
void setDriverClass(Class<?> driverClass); void setDriverClass(Class driverClass);
/** /**
* Sets the JDBC connection URL of the database. * Sets the JDBC connection URL of the database.
* @param url the connection url * @param url the connection url
*/ */
void setUrl(String url); void setUrl(String url);
/** /**
* Sets the username to use to connect to the database. * Sets the username to use to connect to the database.
* @param username the username * @param username the username
*/ */
void setUsername(String username); void setUsername(String username);
/** /**
* Sets the password to use to connect to the database. * Sets the password to use to connect to the database.
* @param password the password * @param password the password
*/ */
void setPassword(String password); void setPassword(String password);
} }

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import javax.sql.DataSource; import javax.sql.DataSource;
@ -20,21 +21,25 @@ import javax.sql.DataSource;
import org.springframework.jdbc.datasource.SimpleDriverDataSource; import org.springframework.jdbc.datasource.SimpleDriverDataSource;
/** /**
* Encapsulates the creation of a particular DataSource implementation, such as a {@link SimpleDriverDataSource} or connection pool such as Apache DBCP or c3p0. * Encapsulates the creation of a particular DataSource implementation, such as a
* Call {@link #getConnectionProperties()} to configure normalized DataSource properties before calling {@link #getDataSource()} to actually get the configured DataSource instance. * {@link SimpleDriverDataSource} or connection pool such as Apache DBCP or C3P0.
*
* <p>Call {@link #getConnectionProperties()} to configure normalized DataSource properties
* before calling {@link #getDataSource()} to actually get the configured DataSource instance.
*
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
*/ */
public interface DataSourceFactory { public interface DataSourceFactory {
/** /**
* Allows properties of the DataSource to be configured. * Allows properties of the DataSource to be configured.
*/ */
ConnectionProperties getConnectionProperties(); ConnectionProperties getConnectionProperties();
/** /**
* Returns the DataSource with the connection properties applied. * Returns the DataSource with the connection properties applied.
*/ */
DataSource getDataSource(); DataSource getDataSource();
} }

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import java.sql.Connection; import java.sql.Connection;
@ -20,6 +21,7 @@ import java.sql.SQLException;
/** /**
* Strategy used to populate an embedded database during initialization. * Strategy used to populate an embedded database during initialization.
*
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
* @see ResourceDatabasePopulator * @see ResourceDatabasePopulator
@ -32,4 +34,5 @@ public interface DatabasePopulator {
* @throws SQLException if an unrecoverable data access exception occurs during database population * @throws SQLException if an unrecoverable data access exception occurs during database population
*/ */
void populate(Connection connection) throws SQLException; void populate(Connection connection) throws SQLException;
}
}

View File

@ -13,26 +13,26 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.apache.derby.impl.io.VFMemoryStorageFactory; import org.apache.derby.impl.io.VFMemoryStorageFactory;
import org.apache.derby.jdbc.EmbeddedDriver; import org.apache.derby.jdbc.EmbeddedDriver;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.util.ClassUtils;
/** /**
* {@link EmbeddedDatabaseConfigurer} for the Apache Derby database. * {@link EmbeddedDatabaseConfigurer} for the Apache Derby database.
*
* @author Oliver Gierke * @author Oliver Gierke
* @author Juergen Hoeller
* @since 3.0 * @since 3.0
*/ */
final class DerbyEmbeddedDatabaseConfigurer implements EmbeddedDatabaseConfigurer { final class DerbyEmbeddedDatabaseConfigurer implements EmbeddedDatabaseConfigurer {
@ -46,9 +46,6 @@ final class DerbyEmbeddedDatabaseConfigurer implements EmbeddedDatabaseConfigure
private static DerbyEmbeddedDatabaseConfigurer INSTANCE; private static DerbyEmbeddedDatabaseConfigurer INSTANCE;
private DerbyEmbeddedDatabaseConfigurer() {
}
/** /**
* Get the singleton {@link DerbyEmbeddedDatabaseConfigurer} instance. * Get the singleton {@link DerbyEmbeddedDatabaseConfigurer} instance.
* @return the configurer * @return the configurer
@ -59,50 +56,48 @@ final class DerbyEmbeddedDatabaseConfigurer implements EmbeddedDatabaseConfigure
// disable log file // disable log file
System.setProperty("derby.stream.error.method", System.setProperty("derby.stream.error.method",
DerbyEmbeddedDatabaseConfigurer.class.getName() + ".getNoopOutputStream"); DerbyEmbeddedDatabaseConfigurer.class.getName() + ".getNoopOutputStream");
ClassUtils.forName("org.apache.derby.jdbc.EmbeddedDriver", DerbyEmbeddedDatabaseConfigurer.class
.getClassLoader());
INSTANCE = new DerbyEmbeddedDatabaseConfigurer(); INSTANCE = new DerbyEmbeddedDatabaseConfigurer();
} }
return INSTANCE; return INSTANCE;
} }
private DerbyEmbeddedDatabaseConfigurer() {
}
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) { public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
properties.setDriverClass(org.apache.derby.jdbc.EmbeddedDriver.class); properties.setDriverClass(EmbeddedDriver.class);
properties.setUrl(String.format(URL_TEMPLATE, databaseName, "create=true")); properties.setUrl(String.format(URL_TEMPLATE, databaseName, "create=true"));
properties.setUsername("sa"); properties.setUsername("sa");
properties.setPassword(""); properties.setPassword("");
} }
public void shutdown(DataSource dataSource, String databaseName) { public void shutdown(DataSource dataSource, String databaseName) {
Connection connection = null;
try { try {
SimpleDriverDataSource shutdownDataSource = new SimpleDriverDataSource(); new EmbeddedDriver().connect(
shutdownDataSource.setDriverClass(EmbeddedDriver.class); String.format(URL_TEMPLATE, databaseName, "shutdown=true"), new Properties());
shutdownDataSource.setUrl(String.format(URL_TEMPLATE, databaseName, "shutdown=true")); }
connection = shutdownDataSource.getConnection(); catch (SQLException ex) {
} catch (SQLException e) { if (SHUTDOWN_CODE.equals(ex.getSQLState())) {
if (SHUTDOWN_CODE.equals(e.getSQLState())) {
purgeDatabase(databaseName); purgeDatabase(databaseName);
} else {
logger.warn("Could not shutdown in-memory Derby database", e);
} }
} finally { else {
JdbcUtils.closeConnection(connection); logger.warn("Could not shutdown in-memory Derby database", ex);
}
} }
} }
/** /**
* Purges the in-memory database, to prevent it from hanging around after * Purge the in-memory database, to prevent it from hanging around after
* being shut down * being shut down.
* @param databaseName
*/ */
private void purgeDatabase(String databaseName) { private void purgeDatabase(String databaseName) {
// TODO: update this code once Derby adds a proper way to remove an in-memory db // TODO: update this code once Derby adds a proper way to remove an in-memory db
// (see http://wiki.apache.org/db-derby/InMemoryBackEndPrimer for details) // (see http://wiki.apache.org/db-derby/InMemoryBackEndPrimer for details)
try { try {
VFMemoryStorageFactory.purgeDatabase(new File(databaseName).getCanonicalPath()); VFMemoryStorageFactory.purgeDatabase(new File(databaseName).getCanonicalPath());
} catch (IOException ioe) { }
logger.warn("Could not purge in-memory Derby database", ioe); catch (IOException ex) {
logger.warn("Could not purge in-memory Derby database", ex);
} }
} }
@ -114,9 +109,9 @@ final class DerbyEmbeddedDatabaseConfigurer implements EmbeddedDatabaseConfigure
static OutputStream getNoopOutputStream() { static OutputStream getNoopOutputStream() {
return new OutputStream() { return new OutputStream() {
public void write(int b) throws IOException { public void write(int b) throws IOException {
// ignore the input // ignore the output
} }
}; };
} }
} }

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import javax.sql.DataSource; import javax.sql.DataSource;
@ -21,13 +22,15 @@ import javax.sql.DataSource;
* A handle to an EmbeddedDatabase instance. * A handle to an EmbeddedDatabase instance.
* Is a {@link DataSource}. * Is a {@link DataSource}.
* Adds a shutdown operation so the embedded database instance can be shutdown. * Adds a shutdown operation so the embedded database instance can be shutdown.
*
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
*/ */
public interface EmbeddedDatabase extends DataSource { public interface EmbeddedDatabase extends DataSource {
/** /**
* Shutdown this embedded database. * Shutdown this embedded database.
*/ */
void shutdown(); void shutdown();
} }

View File

@ -13,39 +13,55 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassRelativeResourceLoader;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
/** /**
* A builder that provides a fluent API for constructing an embedded database. * A builder that provides a fluent API for constructing an embedded database.
* Usage example: *
* <p>Usage example:
* <pre> * <pre>
* EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); * EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
* EmbeddedDatabase db = builder.script("schema.sql").script("test-data.sql").build(); * EmbeddedDatabase db = builder.script("schema.sql").script("test-data.sql").build();
* db.shutdown(); * db.shutdown();
* </pre> * </pre>
*
* @author Keith Donald * @author Keith Donald
* @author Juergen Hoeller
* @since 3.0 * @since 3.0
*/ */
public class EmbeddedDatabaseBuilder { public class EmbeddedDatabaseBuilder {
private EmbeddedDatabaseFactory databaseFactory; private final EmbeddedDatabaseFactory databaseFactory;
private ResourceDatabasePopulator databasePopulator; private final ResourceDatabasePopulator databasePopulator;
private final ResourceLoader resourceLoader;
private ResourceLoader resourceLoader;
/** /**
* Creates a new embedded database builder. * Create a new embedded database builder.
*/ */
public EmbeddedDatabaseBuilder() { public EmbeddedDatabaseBuilder() {
init(new DefaultResourceLoader()); this(new DefaultResourceLoader());
} }
/**
* Create a new embedded database builder withfor the given ResourceLoader.
* @param resourceLoader the ResourceLoader to delegate to
*/
public EmbeddedDatabaseBuilder(ResourceLoader resourceLoader) {
this.databaseFactory = new EmbeddedDatabaseFactory();
this.databasePopulator = new ResourceDatabasePopulator();
this.databaseFactory.setDatabasePopulator(this.databasePopulator);
this.resourceLoader = resourceLoader;
}
/** /**
* Sets the name of the embedded database * Sets the name of the embedded database
* Defaults to 'testdb' if not called. * Defaults to 'testdb' if not called.
@ -53,7 +69,7 @@ public class EmbeddedDatabaseBuilder {
* @return this, for fluent call chaining * @return this, for fluent call chaining
*/ */
public EmbeddedDatabaseBuilder name(String databaseName) { public EmbeddedDatabaseBuilder name(String databaseName) {
databaseFactory.setDatabaseName(databaseName); this.databaseFactory.setDatabaseName(databaseName);
return this; return this;
} }
@ -64,7 +80,7 @@ public class EmbeddedDatabaseBuilder {
* @return this, for fluent call chaining * @return this, for fluent call chaining
*/ */
public EmbeddedDatabaseBuilder type(EmbeddedDatabaseType databaseType) { public EmbeddedDatabaseBuilder type(EmbeddedDatabaseType databaseType) {
databaseFactory.setDatabaseType(databaseType); this.databaseFactory.setDatabaseType(databaseType);
return this; return this;
} }
@ -74,7 +90,7 @@ public class EmbeddedDatabaseBuilder {
* @return this, for fluent call chaining * @return this, for fluent call chaining
*/ */
public EmbeddedDatabaseBuilder script(String sqlResource) { public EmbeddedDatabaseBuilder script(String sqlResource) {
databasePopulator.addScript(resourceLoader.getResource(sqlResource)); this.databasePopulator.addScript(resourceLoader.getResource(sqlResource));
return this; return this;
} }
@ -83,45 +99,28 @@ public class EmbeddedDatabaseBuilder {
* @return the embedded database * @return the embedded database
*/ */
public EmbeddedDatabase build() { public EmbeddedDatabase build() {
return databaseFactory.getDatabase(); return this.databaseFactory.getDatabase();
} }
/**
* Factory method that creates a EmbeddedDatabaseBuilder that loads SQL resources relative to the provided class.
* @param clazz the class to load relative to
* @return the embedded database builder
*/
public static EmbeddedDatabaseBuilder relativeTo(final Class<?> clazz) {
ResourceLoader loader = new ResourceLoader() {
public ClassLoader getClassLoader() {
return getClass().getClassLoader();
}
public Resource getResource(String location) {
return new ClassPathResource(location, clazz);
}
};
return new EmbeddedDatabaseBuilder(loader);
}
/** /**
* Factory method that builds a default EmbeddedDatabase instance. * Factory method that builds a default EmbeddedDatabase instance.
* The default instance is HSQL with a schema created from classpath:schema.sql and test-data loaded from classpath:test-data.sql. * The default instance is HSQL with a schema created from "classpath:schema.sql"
* and test-data loaded from "classpath:test-data.sql".
* @return an embedded database * @return an embedded database
*/ */
public static EmbeddedDatabase buildDefault() { public static EmbeddedDatabase buildDefault() {
return new EmbeddedDatabaseBuilder().script("schema.sql").script("test-data.sql").build(); return new EmbeddedDatabaseBuilder().script("schema.sql").script("test-data.sql").build();
} }
private EmbeddedDatabaseBuilder(ResourceLoader loader) { /**
init(loader); * Factory method that creates a EmbeddedDatabaseBuilder that loads SQL resources
} * relative to the provided class.
* @param clazz the class to load relative to
private void init(ResourceLoader loader) { * @return the embedded database builder
databaseFactory = new EmbeddedDatabaseFactory(); */
databasePopulator = new ResourceDatabasePopulator(); public static EmbeddedDatabaseBuilder relativeTo(Class clazz) {
databaseFactory.setDatabasePopulator(databasePopulator); return new EmbeddedDatabaseBuilder(new ClassRelativeResourceLoader(clazz));
resourceLoader = loader;
} }
} }

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import javax.sql.DataSource; import javax.sql.DataSource;
@ -20,6 +21,7 @@ import javax.sql.DataSource;
/** /**
* Encapsulates the configuration required to create, connect to, and shutdown a specific type of embedded database such as HSQL or H2. * Encapsulates the configuration required to create, connect to, and shutdown a specific type of embedded database such as HSQL or H2.
* Create a implementation for each database type you wish to support; for example HSQL, H2, or some other type. * Create a implementation for each database type you wish to support; for example HSQL, H2, or some other type.
*
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
*/ */

View File

@ -13,13 +13,15 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* Maps well-known {@link EmbeddedDatabaseType embedded database types} to {@link EmbeddedDatabaseConfigurer} * Maps well-known {@link EmbeddedDatabaseType embedded database types} to
* strategies. * {@link EmbeddedDatabaseConfigurer} strategies.
*
* @author Keith Donald * @author Keith Donald
* @author Oliver Gierke * @author Oliver Gierke
* @since 3.0 * @since 3.0
@ -27,25 +29,26 @@ import org.springframework.util.Assert;
final class EmbeddedDatabaseConfigurerFactory { final class EmbeddedDatabaseConfigurerFactory {
public static EmbeddedDatabaseConfigurer getConfigurer(EmbeddedDatabaseType type) throws IllegalStateException { public static EmbeddedDatabaseConfigurer getConfigurer(EmbeddedDatabaseType type) throws IllegalStateException {
Assert.notNull(type, "The EmbeddedDatabaseType is required"); Assert.notNull(type, "EmbeddedDatabaseType is required");
try { try {
switch (type) { switch (type) {
case HSQL: case HSQL:
return HsqlEmbeddedDatabaseConfigurer.getInstance(); return HsqlEmbeddedDatabaseConfigurer.getInstance();
case H2: case H2:
return H2EmbeddedDatabaseConfigurer.getInstance(); return H2EmbeddedDatabaseConfigurer.getInstance();
case DERBY: case DERBY:
return DerbyEmbeddedDatabaseConfigurer.getInstance(); return DerbyEmbeddedDatabaseConfigurer.getInstance();
default: default:
throw new UnsupportedOperationException("Other embedded database types not yet supported"); throw new UnsupportedOperationException("Other embedded database types not yet supported");
} }
} catch (ClassNotFoundException e) { }
throw new IllegalStateException("Drivers for test database type [" + type catch (ClassNotFoundException ex) {
+ "] are not available in the classpath", e); throw new IllegalStateException("Driver for test database type [" + type +
"] is not available in the classpath", ex);
} }
} }
private EmbeddedDatabaseConfigurerFactory() { private EmbeddedDatabaseConfigurerFactory() {
} }
} }

View File

@ -13,39 +13,43 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* Creates a {@link EmbeddedDatabase} instance. Callers are guaranteed that the returned database has been fully * Creates a {@link EmbeddedDatabase} instance. Callers are guaranteed that the returned database has been fully
* initialized and populated. * initialized and populated.
* <p> *
* Can be configured:<br> * <p>Can be configured:<br>
* Call {@link #setDatabaseName(String)} to change the name of the database.<br> * Call {@link #setDatabaseName(String)} to change the name of the database.<br>
* Call {@link #setDatabaseType(EmbeddedDatabaseType)} to set the database type if you wish to use one of the supported types.<br> * Call {@link #setDatabaseType(EmbeddedDatabaseType)} to set the database type if you wish to use one of the supported types.<br>
* Call {@link #setDatabaseConfigurer(EmbeddedDatabaseConfigurer)} to configure support for your own embedded database type.<br> * Call {@link #setDatabaseConfigurer(EmbeddedDatabaseConfigurer)} to configure support for your own embedded database type.<br>
* Call {@link #setDatabasePopulator(DatabasePopulator)} to change the algorithm used to populate the database.<br> * Call {@link #setDatabasePopulator(DatabasePopulator)} to change the algorithm used to populate the database.<br>
* Call {@link #setDataSourceFactory(DataSourceFactory)} to change the type of DataSource used to connect to the database.<br> * Call {@link #setDataSourceFactory(DataSourceFactory)} to change the type of DataSource used to connect to the database.<br>
* Call {@link #getDatabase()} to get the {@link EmbeddedDatabase} instance.<br> * Call {@link #getDatabase()} to get the {@link EmbeddedDatabase} instance.<br>
*
* @author Keith Donald * @author Keith Donald
* @author Juergen Hoeller
* @since 3.0 * @since 3.0
*/ */
public class EmbeddedDatabaseFactory { public class EmbeddedDatabaseFactory {
private static Log logger = LogFactory.getLog(EmbeddedDatabaseFactory.class); private static Log logger = LogFactory.getLog(EmbeddedDatabaseFactory.class);
private String databaseName; private String databaseName = "testdb";
private DataSourceFactory dataSourceFactory; private DataSourceFactory dataSourceFactory = new SimpleDriverDataSourceFactory();
private EmbeddedDatabaseConfigurer databaseConfigurer; private EmbeddedDatabaseConfigurer databaseConfigurer;
@ -53,101 +57,114 @@ public class EmbeddedDatabaseFactory {
private DataSource dataSource; private DataSource dataSource;
/** /**
* Creates a default {@link EmbeddedDatabaseFactory}. * Set the name of the database. Defaults to "testdb".
* Calling {@link #getDatabase()} will create a embedded HSQL database of name 'testdb'. * @param databaseName name of the test database
*/ */
public EmbeddedDatabaseFactory() { public void setDatabaseName(String databaseName) {
setDatabaseName("testdb"); Assert.notNull(databaseName, "Database name is required");
setDatabaseType(EmbeddedDatabaseType.HSQL); this.databaseName = databaseName;
setDataSourceFactory(new SimpleDriverDataSourceFactory());
} }
/** /**
* Sets the name of the database. Defaults to 'testdb'. * Set the type of embedded database to use. Call this when you wish to configure
* @param name of the test database * one of the pre-supported types. Defaults to HSQL.
*/
public void setDatabaseName(String name) {
Assert.notNull(name, "The testDatabaseName is required");
databaseName = name;
}
/**
* Sets the type of embedded database to use. Call this when you wish to configure one of the pre-supported types.
* Defaults to HSQL.
* @param type the test database type * @param type the test database type
*/ */
public void setDatabaseType(EmbeddedDatabaseType type) { public void setDatabaseType(EmbeddedDatabaseType type) {
setDatabaseConfigurer(EmbeddedDatabaseConfigurerFactory.getConfigurer(type)); this.databaseConfigurer = EmbeddedDatabaseConfigurerFactory.getConfigurer(type);
} }
/** /**
* Sets the strategy that will be used to configure the embedded database instance. * 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. * Call this when you wish to use an embedded database type not already supported.
* @param configurer the embedded database configurer * @param configurer the embedded database configurer
*/ */
public void setDatabaseConfigurer(EmbeddedDatabaseConfigurer configurer) { public void setDatabaseConfigurer(EmbeddedDatabaseConfigurer configurer) {
Assert.notNull(configurer, "EmbeddedDatabaseConfigurer is required");
this.databaseConfigurer = configurer; this.databaseConfigurer = configurer;
} }
/** /**
* Sets the strategy that will be used to populate the embedded database. Defaults to null. * Set the strategy that will be used to populate the embedded database. Defaults to null.
* @param populator the database populator * @param populator the database populator
*/ */
public void setDatabasePopulator(DatabasePopulator populator) { public void setDatabasePopulator(DatabasePopulator populator) {
Assert.notNull(populator, "The DatabasePopulator is required"); Assert.notNull(populator, "DatabasePopulator is required");
databasePopulator = populator; this.databasePopulator = populator;
} }
/** /**
* Sets the factory to use to create the DataSource instance that connects to the embedded database * Set the factory to use to create the DataSource instance that connects to the embedded database.
* Defaults to {@link SimpleDriverDataSourceFactory}. * Defaults to {@link SimpleDriverDataSourceFactory}.
* @param dataSourceFactory the data source factory * @param dataSourceFactory the data source factory
*/ */
public void setDataSourceFactory(DataSourceFactory dataSourceFactory) { public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
Assert.notNull(dataSourceFactory, "The DataSourceFactory is required"); Assert.notNull(dataSourceFactory, "DataSourceFactory is required");
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
} }
// other public methods
/** /**
* Factory method that returns the embedded database instance. * Factory method that returns the embedded database instance.
*/ */
public EmbeddedDatabase getDatabase() { public EmbeddedDatabase getDatabase() {
if (dataSource == null) { if (this.dataSource == null) {
initDatabase(); initDatabase();
} }
return new EmbeddedDataSourceProxy(dataSource); return new EmbeddedDataSourceProxy(this.dataSource);
} }
// subclassing hooks
/** /**
* Hook to initialize the embedded database. Subclasses may call to force initialization. After calling this method, * Hook to initialize the embedded database. Subclasses may call to force initialization. After calling this method,
* {@link #getDataSource()} returns the DataSource providing connectivity to the db. * {@link #getDataSource()} returns the DataSource providing connectivity to the db.
*/ */
protected void initDatabase() { protected void initDatabase() {
// create the embedded database source first // Create the embedded database source first
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info("Created embedded database '" + databaseName + "'"); logger.info("Creating embedded database '" + this.databaseName + "'");
} }
databaseConfigurer.configureConnectionProperties(dataSourceFactory.getConnectionProperties(), databaseName); if (this.databaseConfigurer == null) {
dataSource = dataSourceFactory.getDataSource(); this.databaseConfigurer = EmbeddedDatabaseConfigurerFactory.getConfigurer(EmbeddedDatabaseType.HSQL);
if (databasePopulator != null) { }
// now populate the database this.databaseConfigurer.configureConnectionProperties(
this.dataSourceFactory.getConnectionProperties(), this.databaseName);
this.dataSource = this.dataSourceFactory.getDataSource();
// Now populate the database
if (this.databasePopulator != null) {
populateDatabase(); populateDatabase();
} }
} }
private void populateDatabase() {
try {
Connection connection = this.dataSource.getConnection();
try {
this.databasePopulator.populate(connection);
}
finally {
try {
connection.close();
}
catch (SQLException ex) {
// ignore
}
}
}
catch (SQLException ex) {
throw new DataAccessResourceFailureException("Failed to populate database", ex);
}
}
/** /**
* Hook that gets the datasource that provides the connectivity to the embedded database. * Hook that gets the DataSource that provides the connectivity to the embedded database.
* Returns null if the datasource has not been initialized or the database has been shutdown. * <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. * Subclasses may call to access the datasource instance directly.
* @return the datasource
*/ */
protected DataSource getDataSource() { protected DataSource getDataSource() {
return dataSource; return this.dataSource;
} }
/** /**
@ -155,68 +172,56 @@ public class EmbeddedDatabaseFactory {
* After calling, {@link #getDataSource()} returns null. Does nothing if no embedded database has been initialized. * After calling, {@link #getDataSource()} returns null. Does nothing if no embedded database has been initialized.
*/ */
protected void shutdownDatabase() { protected void shutdownDatabase() {
if (dataSource != null) { if (this.dataSource != null) {
databaseConfigurer.shutdown(dataSource, databaseName); this.databaseConfigurer.shutdown(this.dataSource, this.databaseName);
dataSource = null; this.dataSource = null;
} }
} }
// internal helper methods
private void populateDatabase() {
Connection connection = JdbcUtils.getConnection(dataSource);
try {
databasePopulator.populate(connection);
} catch (SQLException e) {
throw new RuntimeException("SQLException occurred populating embedded database", e);
} finally {
JdbcUtils.closeConnection(connection);
}
}
private class EmbeddedDataSourceProxy implements EmbeddedDatabase { private class EmbeddedDataSourceProxy implements EmbeddedDatabase {
private DataSource dataSource;
private final DataSource dataSource;
public EmbeddedDataSourceProxy(DataSource dataSource) { public EmbeddedDataSourceProxy(DataSource dataSource) {
this.dataSource = dataSource; this.dataSource = dataSource;
} }
public Connection getConnection() throws SQLException { public Connection getConnection() throws SQLException {
return dataSource.getConnection(); return this.dataSource.getConnection();
} }
public Connection getConnection(String username, String password) throws SQLException { public Connection getConnection(String username, String password) throws SQLException {
return dataSource.getConnection(username, password); return this.dataSource.getConnection(username, password);
} }
public int getLoginTimeout() throws SQLException { public int getLoginTimeout() throws SQLException {
return dataSource.getLoginTimeout(); return this.dataSource.getLoginTimeout();
} }
public PrintWriter getLogWriter() throws SQLException { public PrintWriter getLogWriter() throws SQLException {
return dataSource.getLogWriter(); return this.dataSource.getLogWriter();
} }
public void setLoginTimeout(int seconds) throws SQLException { public void setLoginTimeout(int seconds) throws SQLException {
dataSource.setLoginTimeout(seconds); this.dataSource.setLoginTimeout(seconds);
} }
public void setLogWriter(PrintWriter out) throws SQLException { public void setLogWriter(PrintWriter out) throws SQLException {
dataSource.setLogWriter(out); this.dataSource.setLogWriter(out);
} }
public boolean isWrapperFor(Class<?> iface) throws SQLException { public boolean isWrapperFor(Class<?> iface) throws SQLException {
return dataSource.isWrapperFor(iface); return this.dataSource.isWrapperFor(iface);
} }
public <T> T unwrap(Class<T> iface) throws SQLException { public <T> T unwrap(Class<T> iface) throws SQLException {
return dataSource.unwrap(iface); return this.dataSource.unwrap(iface);
} }
public void shutdown() { public void shutdown() {
shutdownDatabase(); shutdownDatabase();
} }
} }
} }

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import javax.sql.DataSource; import javax.sql.DataSource;
@ -24,18 +25,23 @@ import org.springframework.beans.factory.InitializingBean;
/** /**
* A subclass of {@link EmbeddedDatabaseFactory} that implements {@link FactoryBean} for registration as a Spring bean. * A subclass of {@link EmbeddedDatabaseFactory} that implements {@link FactoryBean} for registration as a Spring bean.
* Returns the actual {@link DataSource} that provides connectivity to the embedded database to Spring. * Returns the actual {@link DataSource} that provides connectivity to the embedded database to Spring.
* The target DataSource is returned instead of a {@link EmbeddedDatabase} proxy since the FactoryBean will manage the initialization and destruction lifecycle of the database instance. *
* Implements DisposableBean to shutdown the embedded database when the managing Spring container is shutdown. * <p>The target DataSource is returned instead of a {@link EmbeddedDatabase} proxy since the FactoryBean
* will manage the initialization and destruction lifecycle of the database instance.
*
* <p>Implements DisposableBean to shutdown the embedded database when the managing Spring container is shutdown.
*
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
*/ */
public class EmbeddedDatabaseFactoryBean extends EmbeddedDatabaseFactory implements FactoryBean<DataSource>, InitializingBean, DisposableBean { public class EmbeddedDatabaseFactoryBean extends EmbeddedDatabaseFactory
implements FactoryBean<DataSource>, InitializingBean, DisposableBean {
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() {
initDatabase(); initDatabase();
} }
public DataSource getObject() throws Exception { public DataSource getObject() {
return getDataSource(); return getDataSource();
} }
@ -47,7 +53,7 @@ public class EmbeddedDatabaseFactoryBean extends EmbeddedDatabaseFactory impleme
return true; return true;
} }
public void destroy() throws Exception { public void destroy() {
shutdownDatabase(); shutdownDatabase();
} }

View File

@ -13,14 +13,18 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
/** /**
* A supported embedded database type. * A supported embedded database type.
*
* @author Keith Donald * @author Keith Donald
* @author Oliver Gierke * @author Oliver Gierke
* @since 3.0 * @since 3.0
*/ */
public enum EmbeddedDatabaseType { public enum EmbeddedDatabaseType {
HSQL, H2, DERBY;
HSQL, H2, DERBY
} }

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -20,7 +21,9 @@ import org.springframework.util.ClassUtils;
/** /**
* Initializes an H2 embedded database instance. * Initializes an H2 embedded database instance.
* Call {@link #getInstance()} to get the singleton instance of this class. * Call {@link #getInstance()} to get the singleton instance of this class.
*
* @author Oliver Gierke * @author Oliver Gierke
* @author Juergen Hoeller
* @since 3.0 * @since 3.0
*/ */
final class H2EmbeddedDatabaseConfigurer extends AbstractEmbeddedDatabaseConfigurer { final class H2EmbeddedDatabaseConfigurer extends AbstractEmbeddedDatabaseConfigurer {
@ -42,15 +45,15 @@ final class H2EmbeddedDatabaseConfigurer extends AbstractEmbeddedDatabaseConfigu
return INSTANCE; return INSTANCE;
} }
private H2EmbeddedDatabaseConfigurer(Class<?> driverClass) {
this.driverClass = driverClass;
}
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) { public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
properties.setDriverClass(driverClass); properties.setDriverClass(this.driverClass);
properties.setUrl(String.format("jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1", databaseName)); properties.setUrl(String.format("jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1", databaseName));
properties.setUsername("sa"); properties.setUsername("sa");
properties.setPassword(""); properties.setPassword("");
} }
private H2EmbeddedDatabaseConfigurer(Class<?> driverClass) { }
this.driverClass = driverClass;
}
}

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -20,6 +21,7 @@ import org.springframework.util.ClassUtils;
/** /**
* Initializes an HSQL embedded database instance. * Initializes an HSQL embedded database instance.
* Call {@link #getInstance()} to get the singleton instance of this class. * Call {@link #getInstance()} to get the singleton instance of this class.
*
* @author Keith Donald * @author Keith Donald
* @author Oliver Gierke * @author Oliver Gierke
* @since 3.0 * @since 3.0
@ -43,15 +45,15 @@ final class HsqlEmbeddedDatabaseConfigurer extends AbstractEmbeddedDatabaseConfi
return INSTANCE; return INSTANCE;
} }
private HsqlEmbeddedDatabaseConfigurer(Class<?> driverClass) {
this.driverClass = driverClass;
}
public void configureConnectionProperties(ConnectionProperties properties, String databaseName) { public void configureConnectionProperties(ConnectionProperties properties, String databaseName) {
properties.setDriverClass(driverClass); properties.setDriverClass(this.driverClass);
properties.setUrl("jdbc:hsqldb:mem:" + databaseName); properties.setUrl("jdbc:hsqldb:mem:" + databaseName);
properties.setUsername("sa"); properties.setUsername("sa");
properties.setPassword(""); properties.setPassword("");
} }
private HsqlEmbeddedDatabaseConfigurer(Class<?> driverClass) {
this.driverClass = driverClass;
}
} }

View File

@ -1,76 +0,0 @@
/*
* Copyright 2002-2009 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.embedded;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
/**
* Helper JDBC utilities used by other classes in this package.
* Note there is some duplication here with JdbcUtils in jdbc.support package.
* We may want to consider simply using that at some point.
* @author Keith Donald
* @since 3.0
*/
final class JdbcUtils {
private static Log logger = LogFactory.getLog(EmbeddedDatabaseFactory.class);
private JdbcUtils() {
}
public static Connection getConnection(DataSource dataSource) {
try {
return dataSource.getConnection();
} catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
}
}
public static void closeConnection(Connection connection) {
if (connection != null) {
try {
connection.close();
} catch (SQLException ex) {
logger.debug("Could not close JDBC Connection", ex);
} catch (Throwable ex) {
// We don't trust the JDBC driver: It might throw RuntimeException or Error.
logger.debug("Unexpected exception on closing JDBC Connection", ex);
}
}
}
public static void closeStatement(Statement stmt) {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException ex) {
logger.debug("Could not close JDBC Statement", ex);
} catch (Throwable ex) {
// We don't trust the JDBC driver: It might throw RuntimeException or Error.
logger.debug("Unexpected exception on closing JDBC Statement", ex);
}
}
}
}

View File

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import java.io.IOException; import java.io.IOException;
@ -27,15 +28,17 @@ import java.util.List;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.EncodedResource;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
* Populates a database from SQL scripts defined in external resources. * Populates a database from SQL scripts defined in external resources.
* <p> *
* Call {@link #addScript(Resource)} to add a SQL script location.<br> * <p>Call {@link #addScript(Resource)} to add a SQL script location.<br>
* Call {@link #setSqlScriptEncoding(String)} to set the encoding for all added scripts.<br> * Call {@link #setSqlScriptEncoding(String)} to set the encoding for all added scripts.<br>
*
* @author Keith Donald * @author Keith Donald
* @since 3.0 * @since 3.0
*/ */
@ -47,6 +50,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
private String sqlScriptEncoding; private String sqlScriptEncoding;
/** /**
* Add a script to execute to populate the database. * Add a script to execute to populate the database.
* @param script the path to a SQL script * @param script the path to a SQL script
@ -65,15 +69,17 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
/** /**
* Specify the encoding for SQL scripts, if different from the platform encoding. * Specify the encoding for SQL scripts, if different from the platform encoding.
* Note setting this property has no effect on added scripts that are already {@link EncodedResource encoded resources}. * Note setting this property has no effect on added scripts that are already
* {@link EncodedResource encoded resources}.
* @see #addScript(Resource) * @see #addScript(Resource)
*/ */
public void setSqlScriptEncoding(String sqlScriptEncoding) { public void setSqlScriptEncoding(String sqlScriptEncoding) {
this.sqlScriptEncoding = sqlScriptEncoding; this.sqlScriptEncoding = sqlScriptEncoding;
} }
public void populate(Connection connection) throws SQLException { public void populate(Connection connection) throws SQLException {
for (Resource script : scripts) { for (Resource script : this.scripts) {
executeSqlScript(connection, applyEncodingIfNecessary(script), false); executeSqlScript(connection, applyEncodingIfNecessary(script), false);
} }
} }
@ -81,8 +87,9 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
private EncodedResource applyEncodingIfNecessary(Resource script) { private EncodedResource applyEncodingIfNecessary(Resource script) {
if (script instanceof EncodedResource) { if (script instanceof EncodedResource) {
return (EncodedResource) script; return (EncodedResource) script;
} else { }
return new EncodedResource(script, sqlScriptEncoding); else {
return new EncodedResource(script, this.sqlScriptEncoding);
} }
} }
@ -95,6 +102,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
*/ */
private void executeSqlScript(Connection connection, EncodedResource resource, boolean continueOnError) private void executeSqlScript(Connection connection, EncodedResource resource, boolean continueOnError)
throws SQLException { throws SQLException {
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info("Executing SQL script from " + resource); logger.info("Executing SQL script from " + resource);
} }
@ -112,25 +120,34 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
} }
splitSqlScript(script, delimiter, statements); splitSqlScript(script, delimiter, statements);
int lineNumber = 0; int lineNumber = 0;
for (String statement : statements) { Statement stmt = connection.createStatement();
lineNumber++; try {
Statement stmt = null; for (String statement : statements) {
try { lineNumber++;
stmt = connection.createStatement(); try {
int rowsAffected = stmt.executeUpdate(statement); int rowsAffected = stmt.executeUpdate(statement);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(rowsAffected + " rows affected by SQL: " + statement); logger.debug(rowsAffected + " rows affected by SQL: " + statement);
}
} catch (SQLException e) {
if (continueOnError) {
if (logger.isWarnEnabled()) {
logger.warn("Line " + lineNumber + " statement failed: " + statement, e);
} }
} else {
throw e;
} }
} finally { catch (SQLException ex) {
JdbcUtils.closeStatement(stmt); if (continueOnError) {
if (logger.isWarnEnabled()) {
logger.warn("Line " + lineNumber + " statement failed: " + statement, ex);
}
}
else {
throw ex;
}
}
}
}
finally {
try {
stmt.close();
}
catch (Throwable ex) {
logger.debug("Could not close JDBC Statement", ex);
} }
} }
long elapsedTime = System.currentTimeMillis() - startTime; long elapsedTime = System.currentTimeMillis() - startTime;
@ -209,4 +226,4 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
} }
} }
} }

View File

@ -13,35 +13,39 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.jdbc.datasource.embedded; package org.springframework.jdbc.datasource.embedded;
import java.sql.Driver;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.springframework.jdbc.datasource.SimpleDriverDataSource; import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.util.Assert;
/** /**
* Creates a {@link SimpleDriverDataSource}. * Creates a {@link SimpleDriverDataSource}.
*
* @author Keith Donald * @author Keith Donald
* @author Juergen Hoeller
* @since 3.0 * @since 3.0
*/ */
final class SimpleDriverDataSourceFactory implements DataSourceFactory { final class SimpleDriverDataSourceFactory implements DataSourceFactory {
private SimpleDriverDataSource dataSource = new SimpleDriverDataSource(); private final SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
public ConnectionProperties getConnectionProperties() { public ConnectionProperties getConnectionProperties() {
return new ConnectionProperties() { return new ConnectionProperties() {
public void setDriverClass(Class<?> driverClass) { @SuppressWarnings("unchecked")
dataSource.setDriverClass(driverClass); public void setDriverClass(Class driverClass) {
Assert.isAssignable(Driver.class, driverClass);
dataSource.setDriverClass((Class<? extends Driver>) driverClass);
} }
public void setUrl(String url) { public void setUrl(String url) {
dataSource.setUrl(url); dataSource.setUrl(url);
} }
public void setUsername(String username) { public void setUsername(String username) {
dataSource.setUsername(username); dataSource.setUsername(username);
} }
public void setPassword(String password) { public void setPassword(String password) {
dataSource.setPassword(password); dataSource.setPassword(password);
} }
@ -49,7 +53,7 @@ final class SimpleDriverDataSourceFactory implements DataSourceFactory {
} }
public DataSource getDataSource() { public DataSource getDataSource() {
return dataSource; return this.dataSource;
} }
} }