Browse Source

Refactor SQL script support

This commit continues the work in the previous commit as follows:

 - Introduced an exception hierarchy for exceptions related to SQL
   scripts, with ScriptException as the base.

 - CannotReadScriptException and ScriptStatementFailedException now
   extend ScriptException.

 - Introduced ScriptParseException, used by ScriptUtils.splitSqlScript().

 - DatabasePopulatorUtils.execute() now explicitly throws a
   DataAccessException.

 - Polished Javadoc in ResourceDatabasePopulator.

 - Overhauled Javadoc in ScriptUtils and documented all constants.

 - Added missing @author tags for original authors in ScriptUtils and
   ScriptUtilsTests.

 - ScriptUtils.splitSqlScript() now asserts preconditions.

 - Deleted superfluous methods in ScriptUtils and changed method
   visibility to private or package private as appropriate.

 - Deleted the ScriptStatementExecutor introduced in the previous
   commit; ScriptUtils.executeSqlScript() now accepts a JDBC Connection;
   JdbcTestUtils, AbstractTransactionalJUnit4SpringContextTests, and
   AbstractTransactionalTestNGSpringContextTests now use
   DatabasePopulatorUtils to execute a ResourceDatabasePopulator instead
   of executing a script directly via ScriptUtils.

 - Introduced JdbcTestUtilsIntegrationTests.

Issue: SPR-9531
pull/448/merge
Sam Brannen 12 years ago
parent
commit
2bfd6ddcf4
  1. 4
      spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java
  2. 8
      spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CannotReadScriptException.java
  3. 15
      spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulatorUtils.java
  4. 79
      spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java
  5. 45
      spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptException.java
  6. 54
      spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptParseException.java
  7. 12
      spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptStatementFailedException.java
  8. 298
      spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java
  9. 6
      spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java
  10. 104
      spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsTests.java
  11. 23
      spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java
  12. 23
      spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java
  13. 77
      spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java
  14. 63
      spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsIntegrationTests.java
  15. 13
      spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java
  16. 1
      spring-test/src/test/resources/org/springframework/test/jdbc/data.sql
  17. 4
      spring-test/src/test/resources/org/springframework/test/jdbc/schema.sql

