eliminated dependency on jdbc core
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
package org.springframework.jdbc.datasource.embedded;
|
||||
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
|
||||
public class CannotReadScriptException extends RuntimeException {
|
||||
|
||||
public CannotReadScriptException(EncodedResource resource, Throwable cause) {
|
||||
super("Cannot read SQL script from " + resource, cause);
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
package org.springframework.jdbc.datasource.embedded;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Strategy for populating a database with data.
|
||||
@@ -27,8 +27,8 @@ public interface DatabasePopulator {
|
||||
|
||||
/**
|
||||
* Populate the database using the JDBC-based data access template provided.
|
||||
* @param template the data access template to use to populate the db; already configured and ready to use
|
||||
* @throws DataAccessException if an unrecoverable data access exception occurs during database population
|
||||
* @param connection the JDBC connection to use to populate the db; already configured and ready to use
|
||||
* @throws SQLException if an unrecoverable data access exception occurs during database population
|
||||
*/
|
||||
void populate(JdbcTemplate template);
|
||||
void populate(Connection connection) throws SQLException;
|
||||
}
|
||||
@@ -23,24 +23,20 @@ import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Returns a {@link EmbeddedDatabase} instance pre-populated with test data.
|
||||
* When the database is returned, callers are guaranteed that the database schema and test data will have already been loaded.
|
||||
* Returns a {@link EmbeddedDatabase} instance pre-populated with test data. When the database is returned, callers are
|
||||
* guaranteed that the database schema and test data will have already been loaded.
|
||||
* <p>
|
||||
* Can be configured:<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 #setDatabaseConfigurer(EmbeddedDatabaseConfigurer)} to set a configuration strategy for your own embedded database type.<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 #getDatabase()} to get the {@link EmbeddedDatabase} instance.<br>
|
||||
*
|
||||
* Can be configured.
|
||||
* Call {@link #setDatabaseName(String)} to change the name of the database.
|
||||
* Call {@link #setDatabaseType(EmbeddedDatabaseType)} to set the database type if you wish to use one of the supported types.
|
||||
* Call {@link #setDatabaseConfigurer(EmbeddedDatabaseConfigurer)} to set a configuration strategy for your own embedded database type.
|
||||
* Call {@link #setDatabasePopulator(DatabasePopulator)} to change the algorithm used to populate the database.
|
||||
* Call {@link #setDataSourceFactory(DataSourceFactory)} to change the type of DataSource used to connect to the database.
|
||||
* Call {@link #getDatabase()} to get the {@link EmbeddedDatabase} instance.
|
||||
* @author Keith Donald
|
||||
*/
|
||||
public class EmbeddedDatabaseFactory {
|
||||
@@ -50,36 +46,34 @@ public class EmbeddedDatabaseFactory {
|
||||
private String databaseName;
|
||||
|
||||
private DataSourceFactory dataSourceFactory;
|
||||
|
||||
|
||||
private EmbeddedDatabaseConfigurer databaseConfigurer;
|
||||
|
||||
private DatabasePopulator databasePopulator;
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a default {@link EmbeddedDatabaseFactory}.
|
||||
* Calling {@link #getDatabase()} will create a embedded HSQL database of name 'testdb'.
|
||||
* Creates a default {@link EmbeddedDatabaseFactory}. Calling {@link #getDatabase()} will create a embedded HSQL
|
||||
* database of name 'testdb'.
|
||||
*/
|
||||
public EmbeddedDatabaseFactory() {
|
||||
setDatabaseName("testdb");
|
||||
setDatabaseType(EmbeddedDatabaseType.HSQL);
|
||||
setDataSourceFactory(new SimpleDriverDataSourceFactory());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the name of the database.
|
||||
* Defaults to 'testdb'.
|
||||
* Sets the name of the database. Defaults to 'testdb'.
|
||||
* @param name of the test database
|
||||
*/
|
||||
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.
|
||||
* 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
|
||||
*/
|
||||
@@ -88,18 +82,17 @@ public class EmbeddedDatabaseFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets 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.
|
||||
* Sets 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
|
||||
*/
|
||||
public void setDatabaseConfigurer(EmbeddedDatabaseConfigurer configurer) {
|
||||
this.databaseConfigurer = configurer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the strategy that will be used to populate the embedded database.
|
||||
* Defaults to null.
|
||||
* @param populator the database populator
|
||||
* Sets the strategy that will be used to populate the embedded database. Defaults to null.
|
||||
* @param populator the database populator
|
||||
*/
|
||||
public void setDatabasePopulator(DatabasePopulator populator) {
|
||||
Assert.notNull(populator, "The DatabasePopulator is required");
|
||||
@@ -107,8 +100,8 @@ public class EmbeddedDatabaseFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the factory to use to create the DataSource instance that connects to the embedded database.
|
||||
* Defaults to {@link SimpleDriverDataSourceFactory}.
|
||||
* Sets 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) {
|
||||
@@ -129,7 +122,7 @@ public class EmbeddedDatabaseFactory {
|
||||
}
|
||||
|
||||
// subclassing hooks
|
||||
|
||||
|
||||
protected void initDataSource() {
|
||||
// create the embedded database source first
|
||||
if (logger.isInfoEnabled()) {
|
||||
@@ -146,26 +139,27 @@ public class EmbeddedDatabaseFactory {
|
||||
protected DataSource getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
|
||||
protected void shutdownDataSource() {
|
||||
if (dataSource != null) {
|
||||
databaseConfigurer.shutdown(dataSource);
|
||||
dataSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// internal helper methods
|
||||
|
||||
private void populateDatabase() {
|
||||
TransactionTemplate template = new TransactionTemplate(new DataSourceTransactionManager(dataSource));
|
||||
template.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
databasePopulator.populate(new JdbcTemplate(dataSource));
|
||||
}
|
||||
});
|
||||
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 DataSource dataSource;
|
||||
|
||||
@@ -177,8 +171,7 @@ public class EmbeddedDatabaseFactory {
|
||||
return 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);
|
||||
}
|
||||
|
||||
@@ -205,11 +198,11 @@ public class EmbeddedDatabaseFactory {
|
||||
public <T> T unwrap(Class<T> iface) throws SQLException {
|
||||
return dataSource.unwrap(iface);
|
||||
}
|
||||
|
||||
|
||||
public void shutdown() {
|
||||
shutdownDataSource();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -15,13 +15,20 @@
|
||||
*/
|
||||
package org.springframework.jdbc.datasource.embedded;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
public class HsqlEmbeddedDatabaseConfigurer implements EmbeddedDatabaseConfigurer {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(HsqlEmbeddedDatabaseConfigurer.class);
|
||||
|
||||
private static HsqlEmbeddedDatabaseConfigurer INSTANCE;
|
||||
|
||||
public static synchronized HsqlEmbeddedDatabaseConfigurer getInstance() throws ClassNotFoundException {
|
||||
@@ -40,7 +47,18 @@ public class HsqlEmbeddedDatabaseConfigurer implements EmbeddedDatabaseConfigure
|
||||
}
|
||||
|
||||
public void shutdown(DataSource dataSource) {
|
||||
new JdbcTemplate(dataSource).execute("SHUTDOWN");
|
||||
Connection connection = JdbcUtils.getConnection(dataSource);
|
||||
Statement stmt = null;
|
||||
try {
|
||||
stmt = connection.createStatement();
|
||||
stmt.execute("SHUTDOWN");
|
||||
} catch (SQLException e) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Could not shutdown in-memory HSQL database", e);
|
||||
}
|
||||
} finally {
|
||||
JdbcUtils.closeStatement(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
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. There is some duplication here with JdbcUtils in
|
||||
* jdbc.support package. We may want to consider simply using that. Package private for now.
|
||||
* @author Keith Donald
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,9 @@ package org.springframework.jdbc.datasource.embedded;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.LineNumberReader;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -25,19 +28,15 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Populates a database from schema and test-data SQL defined in external resources.
|
||||
* By default, looks for a schema.sql file and test-data.sql resource in the root of the classpath.
|
||||
* Populates a database from schema and test-data SQL defined in external resources. By default, looks for a schema.sql
|
||||
* file and test-data.sql resource in the root of the classpath.
|
||||
*
|
||||
* May be configured.
|
||||
* Call {@link #setSchemaLocation(Resource)} to configure the location of the database schema file.
|
||||
* Call {@link #setTestDataLocation(Resource)} to configure the location of the test data file.
|
||||
* Call {@link #setSqlScriptEncoding(String)} to set the encoding for the schema and test data SQL.
|
||||
* May be configured. Call {@link #setSchemaLocation(Resource)} to configure the location of the database schema file.
|
||||
* Call {@link #setTestDataLocation(Resource)} to configure the location of the test data file. Call
|
||||
* {@link #setSqlScriptEncoding(String)} to set the encoding for the schema and test data SQL.
|
||||
*/
|
||||
public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
|
||||
@@ -46,7 +45,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
private String sqlScriptEncoding;
|
||||
|
||||
private List<Resource> scripts = new ArrayList<Resource>();
|
||||
|
||||
|
||||
/**
|
||||
* Add a script to execute to populate the database.
|
||||
* @param script the path to a SQL script
|
||||
@@ -54,7 +53,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
public void addScript(Resource script) {
|
||||
scripts.add(script);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specify the encoding for SQL scripts, if different from the platform encoding.
|
||||
*/
|
||||
@@ -62,68 +61,64 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
this.sqlScriptEncoding = sqlScriptEncoding;
|
||||
}
|
||||
|
||||
public void populate(JdbcTemplate template) {
|
||||
public void populate(Connection connection) throws SQLException {
|
||||
for (Resource script : scripts) {
|
||||
executeSqlScript(template, new EncodedResource(script, sqlScriptEncoding), false);
|
||||
executeSqlScript(connection, new EncodedResource(script, sqlScriptEncoding), false);
|
||||
}
|
||||
}
|
||||
|
||||
// From SimpleJdbcTestUtils - TODO address duplication
|
||||
|
||||
/**
|
||||
* Execute the given SQL script.
|
||||
* <p>The script will normally be loaded by classpath. There should be one statement
|
||||
* per line. Any semicolons will be removed. <b>Do not use this method to execute
|
||||
* DDL if you expect rollback.</b>
|
||||
* Execute the given SQL script. <p>The script will normally be loaded by classpath. There should be one statement
|
||||
* per line. Any semicolons will be removed. <b>Do not use this method to execute DDL if you expect rollback.</b>
|
||||
* @param template the SimpleJdbcTemplate with which to perform JDBC operations
|
||||
* @param resource the resource (potentially associated with a specific encoding)
|
||||
* to load the SQL script from.
|
||||
* @param continueOnError whether or not to continue without throwing an
|
||||
* exception in the event of an error.
|
||||
* @param resource the resource (potentially associated with a specific encoding) to load the SQL script from.
|
||||
* @param continueOnError whether or not to continue without throwing an exception in the event of an error.
|
||||
*/
|
||||
static void executeSqlScript(JdbcTemplate template, EncodedResource resource, boolean continueOnError) {
|
||||
private void executeSqlScript(Connection connection, EncodedResource resource, boolean continueOnError)
|
||||
throws SQLException {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Executing SQL script from " + resource);
|
||||
}
|
||||
long startTime = System.currentTimeMillis();
|
||||
List<String> statements = new LinkedList<String>();
|
||||
String script;
|
||||
try {
|
||||
LineNumberReader lnr = new LineNumberReader(resource.getReader());
|
||||
String script = readScript(lnr);
|
||||
char delimiter = ';';
|
||||
if (!containsSqlScriptDelimiters(script, delimiter)) {
|
||||
delimiter = '\n';
|
||||
}
|
||||
splitSqlScript(script, delimiter, statements);
|
||||
for (String statement : statements) {
|
||||
try {
|
||||
int rowsAffected = template.update(statement);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(rowsAffected + " rows affected by SQL: " + statement);
|
||||
}
|
||||
script = readScript(resource);
|
||||
} catch (IOException e) {
|
||||
throw new CannotReadScriptException(resource, e);
|
||||
}
|
||||
char delimiter = ';';
|
||||
if (!containsSqlScriptDelimiters(script, delimiter)) {
|
||||
delimiter = '\n';
|
||||
}
|
||||
splitSqlScript(script, delimiter, statements);
|
||||
int lineNumber = 0;
|
||||
for (String statement : statements) {
|
||||
lineNumber++;
|
||||
Statement stmt = null;
|
||||
try {
|
||||
stmt = connection.createStatement();
|
||||
int rowsAffected = stmt.executeUpdate(statement);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(rowsAffected + " rows affected by SQL: " + statement);
|
||||
}
|
||||
catch (DataAccessException ex) {
|
||||
if (continueOnError) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("SQL: " + statement + " failed", ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw ex;
|
||||
} catch (SQLException e) {
|
||||
if (continueOnError) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Line " + lineNumber + " statement failed: " + statement, e);
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Done executing SQL script from " + resource + " in " + elapsedTime + " ms.");
|
||||
} finally {
|
||||
JdbcUtils.closeStatement(stmt);
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new DataAccessResourceFailureException("Failed to open SQL script from " + resource, ex);
|
||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Done executing SQL script from " + resource + " in " + elapsedTime + " ms.");
|
||||
}
|
||||
}
|
||||
|
||||
// From JdbcTestUtils - TODO address duplication - these do not seem as useful as the one above
|
||||
|
||||
/**
|
||||
* Read a script from the LineNumberReader and build a String containing the lines.
|
||||
@@ -131,8 +126,9 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
* @return <code>String</code> containing the script lines
|
||||
* @throws IOException
|
||||
*/
|
||||
private static String readScript(LineNumberReader lineNumberReader) throws IOException {
|
||||
String currentStatement = lineNumberReader.readLine();
|
||||
private static String readScript(EncodedResource resource) throws IOException {
|
||||
LineNumberReader lnr = new LineNumberReader(resource.getReader());
|
||||
String currentStatement = lnr.readLine();
|
||||
StringBuilder scriptBuilder = new StringBuilder();
|
||||
while (currentStatement != null) {
|
||||
if (StringUtils.hasText(currentStatement)) {
|
||||
@@ -141,7 +137,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
}
|
||||
scriptBuilder.append(currentStatement);
|
||||
}
|
||||
currentStatement = lineNumberReader.readLine();
|
||||
currentStatement = lnr.readLine();
|
||||
}
|
||||
return scriptBuilder.toString();
|
||||
}
|
||||
@@ -166,8 +162,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Split an SQL script into separate statements delimited with the provided delimiter character. Each
|
||||
* individual statement will be added to the provided <code>List</code>.
|
||||
* Split an SQL script into separate statements delimited with the provided delimiter character. Each individual
|
||||
* statement will be added to the provided <code>List</code>.
|
||||
* @param script the SQL script
|
||||
* @param delim charecter delimiting each statement - typically a ';' character
|
||||
* @param statements the List that will contain the individual statements
|
||||
@@ -185,8 +181,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
|
||||
statements.add(sb.toString());
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
sb.append(content[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user