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
This commit is contained in:
Sam Brannen
2014-03-11 16:14:17 +01:00
parent e5c17560db
commit 2bfd6ddcf4
17 changed files with 475 additions and 354 deletions

View File

@@ -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.
* <p>Use with caution outside of a transaction!
* <p>The script will normally be loaded by classpath. There should be one
* statement per line. Any semicolons will be removed. <b>Do not use this
* method to execute DDL if you expect rollback.</b>
* <p>The script will normally be loaded by classpath.
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
* @param sqlResourcePath the Spring resource path for the SQL script
* @param continueOnError whether or not to continue without throwing an
* exception in the event of an error
* @throws DataAccessException if there is an error executing a statement
* and continueOnError was {@code false}
* @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean)
* @see ResourceDatabasePopulator
* @see DatabasePopulatorUtils
* @see #setSqlScriptEncoding
*/
@SuppressWarnings("deprecation")
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
Resource resource = this.applicationContext.getResource(sqlResourcePath);
JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding),
continueOnError);
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
databasePopulator.setContinueOnError(continueOnError);
databasePopulator.addScript(resource);
databasePopulator.setSqlScriptEncoding(this.sqlScriptEncoding);
DatabasePopulatorUtils.execute(databasePopulator, jdbcTemplate.getDataSource());
}
}

View File

@@ -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.
* <p>Use with caution outside of a transaction!
* <p>The script will normally be loaded by classpath. There should be one
* statement per line. Any semicolons will be removed. <b>Do not use this
* method to execute DDL if you expect rollback.</b>
* <p>The script will normally be loaded by classpath.
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
* @param sqlResourcePath the Spring resource path for the SQL script
* @param continueOnError whether or not to continue without throwing an
* exception in the event of an error
* @throws DataAccessException if there is an error executing a statement
* and continueOnError was {@code false}
* @see JdbcTestUtils#executeSqlScript(JdbcTemplate, EncodedResource, boolean)
* @see ResourceDatabasePopulator
* @see DatabasePopulatorUtils
* @see #setSqlScriptEncoding
*/
@SuppressWarnings("deprecation")
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
Resource resource = this.applicationContext.getResource(sqlResourcePath);
JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding),
continueOnError);
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
databasePopulator.setContinueOnError(continueOnError);
databasePopulator.addScript(resource);
databasePopulator.setSqlScriptEncoding(this.sqlScriptEncoding);
DatabasePopulatorUtils.execute(databasePopulator, jdbcTemplate.getDataSource());
}
}

View File

@@ -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 &mdash; typically "--"
* @return a {@code String} containing the script lines
* @deprecated as of Spring 4.0.3, in favor of using
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#readScript(LineNumberReader, String, String)}
*/
@Deprecated
@@ -276,14 +267,14 @@ public class JdbcTestUtils {
* @param script the SQL script
* @param delim character delimiting each statement &mdash; typically a ';' character
* @return {@code true} if the script contains the delimiter; {@code false} otherwise
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#containsSqlScriptDelimiters(String, char)}
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#containsSqlScriptDelimiters}
*/
@Deprecated
public static boolean containsSqlScriptDelimiters(String script, char delim) {
return ScriptUtils.containsSqlScriptDelimiters(script, delim);
return ScriptUtils.containsSqlScriptDelimiters(script, String.valueOf(delim));
}
/**
* Split an SQL script into separate statements delimited by the provided
* delimiter character. Each individual statement will be added to the
@@ -295,7 +286,7 @@ public class JdbcTestUtils {
* @param script the SQL script
* @param delim character delimiting each statement &mdash; typically a ';' character
* @param statements the list that will contain the individual statements
* @deprecated as of Spring 4.0.3, in favor of using
* @deprecated as of Spring 4.0.3, in favor of using
* {@link org.springframework.jdbc.datasource.init.ScriptUtils#splitSqlScript(String, char, List)}
*/
@Deprecated

View File

@@ -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'"));
}
}

View File

@@ -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);

View File

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

View File

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