4
spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@ -31,7 +31,7 @@ import org.springframework.util.ObjectUtils; @@ -31,7 +31,7 @@ import org.springframework.util.ObjectUtils;
* with a specific encoding to be used for reading from the resource.
*
* <p>Used as argument for operations that support to read content with
* a specific encoding (usually through a {@code java.io.Reader}.
* a specific encoding (usually through a {@code java.io.Reader}).
*
* @author Juergen Hoeller
* @since 1.2.6

8
spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CannotReadScriptException.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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.
@ -19,14 +19,14 @@ package org.springframework.jdbc.datasource.init; @@ -19,14 +19,14 @@ package org.springframework.jdbc.datasource.init;
import org.springframework.core.io.support.EncodedResource;
/**
* Thrown by {@link ResourceDatabasePopulator} if one of its SQL scripts cannot
* be read during population.
* Thrown by {@link ScriptUtils} if an SQL script cannot be read.
*
* @author Keith Donald
* @author Sam Brannen
* @since 3.0
*/
@SuppressWarnings("serial")
public class CannotReadScriptException extends RuntimeException {
public class CannotReadScriptException extends ScriptException {
/**
* Construct a new {@code CannotReadScriptException}.

15
spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulatorUtils.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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.
@ -20,25 +20,28 @@ import java.sql.Connection; @@ -20,25 +20,28 @@ import java.sql.Connection;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.util.Assert;
/**
* Utility methods for executing a DatabasePopulator.
* Utility methods for executing a {@link DatabasePopulator}.
*
* @author Juergen Hoeller
* @author Oliver Gierke
* @author Sam Brannen
* @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
* Execute the given {@link DatabasePopulator} against the given {@link DataSource}.
* @param populator the {@code DatabasePopulator} to execute
* @param dataSource the {@code DataSource} to execute against
* @throws DataAccessException if an error occurs
*/
public static void execute(DatabasePopulator populator, DataSource dataSource) {
public static void execute(DatabasePopulator populator, DataSource dataSource) throws DataAccessException {
Assert.notNull(populator, "DatabasePopulator must be provided");
Assert.notNull(dataSource, "DataSource must be provided");
try {

79
spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java

@ -18,16 +18,12 @@ package org.springframework.jdbc.datasource.init; @@ -18,16 +18,12 @@ package org.springframework.jdbc.datasource.init;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.jdbc.datasource.init.ScriptUtils.ScriptStatementExecutor;
/**
* Populates a database from SQL scripts defined in external resources.
@ -55,9 +51,9 @@ public class ResourceDatabasePopulator implements DatabasePopulator { @@ -55,9 +51,9 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
private String commentPrefix = ScriptUtils.DEFAULT_COMMENT_PREFIX;
private String blockCommentStartDelimiter = ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER;
private String blockCommentEndDelimiter = ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER;
private boolean continueOnError = false;
private boolean ignoreFailedDrops = false;
@ -65,7 +61,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator { @@ -65,7 +61,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
/**
* Add a script to execute to populate the database.
* @param script the path to a SQL script
* @param script the path to an SQL script
*/
public void addScript(Resource script) {
this.scripts.add(script);
@ -81,8 +77,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator { @@ -81,8 +77,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
/**
* 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}.
* <p>Note that setting this property has no effect on added scripts that are
* already {@linkplain EncodedResource encoded resources}.
* @see #addScript(Resource)
*/
public void setSqlScriptEncoding(String sqlScriptEncoding) {
@ -90,41 +86,46 @@ public class ResourceDatabasePopulator implements DatabasePopulator { @@ -90,41 +86,46 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
}
/**
* Specify the statement separator, if a custom one. Default is ";".
* Specify the statement separator, if a custom one.
* <p>Default is ";".
*/
public void setSeparator(String separator) {
this.separator = separator;
}
/**
* Set the line prefix that identifies comments in the SQL script.
* Default is "--".
* Set the prefix that identifies line comments within the SQL scripts.
* <p>Default is "--".
*/
public void setCommentPrefix(String commentPrefix) {
this.commentPrefix = commentPrefix;
}
/**
* Set the block comment start delimiter in the SQL script.
* Default is "/*"
* Set the start delimiter that identifies block comments within the SQL
* scripts.
* <p>Default is "/*".
* @since 4.0.3
* @see #setBlockCommentEndDelimiter
*/
public void setBlockCommentStartDelimiter(String blockCommentStartDelimiter) {
this.blockCommentStartDelimiter = blockCommentStartDelimiter;
this.blockCommentStartDelimiter = blockCommentStartDelimiter;
}
/**
* Set the block comment end delimiter in the SQL script.
* Default is "*\/"
* Set the end delimiter that identifies block comments within the SQL
* scripts.
* <p>Default is "*&#47;".
* @since 4.0.3
* @see #setBlockCommentStartDelimiter
*/
public void setBlockCommentEndDelimiter(String blockCommentEndDelimiter) {
this.blockCommentEndDelimiter = blockCommentEndDelimiter;
}
/**
* Flag to indicate that all failures in SQL should be logged but not cause a failure.
* Defaults to false.
* <p>Defaults to {@code false}.
*/
public void setContinueOnError(boolean continueOnError) {
this.continueOnError = continueOnError;
@ -133,44 +134,20 @@ public class ResourceDatabasePopulator implements DatabasePopulator { @@ -133,44 +134,20 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
/**
* Flag to indicate that a failed SQL {@code DROP} statement can be ignored.
* <p>This is useful for non-embedded databases whose SQL dialect does not support an
* {@code IF EXISTS} clause in a {@code DROP}. The default is false so that if the
* populator runs accidentally, it will fail fast when the script starts with a {@code DROP}.
* {@code IF EXISTS} clause in a {@code DROP} statement.
* <p>The default is {@code false} so that if the populator runs accidentally, it will
* fail fast if the script starts with a {@code DROP} statement.
*/
public void setIgnoreFailedDrops(boolean ignoreFailedDrops) {
this.ignoreFailedDrops = ignoreFailedDrops;
}
@Override
public void populate(Connection connection) throws SQLException {
Statement statement = null;
try {
statement = connection.createStatement();
final Statement stmt = statement;
for (Resource script : this.scripts) {
ScriptUtils.executeSqlScript(
new ScriptStatementExecutor() {
@Override
public int executeScriptStatement(String statement) throws DataAccessException {
try {
stmt.execute(statement);
return stmt.getUpdateCount();
}
catch (SQLException e) {
throw new UncategorizedSQLException(getClass().getName(), statement, e);
}
}
},
applyEncodingIfNecessary(script), this.continueOnError, this.ignoreFailedDrops,
this.commentPrefix, this.separator, this.blockCommentStartDelimiter,
this.blockCommentEndDelimiter);
}
}
finally {
if (statement != null) {
statement.close();
}
for (Resource script : this.scripts) {
ScriptUtils.executeSqlScript(connection, applyEncodingIfNecessary(script), this.continueOnError,
this.ignoreFailedDrops, this.commentPrefix, this.separator, this.blockCommentStartDelimiter,
this.blockCommentEndDelimiter);
}
}

45
spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptException.java

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
/*
* Copyright 2002-2014 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;
/**
* Root of the hierarchy of SQL script exceptions.
*
* @author Sam Brannen
* @since 4.0.3
*/
@SuppressWarnings("serial")
public abstract class ScriptException extends RuntimeException {
/**
* Constructor for {@code ScriptException}.
* @param message the detail message
*/
public ScriptException(String message) {
super(message);
}
/**
* Constructor for {@code ScriptException}.
* @param message the detail message
* @param cause the root cause
*/
public ScriptException(String message, Throwable cause) {
super(message, cause);
}
}

54
spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptParseException.java

@ -0,0 +1,54 @@ @@ -0,0 +1,54 @@
/*
* Copyright 2002-2014 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 org.springframework.core.io.support.EncodedResource;
/**
* Thrown by {@link ScriptUtils} if an SQL script cannot be properly parsed.
*
* @author Sam Brannen
* @since 4.0.3
*/
@SuppressWarnings("serial")
public class ScriptParseException extends ScriptException {
/**
* Construct a new {@code ScriptParseException}.
* @param message detailed message
* @param resource the resource from which the SQL script was read
*/
public ScriptParseException(String message, EncodedResource resource) {
super(buildMessage(message, resource));
}
/**
* Construct a new {@code ScriptParseException}.
* @param message detailed message
* @param resource the resource from which the SQL script was read
* @param cause the underlying cause of the failure
*/
public ScriptParseException(String message, EncodedResource resource, Throwable cause) {
super(buildMessage(message, resource), cause);
}
private static String buildMessage(String message, EncodedResource resource) {
return String.format("Failed to parse SQL script from resource [%s]: %s", (resource == null ? "<unknown>"
: resource), message);
}
}

12
spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptStatementFailedException.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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.
@ -19,15 +19,15 @@ package org.springframework.jdbc.datasource.init; @@ -19,15 +19,15 @@ package org.springframework.jdbc.datasource.init;
import org.springframework.core.io.support.EncodedResource;
/**
* Thrown by {@link ResourceDatabasePopulator} if a statement in one of its SQL scripts
* failed when executing it against the target database.
* Thrown by {@link ScriptUtils} if a statement in an SQL script failed when
* executing it against the target database.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0.5
*/
@SuppressWarnings("serial")
public class ScriptStatementFailedException extends RuntimeException {
public class ScriptStatementFailedException extends ScriptException {
/**
* Construct a new {@code ScriptStatementFailedException}.
@ -37,8 +37,8 @@ public class ScriptStatementFailedException extends RuntimeException { @@ -37,8 +37,8 @@ public class ScriptStatementFailedException extends RuntimeException {
* @param cause the underlying cause of the failure
*/
public ScriptStatementFailedException(String statement, int lineNumber, EncodedResource resource, Throwable cause) {
super("Failed to execute SQL script statement at line " + lineNumber +
" of resource " + resource + ": " + statement, cause);
super("Failed to execute SQL script statement at line " + lineNumber + " of resource " + resource + ": "
+ statement, cause);
}
}

298
spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java

@ -18,73 +18,119 @@ package org.springframework.jdbc.datasource.init; @@ -18,73 +18,119 @@ package org.springframework.jdbc.datasource.init;
import java.io.IOException;
import java.io.LineNumberReader;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Generic utility methods for working with SQL scripts. Mainly for
* internal use within the framework.
*
* Generic utility methods for working with SQL scripts. Mainly for internal use
* within the framework.
*
* @author Thomas Risberg
* @author Sam Brannen
* @author Juergen Hoeller
* @author Keith Donald
* @author Dave Syer
* @author Chris Beams
* @author Oliver Gierke
* @author Chris Baldwin
* @since 4.0.3
*/
public abstract class ScriptUtils {
private static final Log logger = LogFactory.getLog(ScriptUtils.class);
/**
* Default statement separator within SQL scripts.
*/
public static final String DEFAULT_STATEMENT_SEPARATOR = ";";
/**
* Default prefix for line comments within SQL scripts.
*/
public static final String DEFAULT_COMMENT_PREFIX = "--";
public static final char DEFAULT_STATEMENT_SEPARATOR_CHAR = ';';
public static final String DEFAULT_STATEMENT_SEPARATOR = String.valueOf(DEFAULT_STATEMENT_SEPARATOR_CHAR);
/**
* Default start delimiter for block comments within SQL scripts.
*/
public static final String DEFAULT_BLOCK_COMMENT_START_DELIMITER = "/*";
/**
* Default end delimiter for block comments within SQL scripts.
*/
public static final String DEFAULT_BLOCK_COMMENT_END_DELIMITER = "*/";
private static final Log logger = LogFactory.getLog(ScriptUtils.class);
/**
* Prevent instantiation of this utility class.
*/
private ScriptUtils() {
/* no-op */
}
/**
* Split an SQL script into separate statements delimited by the provided
* delimiter character. Each individual statement will be added to the
* provided {@code List}.
* <p>Within a statement, "{@code --}" will be used as the comment prefix;
* any text beginning with the comment prefix and extending to the end of
* the line will be omitted from the statement. In addition, multiple adjacent
* whitespace characters will be collapsed into a single space.
* <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the
* comment prefix; any text beginning with the comment prefix and extending to
* the end of the line will be omitted from the output. Similarly,
* {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and
* {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the
* <em>start</em> and <em>end</em> block comment delimiters: any text enclosed
* in a block comment will be omitted from the output. In addition, multiple
* adjacent whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param delim character delimiting each statement &mdash; typically a ';' character
* @param delimiter character delimiting each statement &mdash; typically a ';' character
* @param statements the list that will contain the individual statements
* @since 4.0.3
* @see #splitSqlScript(EncodedResource, String, String, String, String, String, List)
*/
public static void splitSqlScript(String script, char delim, List<String> statements) {
splitSqlScript(script, String.valueOf(delim), DEFAULT_COMMENT_PREFIX, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
DEFAULT_BLOCK_COMMENT_END_DELIMITER, statements);
public static void splitSqlScript(String script, char delimiter, List<String> statements) throws ScriptException {
splitSqlScript(null, script, String.valueOf(delimiter), DEFAULT_COMMENT_PREFIX,
DEFAULT_BLOCK_COMMENT_START_DELIMITER, DEFAULT_BLOCK_COMMENT_END_DELIMITER, statements);
}
/**
* Split an SQL script into separate statements delimited by the provided
* delimiter string. Each individual statement will be added to the provided
* {@code List}.
* <p>Within a statement, the provided {@code commentPrefix} will be honored;
* <p>Within the script, the provided {@code commentPrefix} will be honored:
* any text beginning with the comment prefix and extending to the end of the
* line will be omitted from the statement. In addition, multiple adjacent
* whitespace characters will be collapsed into a single space.
* @param script the SQL script
* @param delim character delimiting each statement &mdash; typically a ';' character
* @param commentPrefix the prefix that identifies line comments in the SQL script &mdash; typically "--"
* @param statements the List that will contain the individual statements
* @since 4.0.3
* line will be omitted from the output. Similarly, the provided
* {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
* delimiters will be honored: any text enclosed in a block comment will be
* omitted from the output. In addition, multiple adjacent whitespace characters
* will be collapsed into a single space.
* @param resource the resource from which the script was read
* @param script the SQL script; never {@code null} or empty
* @param delimiter text delimiting each statement &mdash; typically a ';'
* character; never {@code null}
* @param commentPrefix the prefix that identifies SQL line comments &mdash;
* typically "--"; never {@code null} or empty
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
* never {@code null} or empty
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
* never {@code null} or empty
* @param statements the list that will contain the individual statements
*/
public static void splitSqlScript(String script, String delim, String commentPrefix, String blockCommentOpen,
String blockCommentClose, List<String> statements) {
public static void splitSqlScript(EncodedResource resource, String script, String delimiter, String commentPrefix,
String blockCommentStartDelimiter, String blockCommentEndDelimiter, List<String> statements)
throws ScriptException {
Assert.hasText(script, "script must not be null or empty");
Assert.notNull(delimiter, "delimiter must not be null");
Assert.hasText(commentPrefix, "commentPrefix must not be null or empty");
Assert.hasText(blockCommentStartDelimiter, "blockCommentStartDelimiter must not be null or empty");
Assert.hasText(blockCommentEndDelimiter, "blockCommentEndDelimiter must not be null or empty");
StringBuilder sb = new StringBuilder();
boolean inLiteral = false;
boolean inEscape = false;
@ -106,13 +152,13 @@ public abstract class ScriptUtils { @@ -106,13 +152,13 @@ public abstract class ScriptUtils {
inLiteral = !inLiteral;
}
if (!inLiteral) {
if (script.startsWith(delim, i)) {
if (script.startsWith(delimiter, i)) {
// we've reached the end of the current statement
if (sb.length() > 0) {
statements.add(sb.toString());
sb = new StringBuilder();
}
i += delim.length() - 1;
i += delimiter.length() - 1;
continue;
}
else if (script.startsWith(commentPrefix, i)) {
@ -128,16 +174,16 @@ public abstract class ScriptUtils { @@ -128,16 +174,16 @@ public abstract class ScriptUtils {
break;
}
}
else if (script.startsWith(blockCommentOpen, i)) {
else if (script.startsWith(blockCommentStartDelimiter, i)) {
// skip over any block comments
int indexOfCommentClose = script.indexOf(blockCommentClose, i);
if (indexOfCommentClose > i) {
i = indexOfCommentClose + blockCommentClose.length() - 1;
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
if (indexOfCommentEnd > i) {
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
continue;
}
else {
throw new BadSqlGrammarException("", script.substring(i),
new SQLException("Missing block comment end delimiter"));
throw new ScriptParseException(String.format("Missing block comment end delimiter [%s].",
blockCommentEndDelimiter), resource);
}
}
else if (c == ' ' || c == '\n' || c == '\t') {
@ -158,52 +204,32 @@ public abstract class ScriptUtils { @@ -158,52 +204,32 @@ public abstract class ScriptUtils {
}
/**
* Read a script from the provided {@code LineNumberReader}, using
* "{@code --}" as the comment prefix, and build a {@code String} containing
* the lines.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @return a {@code String} containing the script lines
* @see #readScript(LineNumberReader, String, String)
* @since 4.0.3
*/
public static String readScript(LineNumberReader lineNumberReader) throws IOException {
return readScript(lineNumberReader, DEFAULT_COMMENT_PREFIX, DEFAULT_STATEMENT_SEPARATOR);
}
/**
* Read a script from the given resource, using "{@code --}" as the comment prefix
* and "{@code ;} as the statement separator, and build a String containing the lines.
* Read a script from the given resource, using "{@code --}" as the comment prefix
* and "{@code ;}" as the statement separator, and build a String containing the lines.
* @param resource the {@code EncodedResource} to be read
* @return {@code String} containing the script lines
* @throws IOException in case of I/O errors
* @since 4.0.3
*/
public static String readScript(EncodedResource resource) throws IOException {
LineNumberReader lnr = new LineNumberReader(resource.getReader());
try {
return readScript(lnr, DEFAULT_COMMENT_PREFIX, DEFAULT_STATEMENT_SEPARATOR);
}
finally {
lnr.close();
}
static String readScript(EncodedResource resource) throws IOException {
return readScript(resource, DEFAULT_COMMENT_PREFIX, DEFAULT_STATEMENT_SEPARATOR);
}
/**
* Read a script from the provided resource, using the supplied
* comment prefix and statement separator, and build a {@code String} containing the lines.
* comment prefix and statement separator, and build a {@code String} containing
* the lines.
* <p>Lines <em>beginning</em> with the comment prefix are excluded from the
* results; however, line comments anywhere else &mdash; for example, within
* a statement &mdash; will be included in the results.
* @param resource the {@code EncodedResource} containing the script
* to be processed
* @param commentPrefix the prefix that identifies comments in the SQL script &mdash; typically "--"
* @param commentPrefix the prefix that identifies comments in the SQL script &mdash;
* typically "--"
* @param separator the statement separator in the SQL script &mdash; typically ";"
* @return a {@code String} containing the script lines
* @since 4.0.3
*/
public static String readScript(EncodedResource resource, String commentPrefix,
String separator) throws IOException {
private static String readScript(EncodedResource resource, String commentPrefix, String separator)
throws IOException {
LineNumberReader lnr = new LineNumberReader(resource.getReader());
try {
return readScript(lnr, commentPrefix, separator);
@ -212,27 +238,28 @@ public abstract class ScriptUtils { @@ -212,27 +238,28 @@ public abstract class ScriptUtils {
lnr.close();
}
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefix and statement separator, and build a {@code String} containing the lines.
* comment prefix and statement separator, and build a {@code String} containing
* the lines.
* <p>Lines <em>beginning</em> with the comment prefix are excluded from the
* results; however, line comments anywhere else &mdash; for example, within
* a statement &mdash; will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param commentPrefix the prefix that identifies comments in the SQL script &mdash; typically "--"
* @param commentPrefix the prefix that identifies comments in the SQL script &mdash;
* typically "--"
* @param separator the statement separator in the SQL script &mdash; typically ";"
* @return a {@code String} containing the script lines
* @since 4.0.3
*/
public static String readScript(LineNumberReader lineNumberReader, String commentPrefix,
String separator) throws IOException {
public static String readScript(LineNumberReader lineNumberReader, String commentPrefix, String separator)
throws IOException {
String currentStatement = lineNumberReader.readLine();
StringBuilder scriptBuilder = new StringBuilder();
while (currentStatement != null) {
if (StringUtils.hasText(currentStatement) &&
(commentPrefix != null && !currentStatement.startsWith(commentPrefix))) {
if (StringUtils.hasText(currentStatement)
&& (commentPrefix != null && !currentStatement.startsWith(commentPrefix))) {
if (scriptBuilder.length() > 0) {
scriptBuilder.append('\n');
}
@ -259,21 +286,10 @@ public abstract class ScriptUtils { @@ -259,21 +286,10 @@ public abstract class ScriptUtils {
}
}
/**
* Does the provided SQL script contain the specified delimiter?
* @param script the SQL script
* @param delim character delimiting each statement - typically a ';' character
* @since 4.0.3
*/
public static boolean containsSqlScriptDelimiters(String script, char delim) {
return containsSqlScriptDelimiters(script, String.valueOf(delim));
}
/**
* Does the provided SQL script contain the specified delimiter?
* @param script the SQL script
* @param delim String delimiting each statement - typically a ';' character
* @since 4.0.3
*/
public static boolean containsSqlScriptDelimiters(String script, String delim) {
boolean inLiteral = false;
@ -291,24 +307,29 @@ public abstract class ScriptUtils { @@ -291,24 +307,29 @@ public abstract class ScriptUtils {
/**
* Execute the given SQL script.
* <p>The script will normally be loaded by classpath. There should be one statement
* per line. Any statement separators will be removed.
* <p>Statement separators and comments will be removed before executing
* individual statements within the supplied script.
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
* @param executor the {@code ScriptStatementExecutor} 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 ignoreFailedDrops whether of not to continue in the event of specifically an error on a {@code DROP}
* @param commentPrefix the script line comment prefix
* if not specified
* @param separator the script statement separator, defaults to {@code DEFAUT_STATEMENT_SEPARATOR}
* if not specified
* @param blockCommentStartDelim the script block comment starting delimiter
* @param blockCommentEndDelim the script block comment ending delimiter
* @since 4.0.3
* @param connection the JDBC connection to use to execute the script; already
* configured and ready to use
* @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 ignoreFailedDrops whether or not to continue in the event of specifically
* an error on a {@code DROP} statement
* @param commentPrefix the prefix that identifies comments in the SQL script &mdash;
* typically "--"
* @param separator the script statement separator; defaults to
* {@value #DEFAULT_STATEMENT_SEPARATOR} if not specified
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter; never
* {@code null} or empty
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter; never
* {@code null} or empty
*/
public static void executeSqlScript(ScriptStatementExecutor executor, EncodedResource resource,
boolean continueOnError, boolean ignoreFailedDrops, String commentPrefix, String separator,
String blockCommentStartDelim, String blockCommentEndDelim) throws DataAccessException {
public static void executeSqlScript(Connection connection, EncodedResource resource, boolean continueOnError,
boolean ignoreFailedDrops, String commentPrefix, String separator, String blockCommentStartDelimiter,
String blockCommentEndDelimiter) throws SQLException, ScriptException {
if (logger.isInfoEnabled()) {
logger.info("Executing SQL script from " + resource);
@ -322,54 +343,55 @@ public abstract class ScriptUtils { @@ -322,54 +343,55 @@ public abstract class ScriptUtils {
catch (IOException ex) {
throw new CannotReadScriptException(resource, ex);
}
if (separator == null) {
separator = DEFAULT_STATEMENT_SEPARATOR;
if (!containsSqlScriptDelimiters(script, separator)) {
separator = "\n";
}
}
splitSqlScript(script, separator, commentPrefix, blockCommentStartDelim, blockCommentEndDelim, statements);
splitSqlScript(resource, script, separator, commentPrefix, blockCommentStartDelimiter,
blockCommentEndDelimiter, statements);
int lineNumber = 0;
for (String statement : statements) {
lineNumber++;
try {
int rowsAffected = executor.executeScriptStatement(statement);
if (logger.isDebugEnabled()) {
logger.debug(rowsAffected + " returned as updateCount for SQL: " + statement);
}
}
catch (DataAccessException ex) {
boolean dropStatement = StringUtils.startsWithIgnoreCase(statement.trim(), "drop");
if (continueOnError || (dropStatement && ignoreFailedDrops)) {
Statement stmt = connection.createStatement();
try {
for (String statement : statements) {
lineNumber++;
try {
stmt.execute(statement);
int rowsAffected = stmt.getUpdateCount();
if (logger.isDebugEnabled()) {
logger.debug("Failed to execute SQL script statement at line " + lineNumber +
" of resource " + resource + ": " + statement, ex);
logger.debug(rowsAffected + " returned as updateCount for SQL: " + statement);
}
}
else {
throw new ScriptStatementFailedException(statement, lineNumber, resource, ex);
catch (SQLException ex) {
boolean dropStatement = StringUtils.startsWithIgnoreCase(statement.trim(), "drop");
if (continueOnError || (dropStatement && ignoreFailedDrops)) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to execute SQL script statement at line " + lineNumber
+ " of resource " + resource + ": " + statement, ex);
}
}
else {
throw new ScriptStatementFailedException(statement, lineNumber, resource, ex);
}
}
}
}
finally {
try {
stmt.close();
}
catch (Throwable ex) {
logger.debug("Could not close JDBC Statement", ex);
}
}
long elapsedTime = System.currentTimeMillis() - startTime;
if (logger.isInfoEnabled()) {
logger.info("Done executing SQL script from " + resource + " in " + elapsedTime + " ms.");
logger.info("Executed SQL script from " + resource + " in " + elapsedTime + " ms.");
}
}
/**
* Interface to be implemented by an object so that {@code executeScript()} is able to use
* it to execute script statements.
* @since 4.0.3
*/
public interface ScriptStatementExecutor
{
/**
* Execute the given SQL statement and return a count of the number of affected rows.
* @return the number of rows affected by the statement
* @throws DataAccessException if there is a problem during statement execution
*/
public int executeScriptStatement(String statement) throws DataAccessException;
}
}

6
spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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.
@ -40,9 +40,7 @@ import static org.mockito.BDDMockito.*; @@ -40,9 +40,7 @@ import static org.mockito.BDDMockito.*;
*/
public class DatabasePopulatorTests {
private final EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
private final EmbeddedDatabase db = builder.build();
private final EmbeddedDatabase db = new EmbeddedDatabaseBuilder().build();
private final ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();

104
spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsTests.java

@ -16,10 +16,8 @@ @@ -16,10 +16,8 @@
package org.springframework.jdbc.datasource.init;
import java.io.LineNumberReader;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
@ -27,8 +25,6 @@ import org.junit.After; @@ -27,8 +25,6 @@ import org.junit.After;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
@ -37,20 +33,19 @@ import org.springframework.transaction.support.TransactionSynchronizationManager @@ -37,20 +33,19 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* JUnit test cases for ScriptUtils.
*
* Unit and integration tests for {@link ScriptUtils}.
*
* @author Thomas Risberg
* @author Sam Brannen
* @author Phillip Webb
* @author Chris Baldwin
*/
public class ScriptUtilsTests {
private final EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
private final EmbeddedDatabase db = new EmbeddedDatabaseBuilder().build();
private final EmbeddedDatabase db = builder.build();
private final JdbcTemplate jdbcTemplate = new JdbcTemplate(db);
@After
public void shutDown() {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
@ -95,11 +90,9 @@ public class ScriptUtilsTests { @@ -95,11 +90,9 @@ public class ScriptUtilsTests {
@Test
public void readAndSplitScriptContainingComments() throws Exception {
EncodedResource resource = new EncodedResource(new ClassPathResource("test-data-with-comments.sql", getClass()));
LineNumberReader lineNumberReader = new LineNumberReader(resource.getReader());
String script = ScriptUtils.readScript(lineNumberReader);
String script = ScriptUtils.readScript(resource);
char delim = ';';
List<String> statements = new ArrayList<String>();
@ -123,12 +116,10 @@ public class ScriptUtilsTests { @@ -123,12 +116,10 @@ public class ScriptUtilsTests {
*/
@Test
public void readAndSplitScriptContainingCommentsWithLeadingTabs() throws Exception {
EncodedResource resource = new EncodedResource(new ClassPathResource(
"test-data-with-comments-and-leading-tabs.sql", getClass()));
LineNumberReader lineNumberReader = new LineNumberReader(resource.getReader());
String script = ScriptUtils.readScript(lineNumberReader);
String script = ScriptUtils.readScript(resource);
char delim = ';';
List<String> statements = new ArrayList<String>();
@ -143,24 +134,24 @@ public class ScriptUtilsTests { @@ -143,24 +134,24 @@ public class ScriptUtilsTests {
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
assertEquals("statement 3 not split correctly", statement3, statements.get(2));
}
/**
* See <a href="https://jira.springsource.org/browse/SPR-9531">SPR-9531</a>
*/
@Test
public void readAndSplitScriptContainingMuliLineComments() throws Exception {
EncodedResource resource = new EncodedResource(new ClassPathResource(
"test-data-with-multi-line-comments.sql", getClass()));
EncodedResource resource = new EncodedResource(new ClassPathResource("test-data-with-multi-line-comments.sql",
getClass()));
String script = ScriptUtils.readScript(resource);
char delim = ';';
List<String> statements = new ArrayList<String>();
ScriptUtils.splitSqlScript(script, delim, statements);
String statement1 = "INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller')";
String statement2 = "INSERT INTO users(first_name, last_name) VALUES( 'Sam' , 'Brannen' )";
assertEquals("wrong number of statements", 2, statements.size());
assertEquals("statement 1 not split correctly", statement1, statements.get(0));
assertEquals("statement 2 not split correctly", statement2, statements.get(1));
@ -168,71 +159,38 @@ public class ScriptUtilsTests { @@ -168,71 +159,38 @@ public class ScriptUtilsTests {
@Test
public void containsDelimiters() {
assertTrue("test with ';' is wrong", !ScriptUtils.containsSqlScriptDelimiters("select 1\n select ';'", ';'));
assertTrue("test with delimiter ; is wrong",
ScriptUtils.containsSqlScriptDelimiters("select 1; select 2", ';'));
assertTrue("test with ';' is wrong", !ScriptUtils.containsSqlScriptDelimiters("select 1\n select ';'", ";"));
assertTrue("test with delimiter ; is wrong", ScriptUtils.containsSqlScriptDelimiters("select 1; select 2", ";"));
assertTrue("test with '\\n' is wrong",
!ScriptUtils.containsSqlScriptDelimiters("select 1; select '\\n\n';", '\n'));
!ScriptUtils.containsSqlScriptDelimiters("select 1; select '\\n\n';", "\n"));
assertTrue("test with delimiter \\n is wrong",
ScriptUtils.containsSqlScriptDelimiters("select 1\n select 2", '\n'));
ScriptUtils.containsSqlScriptDelimiters("select 1\n select 2", "\n"));
}
@Test
public void executeSqlScript() throws SQLException {
EncodedResource schemaResource = new EncodedResource(new ClassPathResource(
"users-schema.sql", getClass()));
EncodedResource schemaResource = new EncodedResource(new ClassPathResource("users-schema.sql", getClass()));
EncodedResource commentResource = new EncodedResource(new ClassPathResource(
"test-data-with-multi-line-comments.sql", getClass()));
"test-data-with-multi-line-comments.sql", getClass()));
Connection connection = db.getConnection();
Statement stmt = connection.createStatement();
TestScriptStatementExecutor executor = new TestScriptStatementExecutor(stmt);
try {
ScriptUtils.executeSqlScript(executor, schemaResource, false, false, ScriptUtils.DEFAULT_COMMENT_PREFIX,
ScriptUtils.DEFAULT_STATEMENT_SEPARATOR, ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER,
ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER);
ScriptUtils.executeSqlScript(executor, commentResource, false, false, ScriptUtils.DEFAULT_COMMENT_PREFIX,
ScriptUtils.DEFAULT_STATEMENT_SEPARATOR, ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER,
ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
finally {
stmt.close();
connection.close();
}
ScriptUtils.executeSqlScript(connection, schemaResource, false, false, ScriptUtils.DEFAULT_COMMENT_PREFIX,
ScriptUtils.DEFAULT_STATEMENT_SEPARATOR, ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER,
ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER);
ScriptUtils.executeSqlScript(connection, commentResource, false, false, ScriptUtils.DEFAULT_COMMENT_PREFIX,
ScriptUtils.DEFAULT_STATEMENT_SEPARATOR, ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER,
ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER);
assertUsersDatabaseCreated("Hoeller", "Brannen");
}
private void assertUsersDatabaseCreated(String... lastNames) {
final JdbcTemplate jdbcTemplate = new JdbcTemplate(db);
for (String lastName : lastNames) {
assertThat("Did not find user with last name [" + lastName + "].",
jdbcTemplate.queryForObject("select count(0) from users where last_name = ?", Integer.class, lastName),
equalTo(1));
}
}
private class TestScriptStatementExecutor implements ScriptUtils.ScriptStatementExecutor
{
Statement stmt;
public TestScriptStatementExecutor(Statement stmt) {
super();
this.stmt = stmt;
}
/* (non-Javadoc)
* @see org.springframework.jdbc.datasource.init.ScriptUtils.ScriptStatementExecutor#executeScriptStatement(java.lang.String)
*/
@Override
public int executeScriptStatement(String statement) throws DataAccessException {
try {
stmt.execute(statement);
return stmt.getUpdateCount();
}
catch (SQLException e) {
throw new UncategorizedSQLException(getClass().getName(), statement, e);
}
}
}
}

23
spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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,9 +21,10 @@ import javax.sql.DataSource; @@ -21,9 +21,10 @@ import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
@ -171,22 +172,24 @@ public abstract class AbstractTransactionalJUnit4SpringContextTests extends Abst @@ -171,22 +172,24 @@ public abstract class AbstractTransactionalJUnit4SpringContextTests extends Abst
/**
* Execute the given SQL script.
* <p>Use with caution outside of a transaction!
* <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>
* <p>The script will normally be loaded by classpath.
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
* @param sqlResourcePath the Spring resource path for the SQL script
* @param continueOnError whether or not to continue without throwing an
* exception in the event of an error
* @throws DataAccessException if there is an error executing a statement
* and continueOnError was {@code false}
* @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean)
* @see ResourceDatabasePopulator
* @see DatabasePopulatorUtils
* @see #setSqlScriptEncoding
*/
@SuppressWarnings("deprecation")
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
Resource resource = this.applicationContext.getResource(sqlResourcePath);
JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding),
continueOnError);
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
databasePopulator.setContinueOnError(continueOnError);
databasePopulator.addScript(resource);
databasePopulator.setSqlScriptEncoding(this.sqlScriptEncoding);
DatabasePopulatorUtils.execute(databasePopulator, jdbcTemplate.getDataSource());
}
}

23
spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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,9 +21,10 @@ import javax.sql.DataSource; @@ -21,9 +21,10 @@ import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.jdbc.JdbcTestUtils;
@ -162,22 +163,24 @@ public abstract class AbstractTransactionalTestNGSpringContextTests extends Abst @@ -162,22 +163,24 @@ public abstract class AbstractTransactionalTestNGSpringContextTests extends Abst
/**
* Execute the given SQL script.
* <p>Use with caution outside of a transaction!
* <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>
* <p>The script will normally be loaded by classpath.
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
* @param sqlResourcePath the Spring resource path for the SQL script
* @param continueOnError whether or not to continue without throwing an
* exception in the event of an error
* @throws DataAccessException if there is an error executing a statement
* and continueOnError was {@code false}
* @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean)
* @see ResourceDatabasePopulator
* @see DatabasePopulatorUtils
* @see #setSqlScriptEncoding
*/
@SuppressWarnings("deprecation")
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
Resource resource = this.applicationContext.getResource(sqlResourcePath);
JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding),
continueOnError);
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
databasePopulator.setContinueOnError(continueOnError);
databasePopulator.addScript(resource);
databasePopulator.setSqlScriptEncoding(this.sqlScriptEncoding);
DatabasePopulatorUtils.execute(databasePopulator, jdbcTemplate.getDataSource());
}
}

77
spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java

@ -22,15 +22,16 @@ import java.util.List; @@ -22,15 +22,16 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.jdbc.datasource.init.ScriptUtils.ScriptStatementExecutor;
import org.springframework.util.StringUtils;
/**
@ -48,6 +49,7 @@ public class JdbcTestUtils { @@ -48,6 +49,7 @@ public class JdbcTestUtils {
private static final Log logger = LogFactory.getLog(JdbcTestUtils.class);
/**
* Count the rows in the given table.
* @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
@ -117,14 +119,13 @@ public class JdbcTestUtils { @@ -117,14 +119,13 @@ public class JdbcTestUtils {
* optionally the scale.
* @return the number of rows deleted from the table
*/
public static int deleteFromTableWhere(JdbcTemplate jdbcTemplate, String tableName,
String whereClause, Object... args) {
public static int deleteFromTableWhere(JdbcTemplate jdbcTemplate, String tableName, String whereClause,
Object... args) {
String sql = "DELETE FROM " + tableName;
if (StringUtils.hasText(whereClause)) {
sql += " WHERE " + whereClause;
}
int rowCount = (args != null && args.length > 0 ? jdbcTemplate.update(sql, args)
: jdbcTemplate.update(sql));
int rowCount = (args != null && args.length > 0 ? jdbcTemplate.update(sql, args) : jdbcTemplate.update(sql));
if (logger.isInfoEnabled()) {
logger.info("Deleted " + rowCount + " rows from table " + tableName);
}
@ -158,9 +159,11 @@ public class JdbcTestUtils { @@ -158,9 +159,11 @@ public class JdbcTestUtils {
* @throws DataAccessException if there is an error executing a statement
* and {@code continueOnError} is {@code false}
* @see ResourceDatabasePopulator
* @see DatabasePopulatorUtils
* @see #executeSqlScript(JdbcTemplate, Resource, boolean)
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#executeSqlScript(ScriptStatementExecutor, EncodedResource, boolean, boolean, String, String, String, String)}
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#executeSqlScript}
* or {@link org.springframework.jdbc.datasource.init.ResourceDatabasePopulator}.
*/
@Deprecated
public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader,
@ -184,9 +187,11 @@ public class JdbcTestUtils { @@ -184,9 +187,11 @@ public class JdbcTestUtils {
* @throws DataAccessException if there is an error executing a statement
* and {@code continueOnError} is {@code false}
* @see ResourceDatabasePopulator
* @see DatabasePopulatorUtils
* @see #executeSqlScript(JdbcTemplate, EncodedResource, boolean)
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#executeSqlScript(ScriptStatementExecutor, EncodedResource, boolean, boolean, String, String, String, String)}
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#executeSqlScript}
* or {@link org.springframework.jdbc.datasource.init.ResourceDatabasePopulator}.
*/
@Deprecated
public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource, boolean continueOnError)
@ -207,36 +212,22 @@ public class JdbcTestUtils { @@ -207,36 +212,22 @@ public class JdbcTestUtils {
* @throws DataAccessException if there is an error executing a statement
* and {@code continueOnError} is {@code false}
* @see ResourceDatabasePopulator
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#executeSqlScript(ScriptStatementExecutor, EncodedResource, boolean, boolean, String, String, String, String)}
* @see DatabasePopulatorUtils
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#executeSqlScript}
* or {@link org.springframework.jdbc.datasource.init.ResourceDatabasePopulator}.
*/
@Deprecated
public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource resource, boolean continueOnError)
throws DataAccessException {
ScriptUtils.executeSqlScript(new JdbcTemplateScriptStatementExecutor(jdbcTemplate), resource,
continueOnError, continueOnError, ScriptUtils.DEFAULT_COMMENT_PREFIX,
ScriptUtils.DEFAULT_STATEMENT_SEPARATOR, ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER,
ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER);
}
private static class JdbcTemplateScriptStatementExecutor implements ScriptStatementExecutor {
private JdbcTemplate jdbcTemplate;
public JdbcTemplateScriptStatementExecutor(JdbcTemplate jdbcTemplate) {
super();
this.jdbcTemplate = jdbcTemplate;
}
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
databasePopulator.setContinueOnError(continueOnError);
databasePopulator.addScript(resource.getResource());
databasePopulator.setSqlScriptEncoding(resource.getEncoding());
/* (non-Javadoc)
* @see org.springframework.jdbc.support.JdbcUtils.ScriptStatementExecutor#executeScriptStatement(java.lang.String)
*/
@Override
public int executeScriptStatement(String statement) throws DataAccessException {
return jdbcTemplate.update(statement);
}
DatabasePopulatorUtils.execute(databasePopulator, jdbcTemplate.getDataSource());
}
/**
* Read a script from the provided {@code LineNumberReader}, using
* "{@code --}" as the comment prefix, and build a {@code String} containing
@ -245,14 +236,14 @@ public class JdbcTestUtils { @@ -245,14 +236,14 @@ public class JdbcTestUtils {
* to be processed
* @return a {@code String} containing the script lines
* @see #readScript(LineNumberReader, String)
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#readScript(LineNumberReader)}
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#readScript(LineNumberReader, String, String)}
*/
@Deprecated
public static String readScript(LineNumberReader lineNumberReader) throws IOException {
return ScriptUtils.readScript(lineNumberReader);
return readScript(lineNumberReader, ScriptUtils.DEFAULT_COMMENT_PREFIX);
}
/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefix, and build a {@code String} containing the lines.
@ -263,7 +254,7 @@ public class JdbcTestUtils { @@ -263,7 +254,7 @@ public class JdbcTestUtils {
* to be processed
* @param commentPrefix the prefix that identifies comments in the SQL script &mdash; typically "--"
* @return a {@code String} containing the script lines
* @deprecated as of Spring 4.0.3, in favor of using
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#readScript(LineNumberReader, String, String)}
*/
@Deprecated
@ -276,14 +267,14 @@ public class JdbcTestUtils { @@ -276,14 +267,14 @@ public class JdbcTestUtils {
* @param script the SQL script
* @param delim character delimiting each statement &mdash; typically a ';' character
* @return {@code true} if the script contains the delimiter; {@code false} otherwise
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#containsSqlScriptDelimiters(String, char)}
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#containsSqlScriptDelimiters}
*/
@Deprecated
public static boolean containsSqlScriptDelimiters(String script, char delim) {
return ScriptUtils.containsSqlScriptDelimiters(script, delim);
return ScriptUtils.containsSqlScriptDelimiters(script, String.valueOf(delim));
}
/**
* Split an SQL script into separate statements delimited by the provided
* delimiter character. Each individual statement will be added to the
@ -295,7 +286,7 @@ public class JdbcTestUtils { @@ -295,7 +286,7 @@ public class JdbcTestUtils {
* @param script the SQL script
* @param delim character delimiting each statement &mdash; typically a ';' character
* @param statements the list that will contain the individual statements
* @deprecated as of Spring 4.0.3, in favor of using
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#splitSqlScript(String, char, List)}
*/
@Deprecated

63
spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsIntegrationTests.java

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
/*
* Copyright 2002-2014 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.test.jdbc;
import java.util.Arrays;
import org.junit.After;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import static org.junit.Assert.*;
/**
* Integration tests for {@link JdbcTestUtils}.
*
* @author Sam Brannen
* @since 4.0.3
* @see JdbcTestUtilsTests
*/
public class JdbcTestUtilsIntegrationTests {
private final EmbeddedDatabase db = new EmbeddedDatabaseBuilder().build();
private JdbcTemplate jdbcTemplate = new JdbcTemplate(db);
@After
public void shutdown() {
db.shutdown();
}
@Test
@SuppressWarnings("deprecation")
public void executeSqlScriptsAndcountRowsInTableWhere() throws Exception {
for (String script : Arrays.asList("schema.sql", "data.sql")) {
Resource resource = new ClassPathResource(script, getClass());
JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource), false);
}
assertEquals(1, JdbcTestUtils.countRowsInTableWhere(jdbcTemplate, "person", "name = 'bob'"));
}
}

13
spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java

@ -16,24 +16,22 @@ @@ -16,24 +16,22 @@
package org.springframework.test.jdbc;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.BDDMockito.given;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.jdbc.core.JdbcTemplate;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
/**
* Unit tests for {@link JdbcTestUtils}.
*
* @author Thomas Risberg
* @author Sam Brannen
* @author Phillip Webb
* @author Chris Baldwin
* @since 2.5.4
* @see JdbcTestUtilsIntegrationTests
*/
@RunWith(MockitoJUnitRunner.class)
public class JdbcTestUtilsTests {
@ -41,6 +39,7 @@ public class JdbcTestUtilsTests { @@ -41,6 +39,7 @@ public class JdbcTestUtilsTests {
@Mock
private JdbcTemplate jdbcTemplate;
@Test
public void deleteWithoutWhereClause() throws Exception {
given(jdbcTemplate.update("DELETE FROM person")).willReturn(10);

1
spring-test/src/test/resources/org/springframework/test/jdbc/data.sql

@ -0,0 +1 @@ @@ -0,0 +1 @@
INSERT INTO person VALUES('bob');

4
spring-test/src/test/resources/org/springframework/test/jdbc/schema.sql

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
CREATE TABLE person (
name VARCHAR(20) NOT NULL,
PRIMARY KEY(name)
);
Loading…
Cancel
Save