From e5c17560db71996157f52baada68c8a294c95d10 Mon Sep 17 00:00:00 2001 From: cbaldwin74 Date: Thu, 6 Mar 2014 01:38:38 -0800 Subject: [PATCH 1/2] Support multi-line comments in SQL scripts Prior to this commit neither ResourceDatabasePopulator nor JdbcTestUtils properly supported multi-line comments (e.g., /* ... */). Secondarily there has developed a significant amount of code duplication in these two classes that has led to maintenance issues over the years. This commit addresses these issues as follows: - Common code has been extracted from ResourceDatabasePopulator and JdbcTestUtils and moved to a new ScriptUtils class in the spring-jdbc module. - Relevant test cases have been migrated from JdbcTestUtilsTests to ScriptUtilsTests. - ScriptUtils.splitSqlScript() has been modified to ignore multi-line comments in scripts during processing. - ResourceDatabasePopulator supports configuration of the start and end delimiters for multi-line (block) comments. - A new test case was added to ScriptUtilsTests for the new multi-line comment support. Issue: SPR-9531 --- .../init/ResourceDatabasePopulator.java | 286 +++---------- .../jdbc/datasource/init/ScriptUtils.java | 375 ++++++++++++++++++ .../datasource/init/ScriptUtilsTests.java | 238 +++++++++++ ...st-data-with-comments-and-leading-tabs.sql | 0 .../init}/test-data-with-comments.sql | 0 .../test-data-with-multi-line-comments.sql | 17 + ...TransactionalJUnit4SpringContextTests.java | 1 + ...TransactionalTestNGSpringContextTests.java | 1 + .../test/jdbc/JdbcTestUtils.java | 218 +++------- .../test/jdbc/JdbcTestUtilsTests.java | 108 +---- 10 files changed, 744 insertions(+), 500 deletions(-) create mode 100644 spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java create mode 100644 spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsTests.java rename {spring-test/src/test/resources/org/springframework/test/jdbc => spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init}/test-data-with-comments-and-leading-tabs.sql (100%) rename {spring-test/src/test/resources/org/springframework/test/jdbc => spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init}/test-data-with-comments.sql (100%) create mode 100644 spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/test-data-with-multi-line-comments.sql diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java index 84cf7b355b..05d7a7b84a 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java @@ -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. @@ -16,22 +16,18 @@ 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.ArrayList; import java.util.Arrays; -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.Resource; import org.springframework.core.io.support.EncodedResource; -import org.springframework.util.StringUtils; +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. @@ -45,25 +41,23 @@ import org.springframework.util.StringUtils; * @author Chris Beams * @author Oliver Gierke * @author Sam Brannen + * @author Chris Baldwin * @since 3.0 */ public class ResourceDatabasePopulator implements DatabasePopulator { - private static final String DEFAULT_COMMENT_PREFIX = "--"; - - private static final String DEFAULT_STATEMENT_SEPARATOR = ";"; - - private static final Log logger = LogFactory.getLog(ResourceDatabasePopulator.class); - - private List scripts = new ArrayList(); private String sqlScriptEncoding; - private String separator; + private String separator = ScriptUtils.DEFAULT_STATEMENT_SEPARATOR; - private String commentPrefix = DEFAULT_COMMENT_PREFIX; + 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; @@ -110,6 +104,24 @@ public class ResourceDatabasePopulator implements DatabasePopulator { this.commentPrefix = commentPrefix; } + /** + * Set the block comment start delimiter in the SQL script. + * Default is "/*" + * @since 4.0.3 + */ + public void setBlockCommentStartDelimiter(String blockCommentStartDelimiter) { + this.blockCommentStartDelimiter = blockCommentStartDelimiter; + } + + /** + * Set the block comment end delimiter in the SQL script. + * Default is "*\/" + * @since 4.0.3 + */ + 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. @@ -131,8 +143,34 @@ public class ResourceDatabasePopulator implements DatabasePopulator { @Override public void populate(Connection connection) throws SQLException { - for (Resource script : this.scripts) { - executeSqlScript(connection, applyEncodingIfNecessary(script), this.continueOnError, this.ignoreFailedDrops); + 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(); + } } } @@ -144,214 +182,4 @@ public class ResourceDatabasePopulator implements DatabasePopulator { return new EncodedResource(script, this.sqlScriptEncoding); } } - - /** - * Execute the given SQL script. - *

The script will normally be loaded by classpath. There should be one statement - * per line. Any {@link #setSeparator(String) statement separators} will be removed. - *

Do not use this method to execute DDL if you expect rollback. - * @param connection the JDBC Connection 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} - */ - private void executeSqlScript(Connection connection, EncodedResource resource, boolean continueOnError, - boolean ignoreFailedDrops) throws SQLException { - - if (logger.isInfoEnabled()) { - logger.info("Executing SQL script from " + resource); - } - long startTime = System.currentTimeMillis(); - List statements = new LinkedList(); - String script; - try { - script = readScript(resource); - } - catch (IOException ex) { - throw new CannotReadScriptException(resource, ex); - } - String delimiter = this.separator; - if (delimiter == null) { - delimiter = DEFAULT_STATEMENT_SEPARATOR; - if (!containsSqlScriptDelimiters(script, delimiter)) { - delimiter = "\n"; - } - } - splitSqlScript(script, delimiter, this.commentPrefix, statements); - int lineNumber = 0; - Statement stmt = connection.createStatement(); - try { - for (String statement : statements) { - lineNumber++; - try { - stmt.execute(statement); - int rowsAffected = stmt.getUpdateCount(); - if (logger.isDebugEnabled()) { - logger.debug(rowsAffected + " returned as updateCount for SQL: " + statement); - } - } - 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."); - } - } - - /** - * Read a script from the given resource and build a String containing the lines. - * @param resource the resource to be read - * @return {@code String} containing the script lines - * @throws IOException in case of I/O errors - */ - private String readScript(EncodedResource resource) throws IOException { - LineNumberReader lnr = new LineNumberReader(resource.getReader()); - try { - String currentStatement = lnr.readLine(); - StringBuilder scriptBuilder = new StringBuilder(); - while (currentStatement != null) { - if (StringUtils.hasText(currentStatement) && - (this.commentPrefix != null && !currentStatement.startsWith(this.commentPrefix))) { - if (scriptBuilder.length() > 0) { - scriptBuilder.append('\n'); - } - scriptBuilder.append(currentStatement); - } - currentStatement = lnr.readLine(); - } - maybeAddSeparatorToScript(scriptBuilder); - return scriptBuilder.toString(); - } - finally { - lnr.close(); - } - } - - private void maybeAddSeparatorToScript(StringBuilder scriptBuilder) { - if (this.separator == null) { - return; - } - String trimmed = this.separator.trim(); - if (trimmed.length() == this.separator.length()) { - return; - } - // separator ends in whitespace, so we might want to see if the script is trying - // to end the same way - if (scriptBuilder.lastIndexOf(trimmed) == scriptBuilder.length() - trimmed.length()) { - scriptBuilder.append(this.separator.substring(trimmed.length())); - } - } - - /** - * Does the provided SQL script contain the specified delimiter? - * @param script the SQL script - * @param delim character delimiting each statement - typically a ';' character - */ - private boolean containsSqlScriptDelimiters(String script, String delim) { - boolean inLiteral = false; - char[] content = script.toCharArray(); - for (int i = 0; i < script.length(); i++) { - if (content[i] == '\'') { - inLiteral = !inLiteral; - } - if (!inLiteral && script.startsWith(delim, i)) { - return true; - } - } - return false; - } - - /** - * Split an SQL script into separate statements delimited by the provided delimiter - * string. Each individual statement will be added to the provided {@code List}. - *

Within a statement, 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 (typically a ';' character) - * @param commentPrefix the prefix that identifies line comments in the SQL script — typically "--" - * @param statements the List that will contain the individual statements - */ - private void splitSqlScript(String script, String delim, String commentPrefix, List statements) { - StringBuilder sb = new StringBuilder(); - boolean inLiteral = false; - boolean inEscape = false; - char[] content = script.toCharArray(); - for (int i = 0; i < script.length(); i++) { - char c = content[i]; - if (inEscape) { - inEscape = false; - sb.append(c); - continue; - } - // MySQL style escapes - if (c == '\\') { - inEscape = true; - sb.append(c); - continue; - } - if (c == '\'') { - inLiteral = !inLiteral; - } - if (!inLiteral) { - if (script.startsWith(delim, 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; - continue; - } - else if (script.startsWith(commentPrefix, i)) { - // skip over any content from the start of the comment to the EOL - int indexOfNextNewline = script.indexOf("\n", i); - if (indexOfNextNewline > i) { - i = indexOfNextNewline; - continue; - } - else { - // if there's no newline after the comment, we must be at the end - // of the script, so stop here. - break; - } - } - else if (c == ' ' || c == '\n' || c == '\t') { - // avoid multiple adjacent whitespace characters - if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') { - c = ' '; - } - else { - continue; - } - } - } - sb.append(c); - } - if (StringUtils.hasText(sb)) { - statements.add(sb.toString()); - } - } - } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java new file mode 100644 index 0000000000..7b7886a000 --- /dev/null +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java @@ -0,0 +1,375 @@ +/* + * 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 java.io.IOException; +import java.io.LineNumberReader; +import java.sql.SQLException; +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.StringUtils; + + +/** + * Generic utility methods for working with SQL scripts. Mainly for + * internal use within the framework. + * + * @author Chris Baldwin + * @since 4.0.3 + */ +public abstract class ScriptUtils { + + 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); + + public static final String DEFAULT_BLOCK_COMMENT_START_DELIMITER = "/*"; + + public static final String DEFAULT_BLOCK_COMMENT_END_DELIMITER = "*/"; + + private static final Log logger = LogFactory.getLog(ScriptUtils.class); + + /** + * Split an SQL script into separate statements delimited by the provided + * delimiter character. Each individual statement will be added to the + * provided {@code List}. + *

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. + * @param script the SQL script + * @param delim character delimiting each statement — typically a ';' character + * @param statements the list that will contain the individual statements + * @since 4.0.3 + */ + public static void splitSqlScript(String script, char delim, List statements) { + splitSqlScript(script, String.valueOf(delim), 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}. + *

Within a statement, 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 — typically a ';' character + * @param commentPrefix the prefix that identifies line comments in the SQL script — typically "--" + * @param statements the List that will contain the individual statements + * @since 4.0.3 + */ + public static void splitSqlScript(String script, String delim, String commentPrefix, String blockCommentOpen, + String blockCommentClose, List statements) { + StringBuilder sb = new StringBuilder(); + boolean inLiteral = false; + boolean inEscape = false; + char[] content = script.toCharArray(); + for (int i = 0; i < script.length(); i++) { + char c = content[i]; + if (inEscape) { + inEscape = false; + sb.append(c); + continue; + } + // MySQL style escapes + if (c == '\\') { + inEscape = true; + sb.append(c); + continue; + } + if (c == '\'') { + inLiteral = !inLiteral; + } + if (!inLiteral) { + if (script.startsWith(delim, 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; + continue; + } + else if (script.startsWith(commentPrefix, i)) { + // skip over any content from the start of the comment to the EOL + int indexOfNextNewline = script.indexOf("\n", i); + if (indexOfNextNewline > i) { + i = indexOfNextNewline; + continue; + } + else { + // if there's no EOL, we must be at the end + // of the script, so stop here. + break; + } + } + else if (script.startsWith(blockCommentOpen, i)) { + // skip over any block comments + int indexOfCommentClose = script.indexOf(blockCommentClose, i); + if (indexOfCommentClose > i) { + i = indexOfCommentClose + blockCommentClose.length() - 1; + continue; + } + else { + throw new BadSqlGrammarException("", script.substring(i), + new SQLException("Missing block comment end delimiter")); + } + } + else if (c == ' ' || c == '\n' || c == '\t') { + // avoid multiple adjacent whitespace characters + if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') { + c = ' '; + } + else { + continue; + } + } + } + sb.append(c); + } + if (StringUtils.hasText(sb)) { + statements.add(sb.toString()); + } + } + + /** + * 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. + * @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(); + } + } + + /** + * Read a script from the provided resource, using the supplied + * comment prefix and statement separator, and build a {@code String} containing the lines. + *

Lines beginning with the comment prefix are excluded from the + * results; however, line comments anywhere else — for example, within + * a statement — 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 — typically "--" + * @param separator the statement separator in the SQL script — 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 { + LineNumberReader lnr = new LineNumberReader(resource.getReader()); + try { + return readScript(lnr, commentPrefix, separator); + } + finally { + 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. + *

Lines beginning with the comment prefix are excluded from the + * results; however, line comments anywhere else — for example, within + * a statement — 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 — typically "--" + * @param separator the statement separator in the SQL script — 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 { + String currentStatement = lineNumberReader.readLine(); + StringBuilder scriptBuilder = new StringBuilder(); + while (currentStatement != null) { + if (StringUtils.hasText(currentStatement) && + (commentPrefix != null && !currentStatement.startsWith(commentPrefix))) { + if (scriptBuilder.length() > 0) { + scriptBuilder.append('\n'); + } + scriptBuilder.append(currentStatement); + } + currentStatement = lineNumberReader.readLine(); + } + maybeAddSeparatorToScript(scriptBuilder, separator); + return scriptBuilder.toString(); + } + + private static void maybeAddSeparatorToScript(StringBuilder scriptBuilder, String separator) { + if (separator == null) { + return; + } + String trimmed = separator.trim(); + if (trimmed.length() == separator.length()) { + return; + } + // separator ends in whitespace, so we might want to see if the script is trying + // to end the same way + if (scriptBuilder.lastIndexOf(trimmed) == scriptBuilder.length() - trimmed.length()) { + scriptBuilder.append(separator.substring(trimmed.length())); + } + } + + /** + * 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; + char[] content = script.toCharArray(); + for (int i = 0; i < script.length(); i++) { + if (content[i] == '\'') { + inLiteral = !inLiteral; + } + if (!inLiteral && script.startsWith(delim, i)) { + return true; + } + } + return false; + } + + /** + * Execute the given SQL script. + *

The script will normally be loaded by classpath. There should be one statement + * per line. Any statement separators will be removed. + *

Do not use this method to execute DDL if you expect rollback. + * @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 + */ + public static void executeSqlScript(ScriptStatementExecutor executor, EncodedResource resource, + boolean continueOnError, boolean ignoreFailedDrops, String commentPrefix, String separator, + String blockCommentStartDelim, String blockCommentEndDelim) throws DataAccessException { + + if (logger.isInfoEnabled()) { + logger.info("Executing SQL script from " + resource); + } + long startTime = System.currentTimeMillis(); + List statements = new LinkedList(); + String script; + try { + script = readScript(resource, commentPrefix, separator); + } + 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); + 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)) { + 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); + } + } + } + long elapsedTime = System.currentTimeMillis() - startTime; + if (logger.isInfoEnabled()) { + logger.info("Done executing 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; + } + +} diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsTests.java new file mode 100644 index 0000000000..f7d9e9764a --- /dev/null +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsTests.java @@ -0,0 +1,238 @@ +/* + * 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 java.io.LineNumberReader; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; + +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; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + + +/** + * JUnit test cases for ScriptUtils. + * + * @author Chris Baldwin + */ +public class ScriptUtilsTests { + + private final EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); + + private final EmbeddedDatabase db = builder.build(); + + private final JdbcTemplate jdbcTemplate = new JdbcTemplate(db); + + @After + public void shutDown() { + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.clear(); + TransactionSynchronizationManager.unbindResource(db); + } + db.shutdown(); + } + + @Test + public void splitSqlScriptDelimitedWithSemicolon() { + String rawStatement1 = "insert into customer (id, name)\nvalues (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')"; + String cleanedStatement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')"; + String rawStatement2 = "insert into orders(id, order_date, customer_id)\nvalues (1, '2008-01-02', 2)"; + String cleanedStatement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; + String rawStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; + String cleanedStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; + char delim = ';'; + String script = rawStatement1 + delim + rawStatement2 + delim + rawStatement3 + delim; + List statements = new ArrayList(); + ScriptUtils.splitSqlScript(script, delim, statements); + assertEquals("wrong number of statements", 3, statements.size()); + assertEquals("statement 1 not split correctly", cleanedStatement1, statements.get(0)); + assertEquals("statement 2 not split correctly", cleanedStatement2, statements.get(1)); + assertEquals("statement 3 not split correctly", cleanedStatement3, statements.get(2)); + } + + @Test + public void splitSqlScriptDelimitedWithNewLine() { + String statement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')"; + String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; + String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; + char delim = '\n'; + String script = statement1 + delim + statement2 + delim + statement3 + delim; + List statements = new ArrayList(); + ScriptUtils.splitSqlScript(script, delim, statements); + assertEquals("wrong number of statements", 3, statements.size()); + assertEquals("statement 1 not split correctly", statement1, statements.get(0)); + assertEquals("statement 2 not split correctly", statement2, statements.get(1)); + assertEquals("statement 3 not split correctly", statement3, statements.get(2)); + } + + @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); + + char delim = ';'; + List statements = new ArrayList(); + ScriptUtils.splitSqlScript(script, delim, statements); + + String statement1 = "insert into customer (id, name) values (1, 'Rod; Johnson'), (2, 'Adrian Collier')"; + String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; + String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; + // Statement 4 addresses the error described in SPR-9982. + String statement4 = "INSERT INTO persons( person_id , name) VALUES( 1 , 'Name' )"; + + assertEquals("wrong number of statements", 4, statements.size()); + assertEquals("statement 1 not split correctly", statement1, statements.get(0)); + assertEquals("statement 2 not split correctly", statement2, statements.get(1)); + assertEquals("statement 3 not split correctly", statement3, statements.get(2)); + assertEquals("statement 4 not split correctly", statement4, statements.get(3)); + } + + /** + * See SPR-10330 + */ + @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); + + char delim = ';'; + List statements = new ArrayList(); + ScriptUtils.splitSqlScript(script, delim, statements); + + String statement1 = "insert into customer (id, name) values (1, 'Sam Brannen')"; + String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2013-06-08', 1)"; + String statement3 = "insert into orders(id, order_date, customer_id) values (2, '2013-06-08', 1)"; + + assertEquals("wrong number of statements", 3, statements.size()); + assertEquals("statement 1 not split correctly", statement1, statements.get(0)); + assertEquals("statement 2 not split correctly", statement2, statements.get(1)); + assertEquals("statement 3 not split correctly", statement3, statements.get(2)); + } + + /** + * See SPR-9531 + */ + @Test + public void readAndSplitScriptContainingMuliLineComments() throws Exception { + EncodedResource resource = new EncodedResource(new ClassPathResource( + "test-data-with-multi-line-comments.sql", getClass())); + + String script = ScriptUtils.readScript(resource); + + char delim = ';'; + List statements = new ArrayList(); + 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)); + } + + @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 '\\n' is wrong", + !ScriptUtils.containsSqlScriptDelimiters("select 1; select '\\n\n';", '\n')); + assertTrue("test with delimiter \\n is wrong", + 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 commentResource = new EncodedResource(new ClassPathResource( + "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(); + } + + assertUsersDatabaseCreated("Hoeller", "Brannen"); + } + + private void assertUsersDatabaseCreated(String... lastNames) { + 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); + } + } + } + +} diff --git a/spring-test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments-and-leading-tabs.sql b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/test-data-with-comments-and-leading-tabs.sql similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments-and-leading-tabs.sql rename to spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/test-data-with-comments-and-leading-tabs.sql diff --git a/spring-test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments.sql b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/test-data-with-comments.sql similarity index 100% rename from spring-test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments.sql rename to spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/test-data-with-comments.sql diff --git a/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/test-data-with-multi-line-comments.sql b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/test-data-with-multi-line-comments.sql new file mode 100644 index 0000000000..5886bd002e --- /dev/null +++ b/spring-jdbc/src/test/resources/org/springframework/jdbc/datasource/init/test-data-with-multi-line-comments.sql @@ -0,0 +1,17 @@ +/* This is a multi line comment + * The next comment line has no text + + * The next comment line starts with a space. + * x, y, z... + */ + +INSERT INTO users(first_name, last_name) VALUES('Juergen', 'Hoeller'); +-- This is also a comment. +/* + * Let's add another comment + * that covers multiple lines + */INSERT INTO +users(first_name, last_name) +VALUES( 'Sam' -- first_name + , 'Brannen' -- last_name +);-- \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java index c1494050a2..8bd80e7e23 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java @@ -182,6 +182,7 @@ public abstract class AbstractTransactionalJUnit4SpringContextTests extends Abst * @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean) * @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), diff --git a/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java index e71b82d529..0e1d6712b8 100644 --- a/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java +++ b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java @@ -173,6 +173,7 @@ public abstract class AbstractTransactionalTestNGSpringContextTests extends Abst * @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean) * @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), diff --git a/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java b/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java index 27d14b82ac..e3eab00efc 100644 --- a/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java +++ b/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java @@ -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. @@ -18,7 +18,6 @@ package org.springframework.test.jdbc; import java.io.IOException; import java.io.LineNumberReader; -import java.util.LinkedList; import java.util.List; import org.apache.commons.logging.Log; @@ -27,10 +26,11 @@ 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.dao.DataAccessResourceFailureException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.SqlParameterValue; 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; /** @@ -41,17 +41,13 @@ import org.springframework.util.StringUtils; * @author Sam Brannen * @author Juergen Hoeller * @author Phillip Webb + * @author Chris Baldwin * @since 2.5.4 */ public class JdbcTestUtils { private static final Log logger = LogFactory.getLog(JdbcTestUtils.class); - private static final String DEFAULT_COMMENT_PREFIX = "--"; - - private static final char DEFAULT_STATEMENT_SEPARATOR = ';'; - - /** * Count the rows in the given table. * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations @@ -163,7 +159,10 @@ public class JdbcTestUtils { * and {@code continueOnError} is {@code false} * @see ResourceDatabasePopulator * @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 public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader, String sqlResourcePath, boolean continueOnError) throws DataAccessException { Resource resource = resourceLoader.getResource(sqlResourcePath); @@ -186,7 +185,10 @@ public class JdbcTestUtils { * and {@code continueOnError} is {@code false} * @see ResourceDatabasePopulator * @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 public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource, boolean continueOnError) throws DataAccessException { executeSqlScript(jdbcTemplate, new EncodedResource(resource), continueOnError); @@ -205,65 +207,36 @@ 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)} */ + @Deprecated public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource resource, boolean continueOnError) throws DataAccessException { - - if (logger.isInfoEnabled()) { - logger.info("Executing SQL script from " + resource); - } - long startTime = System.currentTimeMillis(); - List statements = new LinkedList(); - LineNumberReader reader = null; - try { - reader = new LineNumberReader(resource.getReader()); - String script = readScript(reader); - char delimiter = DEFAULT_STATEMENT_SEPARATOR; - if (!containsSqlScriptDelimiters(script, delimiter)) { - delimiter = '\n'; - } - splitSqlScript(script, delimiter, statements); - int lineNumber = 0; - for (String statement : statements) { - lineNumber++; - try { - int rowsAffected = jdbcTemplate.update(statement); - if (logger.isDebugEnabled()) { - logger.debug(rowsAffected + " rows affected by SQL: " + statement); - } - } - catch (DataAccessException ex) { - if (continueOnError) { - if (logger.isWarnEnabled()) { - logger.warn("Failed to execute SQL script statement at line " + lineNumber - + " of resource " + resource + ": " + statement, ex); - } - } - else { - throw ex; - } - } - } - long elapsedTime = System.currentTimeMillis() - startTime; - if (logger.isInfoEnabled()) { - logger.info(String.format("Executed SQL script from %s in %s ms.", resource, elapsedTime)); - } - } - catch (IOException ex) { - throw new DataAccessResourceFailureException("Failed to open SQL script from " + resource, ex); - } - finally { - try { - if (reader != null) { - reader.close(); - } - } - catch (IOException ex) { - // ignore - } - } + 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; + } + + /* (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); + } + } + /** * Read a script from the provided {@code LineNumberReader}, using * "{@code --}" as the comment prefix, and build a {@code String} containing @@ -272,11 +245,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 public static String readScript(LineNumberReader lineNumberReader) throws IOException { - return readScript(lineNumberReader, DEFAULT_COMMENT_PREFIX); + return ScriptUtils.readScript(lineNumberReader); } - + /** * Read a script from the provided {@code LineNumberReader}, using the supplied * comment prefix, and build a {@code String} containing the lines. @@ -287,21 +263,12 @@ public class JdbcTestUtils { * to be processed * @param commentPrefix the prefix that identifies comments in the SQL script — typically "--" * @return a {@code String} containing the script lines + * @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, String commentPrefix) throws IOException { - String currentStatement = lineNumberReader.readLine(); - StringBuilder scriptBuilder = new StringBuilder(); - while (currentStatement != null) { - if (StringUtils.hasText(currentStatement) - && (commentPrefix != null && !currentStatement.startsWith(commentPrefix))) { - if (scriptBuilder.length() > 0) { - scriptBuilder.append('\n'); - } - scriptBuilder.append(currentStatement); - } - currentStatement = lineNumberReader.readLine(); - } - return scriptBuilder.toString(); + return ScriptUtils.readScript(lineNumberReader, commentPrefix, ScriptUtils.DEFAULT_STATEMENT_SEPARATOR); } /** @@ -309,21 +276,14 @@ public class JdbcTestUtils { * @param script the SQL script * @param delim character delimiting each statement — 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 public static boolean containsSqlScriptDelimiters(String script, char delim) { - boolean inLiteral = false; - char[] content = script.toCharArray(); - for (int i = 0; i < script.length(); i++) { - if (content[i] == '\'') { - inLiteral = !inLiteral; - } - if (content[i] == delim && !inLiteral) { - return true; - } - } - return false; + return ScriptUtils.containsSqlScriptDelimiters(script, delim); } - + /** * Split an SQL script into separate statements delimited by the provided * delimiter character. Each individual statement will be added to the @@ -335,83 +295,11 @@ public class JdbcTestUtils { * @param script the SQL script * @param delim character delimiting each statement — typically a ';' character * @param statements the list that will contain the individual statements + * @deprecated as of Spring 4.0.3, in favor of using + * {@link org.springframework.jdbc.datasource.init.ScriptUtils#splitSqlScript(String, char, List)} */ + @Deprecated public static void splitSqlScript(String script, char delim, List statements) { - splitSqlScript(script, "" + delim, DEFAULT_COMMENT_PREFIX, statements); + ScriptUtils.splitSqlScript(script, delim, 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}. - *

Within a statement, 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 — typically a ';' character - * @param commentPrefix the prefix that identifies line comments in the SQL script — typically "--" - * @param statements the List that will contain the individual statements - */ - private static void splitSqlScript(String script, String delim, String commentPrefix, List statements) { - StringBuilder sb = new StringBuilder(); - boolean inLiteral = false; - boolean inEscape = false; - char[] content = script.toCharArray(); - for (int i = 0; i < script.length(); i++) { - char c = content[i]; - if (inEscape) { - inEscape = false; - sb.append(c); - continue; - } - // MySQL style escapes - if (c == '\\') { - inEscape = true; - sb.append(c); - continue; - } - if (c == '\'') { - inLiteral = !inLiteral; - } - if (!inLiteral) { - if (script.startsWith(delim, 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; - continue; - } - else if (script.startsWith(commentPrefix, i)) { - // skip over any content from the start of the comment to the EOL - int indexOfNextNewline = script.indexOf("\n", i); - if (indexOfNextNewline > i) { - i = indexOfNextNewline; - continue; - } - else { - // if there's no newline after the comment, we must be at the end - // of the script, so stop here. - break; - } - } - else if (c == ' ' || c == '\n' || c == '\t') { - // avoid multiple adjacent whitespace characters - if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') { - c = ' '; - } - else { - continue; - } - } - } - sb.append(c); - } - if (StringUtils.hasText(sb)) { - statements.add(sb.toString()); - } - } - } diff --git a/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java b/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java index a67697165c..748e4a7277 100644 --- a/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java @@ -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. @@ -17,21 +17,13 @@ package org.springframework.test.jdbc; import static org.hamcrest.Matchers.equalTo; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; import static org.mockito.BDDMockito.given; -import java.io.LineNumberReader; -import java.util.ArrayList; -import java.util.List; - import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.support.EncodedResource; import org.springframework.jdbc.core.JdbcTemplate; /** @@ -40,6 +32,7 @@ import org.springframework.jdbc.core.JdbcTemplate; * @author Thomas Risberg * @author Sam Brannen * @author Phillip Webb + * @author Chris Baldwin * @since 2.5.4 */ @RunWith(MockitoJUnitRunner.class) @@ -48,103 +41,6 @@ public class JdbcTestUtilsTests { @Mock private JdbcTemplate jdbcTemplate; - - @Test - public void containsDelimiters() { - assertTrue("test with ';' is wrong", !JdbcTestUtils.containsSqlScriptDelimiters("select 1\n select ';'", ';')); - assertTrue("test with delimiter ; is wrong", - JdbcTestUtils.containsSqlScriptDelimiters("select 1; select 2", ';')); - assertTrue("test with '\\n' is wrong", - !JdbcTestUtils.containsSqlScriptDelimiters("select 1; select '\\n\n';", '\n')); - assertTrue("test with delimiter \\n is wrong", - JdbcTestUtils.containsSqlScriptDelimiters("select 1\n select 2", '\n')); - } - - @Test - public void splitSqlScriptDelimitedWithSemicolon() { - String rawStatement1 = "insert into customer (id, name)\nvalues (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')"; - String cleanedStatement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')"; - String rawStatement2 = "insert into orders(id, order_date, customer_id)\nvalues (1, '2008-01-02', 2)"; - String cleanedStatement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; - String rawStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; - String cleanedStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; - char delim = ';'; - String script = rawStatement1 + delim + rawStatement2 + delim + rawStatement3 + delim; - List statements = new ArrayList(); - JdbcTestUtils.splitSqlScript(script, delim, statements); - assertEquals("wrong number of statements", 3, statements.size()); - assertEquals("statement 1 not split correctly", cleanedStatement1, statements.get(0)); - assertEquals("statement 2 not split correctly", cleanedStatement2, statements.get(1)); - assertEquals("statement 3 not split correctly", cleanedStatement3, statements.get(2)); - } - - @Test - public void splitSqlScriptDelimitedWithNewLine() { - String statement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')"; - String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; - String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; - char delim = '\n'; - String script = statement1 + delim + statement2 + delim + statement3 + delim; - List statements = new ArrayList(); - JdbcTestUtils.splitSqlScript(script, delim, statements); - assertEquals("wrong number of statements", 3, statements.size()); - assertEquals("statement 1 not split correctly", statement1, statements.get(0)); - assertEquals("statement 2 not split correctly", statement2, statements.get(1)); - assertEquals("statement 3 not split correctly", statement3, statements.get(2)); - } - - @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 = JdbcTestUtils.readScript(lineNumberReader); - - char delim = ';'; - List statements = new ArrayList(); - JdbcTestUtils.splitSqlScript(script, delim, statements); - - String statement1 = "insert into customer (id, name) values (1, 'Rod; Johnson'), (2, 'Adrian Collier')"; - String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; - String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)"; - // Statement 4 addresses the error described in SPR-9982. - String statement4 = "INSERT INTO persons( person_id , name) VALUES( 1 , 'Name' )"; - - assertEquals("wrong number of statements", 4, statements.size()); - assertEquals("statement 1 not split correctly", statement1, statements.get(0)); - assertEquals("statement 2 not split correctly", statement2, statements.get(1)); - assertEquals("statement 3 not split correctly", statement3, statements.get(2)); - assertEquals("statement 4 not split correctly", statement4, statements.get(3)); - } - - /** - * See SPR-10330 - * @since 4.0 - */ - @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 = JdbcTestUtils.readScript(lineNumberReader); - - char delim = ';'; - List statements = new ArrayList(); - JdbcTestUtils.splitSqlScript(script, delim, statements); - - String statement1 = "insert into customer (id, name) values (1, 'Sam Brannen')"; - String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2013-06-08', 1)"; - String statement3 = "insert into orders(id, order_date, customer_id) values (2, '2013-06-08', 1)"; - - assertEquals("wrong number of statements", 3, statements.size()); - assertEquals("statement 1 not split correctly", statement1, statements.get(0)); - assertEquals("statement 2 not split correctly", statement2, statements.get(1)); - assertEquals("statement 3 not split correctly", statement3, statements.get(2)); - } - @Test public void deleteWithoutWhereClause() throws Exception { given(jdbcTemplate.update("DELETE FROM person")).willReturn(10); From 2bfd6ddcf4de600942a7564a43a338febd4267f2 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 11 Mar 2014 16:14:17 +0100 Subject: [PATCH 2/2] 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 --- .../core/io/support/EncodedResource.java | 4 +- .../init/CannotReadScriptException.java | 8 +- .../init/DatabasePopulatorUtils.java | 15 +- .../init/ResourceDatabasePopulator.java | 79 ++--- .../jdbc/datasource/init/ScriptException.java | 45 +++ .../datasource/init/ScriptParseException.java | 54 ++++ .../init/ScriptStatementFailedException.java | 12 +- .../jdbc/datasource/init/ScriptUtils.java | 298 ++++++++++-------- .../init/DatabasePopulatorTests.java | 6 +- .../datasource/init/ScriptUtilsTests.java | 104 ++---- ...TransactionalJUnit4SpringContextTests.java | 23 +- ...TransactionalTestNGSpringContextTests.java | 23 +- .../test/jdbc/JdbcTestUtils.java | 77 ++--- .../jdbc/JdbcTestUtilsIntegrationTests.java | 63 ++++ .../test/jdbc/JdbcTestUtilsTests.java | 13 +- .../org/springframework/test/jdbc/data.sql | 1 + .../org/springframework/test/jdbc/schema.sql | 4 + 17 files changed, 475 insertions(+), 354 deletions(-) create mode 100644 spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptException.java create mode 100644 spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptParseException.java create mode 100644 spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsIntegrationTests.java create mode 100644 spring-test/src/test/resources/org/springframework/test/jdbc/data.sql create mode 100644 spring-test/src/test/resources/org/springframework/test/jdbc/schema.sql diff --git a/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java b/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java index 04a173836c..59643dd6b2 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java @@ -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; * with a specific encoding to be used for reading from the resource. * *

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 diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CannotReadScriptException.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CannotReadScriptException.java index 93388defff..7ab5b198bb 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CannotReadScriptException.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CannotReadScriptException.java @@ -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; 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}. diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulatorUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulatorUtils.java index 1f046d07d2..6125ae9fd9 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulatorUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulatorUtils.java @@ -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; 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 { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java index 05d7a7b84a..c2ebb314c0 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java @@ -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 { 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 { /** * 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 { /** * 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 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 { } /** - * Specify the statement separator, if a custom one. Default is ";". + * Specify the statement separator, if a custom one. + *

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. + *

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. + *

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. + *

Default is "*/". * @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. + *

Defaults to {@code false}. */ public void setContinueOnError(boolean continueOnError) { this.continueOnError = continueOnError; @@ -133,44 +134,20 @@ public class ResourceDatabasePopulator implements DatabasePopulator { /** * Flag to indicate that a failed SQL {@code DROP} statement can be ignored. *

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. + *

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); } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptException.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptException.java new file mode 100644 index 0000000000..31c8a9618d --- /dev/null +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptException.java @@ -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); + } + +} diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptParseException.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptParseException.java new file mode 100644 index 0000000000..63edc6b03f --- /dev/null +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptParseException.java @@ -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 ? "" + : resource), message); + } + +} diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptStatementFailedException.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptStatementFailedException.java index 016ab1eed9..77c49a1c37 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptStatementFailedException.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptStatementFailedException.java @@ -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; 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 { * @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); } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java index 7b7886a000..f43a42304d 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java @@ -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}. - *

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. + *

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 + * start and end 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 — typically a ';' character + * @param delimiter character delimiting each statement — 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 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 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}. - *

Within a statement, the provided {@code commentPrefix} will be honored; + *

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 — typically a ';' character - * @param commentPrefix the prefix that identifies line comments in the SQL script — 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 — typically a ';' + * character; never {@code null} + * @param commentPrefix the prefix that identifies SQL line comments — + * typically "--"; never {@code null} or empty + * @param blockCommentStartDelimiter the start block comment delimiter; + * never {@code null} or empty + * @param blockCommentEndDelimiter the end 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 statements) { + public static void splitSqlScript(EncodedResource resource, String script, String delimiter, String commentPrefix, + String blockCommentStartDelimiter, String blockCommentEndDelimiter, List 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 { 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 { 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 { } /** - * 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. *

Lines beginning with the comment prefix are excluded from the * results; however, line comments anywhere else — for example, within * a statement — 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 — typically "--" + * @param commentPrefix the prefix that identifies comments in the SQL script — + * typically "--" * @param separator the statement separator in the SQL script — 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 { 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. *

Lines beginning with the comment prefix are excluded from the * results; however, line comments anywhere else — for example, within * a statement — 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 — typically "--" + * @param commentPrefix the prefix that identifies comments in the SQL script — + * typically "--" * @param separator the statement separator in the SQL script — 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 { } } - /** - * 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 { /** * Execute the given SQL script. - *

The script will normally be loaded by classpath. There should be one statement - * per line. Any statement separators will be removed. + *

Statement separators and comments will be removed before executing + * individual statements within the supplied script. *

Do not use this method to execute DDL if you expect rollback. - * @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 — + * typically "--" + * @param separator the script statement separator; defaults to + * {@value #DEFAULT_STATEMENT_SEPARATOR} if not specified + * @param blockCommentStartDelimiter the start block comment delimiter; never + * {@code null} or empty + * @param blockCommentEndDelimiter the end 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 { 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; - } - } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java index 68f33bcf82..73f3629ba5 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/DatabasePopulatorTests.java @@ -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.*; */ 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(); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsTests.java index f7d9e9764a..dff78a50b9 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsTests.java @@ -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; 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 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 { @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 statements = new ArrayList(); @@ -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 statements = new ArrayList(); @@ -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 SPR-9531 */ @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 statements = new ArrayList(); 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 { @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); - } - } - } } diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java index 8bd80e7e23..2bc4582716 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java @@ -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; 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 /** * Execute the given SQL script. *

Use with caution outside of a transaction! - *

The script will normally be loaded by classpath. There should be one - * statement per line. Any semicolons will be removed. Do not use this - * method to execute DDL if you expect rollback. + *

The script will normally be loaded by classpath. + *

Do not use this method to execute DDL if you expect rollback. * @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()); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java index 0e1d6712b8..3d99d31c71 100644 --- a/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java +++ b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java @@ -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; 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 /** * Execute the given SQL script. *

Use with caution outside of a transaction! - *

The script will normally be loaded by classpath. There should be one - * statement per line. Any semicolons will be removed. Do not use this - * method to execute DDL if you expect rollback. + *

The script will normally be loaded by classpath. + *

Do not use this method to execute DDL if you expect rollback. * @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()); } } diff --git a/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java b/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java index e3eab00efc..dd74f449f6 100644 --- a/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java +++ b/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java @@ -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 { 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 { * 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 { * @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 { * @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 { * @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); + ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(); + databasePopulator.setContinueOnError(continueOnError); + databasePopulator.addScript(resource.getResource()); + databasePopulator.setSqlScriptEncoding(resource.getEncoding()); + + DatabasePopulatorUtils.execute(databasePopulator, jdbcTemplate.getDataSource()); } - private static class JdbcTemplateScriptStatementExecutor implements ScriptStatementExecutor { - - private JdbcTemplate jdbcTemplate; - - public JdbcTemplateScriptStatementExecutor(JdbcTemplate jdbcTemplate) { - super(); - this.jdbcTemplate = jdbcTemplate; - } - - /* (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); - } - } - /** * 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 { * 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 { * to be processed * @param commentPrefix the prefix that identifies comments in the SQL script — 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 { * @param script the SQL script * @param delim character delimiting each statement — 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 { * @param script the SQL script * @param delim character delimiting each statement — 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 diff --git a/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsIntegrationTests.java new file mode 100644 index 0000000000..5f9897e5bc --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsIntegrationTests.java @@ -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'")); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java b/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java index 748e4a7277..1c9452d034 100644 --- a/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java @@ -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 { @Mock private JdbcTemplate jdbcTemplate; + @Test public void deleteWithoutWhereClause() throws Exception { given(jdbcTemplate.update("DELETE FROM person")).willReturn(10); diff --git a/spring-test/src/test/resources/org/springframework/test/jdbc/data.sql b/spring-test/src/test/resources/org/springframework/test/jdbc/data.sql new file mode 100644 index 0000000000..10d02a9c91 --- /dev/null +++ b/spring-test/src/test/resources/org/springframework/test/jdbc/data.sql @@ -0,0 +1 @@ +INSERT INTO person VALUES('bob'); \ No newline at end of file diff --git a/spring-test/src/test/resources/org/springframework/test/jdbc/schema.sql b/spring-test/src/test/resources/org/springframework/test/jdbc/schema.sql new file mode 100644 index 0000000000..d5bacef4fb --- /dev/null +++ b/spring-test/src/test/resources/org/springframework/test/jdbc/schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE person ( + name VARCHAR(20) NOT NULL, + PRIMARY KEY(name) +); \ No newline at end of file