diff options
Diffstat (limited to 'spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init')
12 files changed, 856 insertions, 299 deletions
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 93388def..7ab5b198 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/CompositeDatabasePopulator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CompositeDatabasePopulator.java index 143317b6..16c7b8e0 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CompositeDatabasePopulator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CompositeDatabasePopulator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 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. @@ -13,20 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.jdbc.datasource.init; import java.sql.Connection; import java.sql.SQLException; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * {@link DatabasePopulator} implementation that delegates to a list of other - * DatabasePopulator implementations, executing all scripts. + * {@code DatabasePopulator} implementations, executing all scripts. * * @author Dave Syer * @author Juergen Hoeller + * @author Sam Brannen * @since 3.1 */ public class CompositeDatabasePopulator implements DatabasePopulator { @@ -43,14 +46,17 @@ public class CompositeDatabasePopulator implements DatabasePopulator { } /** - * Add a populator to the list of delegates. + * Add one or more populators to the list of delegates. */ public void addPopulators(DatabasePopulator... populators) { this.populators.addAll(Arrays.asList(populators)); } - - public void populate(Connection connection) throws SQLException { + /** + * {@inheritDoc} + */ + @Override + public void populate(Connection connection) throws SQLException, ScriptException { for (DatabasePopulator populator : this.populators) { populator.populate(connection); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DataSourceInitializer.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DataSourceInitializer.java index 96f795ca..574c1ae5 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DataSourceInitializer.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DataSourceInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 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,11 +20,15 @@ import javax.sql.DataSource; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; /** - * Used to populate a database during initialization. + * Used to {@linkplain #setDatabasePopulator set up} a database during + * initialization and {@link #setDatabaseCleaner clean up} a database during + * destruction. * * @author Dave Syer + * @author Sam Brannen * @since 3.0 * @see DatabasePopulator */ @@ -40,8 +44,9 @@ public class DataSourceInitializer implements InitializingBean, DisposableBean { /** - * The {@link DataSource} to populate when this component is initialized. - * Mandatory with no default. + * The {@link DataSource} for the database to populate when this component + * is initialized and to clean up when this component is shut down. + * <p>This property is mandatory with no default provided. * @param dataSource the DataSource */ public void setDataSource(DataSource dataSource) { @@ -49,47 +54,58 @@ public class DataSourceInitializer implements InitializingBean, DisposableBean { } /** - * The {@link DatabasePopulator} to use to populate the data source. - * Mandatory with no default. - * @param databasePopulator the database populator to use. + * Set the {@link DatabasePopulator} to execute during the bean initialization + * phase. + * @param databasePopulator the {@code DatabasePopulator} to use during + * initialization + * @see #setDatabaseCleaner */ public void setDatabasePopulator(DatabasePopulator databasePopulator) { this.databasePopulator = databasePopulator; } /** - * Set a script execution to be run in the bean destruction callback, - * cleaning up the database and leaving it in a known state for others. - * @param databaseCleaner the database script executor to run on destroy + * Set the {@link DatabasePopulator} to execute during the bean destruction + * phase, cleaning up the database and leaving it in a known state for others. + * @param databaseCleaner the {@code DatabasePopulator} to use during destruction + * @see #setDatabasePopulator */ public void setDatabaseCleaner(DatabasePopulator databaseCleaner) { this.databaseCleaner = databaseCleaner; } /** - * Flag to explicitly enable or disable the database populator. - * @param enabled true if the database populator will be called on startup + * Flag to explicitly enable or disable the {@linkplain #setDatabasePopulator + * database populator} and {@linkplain #setDatabaseCleaner database cleaner}. + * @param enabled {@code true} if the database populator and database cleaner + * should be called on startup and shutdown, respectively */ public void setEnabled(boolean enabled) { this.enabled = enabled; } - /** - * Use the populator to set up data in the data source. + * Use the {@linkplain #setDatabasePopulator database populator} to set up + * the database. */ + @Override public void afterPropertiesSet() { - if (this.databasePopulator != null && this.enabled) { - DatabasePopulatorUtils.execute(this.databasePopulator, this.dataSource); - } + execute(this.databasePopulator); } /** - * Use the populator to clean up data in the data source. + * Use the {@linkplain #setDatabaseCleaner database cleaner} to clean up the + * database. */ + @Override public void destroy() { - if (this.databaseCleaner != null && this.enabled) { - DatabasePopulatorUtils.execute(this.databaseCleaner, this.dataSource); + execute(this.databaseCleaner); + } + + private void execute(DatabasePopulator populator) { + Assert.state(dataSource != null, "DataSource must be set"); + if (this.enabled && populator != null) { + DatabasePopulatorUtils.execute(populator, this.dataSource); } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulator.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulator.java index 52cdd430..b6b9f2d2 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulator.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulator.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,19 +20,32 @@ import java.sql.Connection; import java.sql.SQLException; /** - * Strategy used to populate a database during initialization. + * Strategy used to populate, initialize, or clean up a database. * * @author Keith Donald + * @author Sam Brannen * @since 3.0 * @see ResourceDatabasePopulator + * @see DatabasePopulatorUtils + * @see DataSourceInitializer */ public interface DatabasePopulator { /** - * Populate the database using the JDBC connection provided. - * @param connection the JDBC connection to use to populate the db; already configured and ready to use - * @throws SQLException if an unrecoverable data access exception occurs during database population + * Populate, initialize, or clean up the database using the provided JDBC + * connection. + * <p>Concrete implementations <em>may</em> throw an {@link SQLException} if + * an error is encountered but are <em>strongly encouraged</em> to throw a + * specific {@link ScriptException} instead. For example, Spring's + * {@link ResourceDatabasePopulator} and {@link DatabasePopulatorUtils} wrap + * all {@code SQLExceptions} in {@code ScriptExceptions}. + * @param connection the JDBC connection to use to populate the db; already + * configured and ready to use + * @throws SQLException if an unrecoverable data access exception occurs + * during database population + * @throws ScriptException in all other error cases + * @see DatabasePopulatorUtils#execute */ - void populate(Connection connection) throws SQLException; + void populate(Connection connection) throws SQLException, ScriptException; } 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 1f046d07..898c6a39 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,27 @@ import java.sql.Connection; import javax.sql.DataSource; -import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.dao.DataAccessException; 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, specifically a {@link ScriptException} */ - 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 { @@ -53,7 +55,11 @@ public abstract class DatabasePopulatorUtils { } } catch (Exception ex) { - throw new DataAccessResourceFailureException("Failed to execute database script", ex); + if (ex instanceof ScriptException) { + throw (ScriptException) ex; + } + + throw new UncategorizedScriptException("Failed to execute database script", ex); } } 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 1b44edf2..23082390 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,28 +16,22 @@ 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; /** - * Populates a database from SQL scripts defined in external resources. + * Populates or initializes a database from SQL scripts defined in external + * resources. * - * <p>Call {@link #addScript(Resource)} to add a SQL script location. - * Call {@link #setSqlScriptEncoding(String)} to set the encoding for all added scripts. + * <p>Call {@link #addScript(Resource)} to add a single SQL script location. + * Call {@link #addScripts(Resource...)} to add multiple SQL script locations. + * Call {@link #setSqlScriptEncoding(String)} to set the encoding for all added + * scripts. * * @author Keith Donald * @author Dave Syer @@ -45,24 +39,23 @@ import org.springframework.util.StringUtils; * @author Chris Beams * @author Oliver Gierke * @author Sam Brannen + * @author Chris Baldwin * @since 3.0 + * @see DatabasePopulatorUtils */ 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<Resource> scripts = new ArrayList<Resource>(); 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; @@ -70,15 +63,62 @@ public class ResourceDatabasePopulator implements DatabasePopulator { /** - * Add a script to execute to populate the database. - * @param script the path to a SQL script + * Construct a new {@code ResourceDatabasePopulator} with default settings. + * @since 4.0.3 + */ + public ResourceDatabasePopulator() { + /* no-op */ + } + + /** + * Construct a new {@code ResourceDatabasePopulator} with default settings + * for the supplied scripts. + * @param scripts the scripts to execute to initialize or populate the database + * @since 4.0.3 + */ + public ResourceDatabasePopulator(Resource... scripts) { + this(); + this.scripts = Arrays.asList(scripts); + } + + /** + * Construct a new {@code ResourceDatabasePopulator} with the supplied values. + * @param continueOnError flag to indicate that all failures in SQL should be + * logged but not cause a failure + * @param ignoreFailedDrops flag to indicate that a failed SQL {@code DROP} + * statement can be ignored + * @param sqlScriptEncoding the encoding for the supplied SQL scripts, if + * different from the platform encoding; may be {@code null} + * @param scripts the scripts to execute to initialize or populate the database + * @since 4.0.3 + */ + public ResourceDatabasePopulator(boolean continueOnError, boolean ignoreFailedDrops, String sqlScriptEncoding, + Resource... scripts) { + this(scripts); + this.continueOnError = continueOnError; + this.ignoreFailedDrops = ignoreFailedDrops; + this.sqlScriptEncoding = sqlScriptEncoding; + } + + /** + * Add a script to execute to initialize or populate the database. + * @param script the path to an SQL script */ public void addScript(Resource script) { this.scripts.add(script); } /** - * Set the scripts to execute to populate the database. + * Add multiple scripts to execute to initialize or populate the database. + * @param scripts the scripts to execute + */ + public void addScripts(Resource... scripts) { + this.scripts.addAll(Arrays.asList(scripts)); + } + + /** + * Set the scripts to execute to initialize or populate the database, + * replacing any previously added scripts. * @param scripts the scripts to execute */ public void setScripts(Resource... scripts) { @@ -87,8 +127,7 @@ 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}. + * @param sqlScriptEncoding the encoding used in scripts * @see #addScript(Resource) */ public void setSqlScriptEncoding(String sqlScriptEncoding) { @@ -96,261 +135,88 @@ public class ResourceDatabasePopulator implements DatabasePopulator { } /** - * Specify the statement separator, if a custom one. Default is ";". + * Specify the statement separator, if a custom one. + * <p>Defaults to {@code ";"} if not specified and falls back to {@code "\n"} + * as a last resort; may be set to {@link ScriptUtils#EOF_STATEMENT_SEPARATOR} + * to signal that each script contains a single statement without a separator. + * @param separator the script statement separator */ 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 single-line comments within the SQL scripts. + * <p>Defaults to {@code "--"}. + * @param commentPrefix the prefix for single-line comments */ public void setCommentPrefix(String commentPrefix) { this.commentPrefix = commentPrefix; } /** - * Flag to indicate that all failures in SQL should be logged but not cause a failure. - * Defaults to false. + * Set the start delimiter that identifies block comments within the SQL + * scripts. + * <p>Defaults to {@code "/*"}. + * @param blockCommentStartDelimiter the start delimiter for block comments + * @since 4.0.3 + * @see #setBlockCommentEndDelimiter */ - public void setContinueOnError(boolean continueOnError) { - this.continueOnError = continueOnError; + public void setBlockCommentStartDelimiter(String blockCommentStartDelimiter) { + this.blockCommentStartDelimiter = blockCommentStartDelimiter; } /** - * Flag to indicate that a failed SQL {@code DROP} statement can be ignored. - * <p>This is useful for non-embedded databases whose SQL dialect does not support an - * {@code IF EXISTS} clause in a {@code DROP}. The default is false so that if the - * populator runs accidentally, it will fail fast when the script starts with a {@code DROP}. + * Set the end delimiter that identifies block comments within the SQL + * scripts. + * <p>Defaults to <code>"*/"</code>. + * @param blockCommentEndDelimiter the end delimiter for block comments + * @since 4.0.3 + * @see #setBlockCommentStartDelimiter */ - public void setIgnoreFailedDrops(boolean ignoreFailedDrops) { - this.ignoreFailedDrops = ignoreFailedDrops; - } - - - public void populate(Connection connection) throws SQLException { - for (Resource script : this.scripts) { - executeSqlScript(connection, applyEncodingIfNecessary(script), this.continueOnError, this.ignoreFailedDrops); - } - } - - private EncodedResource applyEncodingIfNecessary(Resource script) { - if (script instanceof EncodedResource) { - return (EncodedResource) script; - } - else { - return new EncodedResource(script, this.sqlScriptEncoding); - } + public void setBlockCommentEndDelimiter(String blockCommentEndDelimiter) { + this.blockCommentEndDelimiter = blockCommentEndDelimiter; } /** - * Execute the given SQL script. - * <p>The script will normally be loaded by classpath. There should be one statement - * per line. Any {@link #setSeparator(String) statement separators} will be removed. - * <p><b>Do not use this method to execute DDL if you expect rollback.</b> - * @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} + * Flag to indicate that all failures in SQL should be logged but not cause a failure. + * <p>Defaults to {@code false}. + * @param continueOnError {@code true} if script execution should continue on error */ - 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<String> statements = new LinkedList<String>(); - 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."); - } + public void setContinueOnError(boolean continueOnError) { + this.continueOnError = continueOnError; } /** - * 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 + * Flag to indicate that a failed SQL {@code DROP} statement can be ignored. + * <p>This is useful for non-embedded databases whose SQL dialect does not support an + * {@code IF EXISTS} clause in a {@code DROP} statement. + * <p>The default is {@code false} so that if the populator runs accidentally, it will + * fail fast if the script starts with a {@code DROP} statement. + * @param ignoreFailedDrops {@code true} if failed drop statements should be ignored */ - 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())); - } + public void setIgnoreFailedDrops(boolean ignoreFailedDrops) { + this.ignoreFailedDrops = ignoreFailedDrops; } /** - * Does the provided SQL script contain the specified delimiter? - * @param script the SQL script - * @param delim character delimiting each statement - typically a ';' character + * {@inheritDoc} */ - 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; - } + @Override + public void populate(Connection connection) throws ScriptException { + for (Resource script : this.scripts) { + ScriptUtils.executeSqlScript(connection, encodeScript(script), this.continueOnError, + this.ignoreFailedDrops, this.commentPrefix, this.separator, this.blockCommentStartDelimiter, + this.blockCommentEndDelimiter); } - 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}. - * <p>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 + * {@link EncodedResource} is not a sub-type of {@link Resource}. Thus we + * always need to wrap each script resource in an encoded resource. */ - private void splitSqlScript(String script, String delim, String commentPrefix, List<String> 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()); - } + private EncodedResource encodeScript(Resource script) { + return new EncodedResource(script, this.sqlScriptEncoding); } } 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 00000000..198284d1 --- /dev/null +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptException.java @@ -0,0 +1,48 @@ +/* + * 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.dao.DataAccessException; + +/** + * Root of the hierarchy of data access exceptions that are related to processing + * of SQL scripts. + * + * @author Sam Brannen + * @since 4.0.3 + */ +@SuppressWarnings("serial") +public abstract class ScriptException extends DataAccessException { + + /** + * 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 00000000..63edc6b0 --- /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 ? "<unknown>" + : 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 016ab1ee..77c49a1c 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 new file mode 100644 index 00000000..9f23e009 --- /dev/null +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java @@ -0,0 +1,503 @@ +/* + * 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.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.Resource; +import org.springframework.core.io.support.EncodedResource; +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. + * + * @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 = ";"; + + /** + * Fallback statement separator within SQL scripts. + * <p>Used if neither a custom defined separator nor the + * {@link #DEFAULT_STATEMENT_SEPARATOR} is present in a given script. + */ + public static final String FALLBACK_STATEMENT_SEPARATOR = "\n"; + + /** + * End of file (EOF) SQL statement separator. + * <p>This value may be supplied as the {@code separator} to {@link + * #executeSqlScript(Connection, EncodedResource, boolean, boolean, String, String, String, String)} + * to denote that an SQL script contains a single statement (potentially + * spanning multiple lines) with no explicit statement separator. Note that + * such a script should not actually contain this value; it is merely a + * <em>virtual</em> statement separator. + */ + public static final String EOF_STATEMENT_SEPARATOR = "^^^ END OF SCRIPT ^^^"; + + /** + * Default prefix for line comments within SQL scripts. + */ + public static final String DEFAULT_COMMENT_PREFIX = "--"; + + /** + * 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 = "*/"; + + + /** + * Prevent instantiation of this utility class. + */ + private ScriptUtils() { + /* no-op */ + } + + /** + * Split an SQL script into separate statements delimited by the provided + * separator character. Each individual statement will be added to the + * provided {@code List}. + * <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the + * comment prefix; any text beginning with the comment prefix and extending to + * the end of the line will be omitted from the output. Similarly, + * {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and + * {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the + * <em>start</em> and <em>end</em> block comment delimiters: any text enclosed + * in a block comment will be omitted from the output. In addition, multiple + * adjacent whitespace characters will be collapsed into a single space. + * @param script the SQL script + * @param separator character separating each statement — typically a ';' + * @param statements the list that will contain the individual statements + * @throws ScriptException if an error occurred while splitting the SQL script + * @see #splitSqlScript(String, String, List) + * @see #splitSqlScript(EncodedResource, String, String, String, String, String, List) + */ + public static void splitSqlScript(String script, char separator, List<String> statements) throws ScriptException { + splitSqlScript(script, String.valueOf(separator), statements); + } + + /** + * Split an SQL script into separate statements delimited by the provided + * separator string. Each individual statement will be added to the + * provided {@code List}. + * <p>Within the script, {@value #DEFAULT_COMMENT_PREFIX} will be used as the + * comment prefix; any text beginning with the comment prefix and extending to + * the end of the line will be omitted from the output. Similarly, + * {@value #DEFAULT_BLOCK_COMMENT_START_DELIMITER} and + * {@value #DEFAULT_BLOCK_COMMENT_END_DELIMITER} will be used as the + * <em>start</em> and <em>end</em> block comment delimiters: any text enclosed + * in a block comment will be omitted from the output. In addition, multiple + * adjacent whitespace characters will be collapsed into a single space. + * @param script the SQL script + * @param separator text separating each statement — typically a ';' or newline character + * @param statements the list that will contain the individual statements + * @throws ScriptException if an error occurred while splitting the SQL script + * @see #splitSqlScript(String, char, List) + * @see #splitSqlScript(EncodedResource, String, String, String, String, String, List) + */ + public static void splitSqlScript(String script, String separator, List<String> statements) throws ScriptException { + splitSqlScript(null, script, separator, 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 + * separator string. Each individual statement will be added to the provided + * {@code List}. + * <p>Within the script, the provided {@code commentPrefix} will be honored: + * any text beginning with the comment prefix and extending to the end of the + * line will be omitted from the 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 separator text separating each statement — typically a ';' or + * newline character; never {@code null} + * @param commentPrefix the prefix that identifies SQL line comments — + * typically "--"; never {@code null} or empty + * @param blockCommentStartDelimiter the <em>start</em> block comment delimiter; + * never {@code null} or empty + * @param blockCommentEndDelimiter the <em>end</em> block comment delimiter; + * never {@code null} or empty + * @param statements the list that will contain the individual statements + * @throws ScriptException if an error occurred while splitting the SQL script + */ + public static void splitSqlScript(EncodedResource resource, String script, String separator, String commentPrefix, + String blockCommentStartDelimiter, String blockCommentEndDelimiter, List<String> statements) + throws ScriptException { + + Assert.hasText(script, "script must not be null or empty"); + Assert.notNull(separator, "separator 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; + 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(separator, i)) { + // we've reached the end of the current statement + if (sb.length() > 0) { + statements.add(sb.toString()); + sb = new StringBuilder(); + } + i += separator.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(blockCommentStartDelimiter, i)) { + // skip over any block comments + int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i); + if (indexOfCommentEnd > i) { + i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1; + continue; + } + else { + throw new ScriptParseException(String.format("Missing block comment end delimiter [%s].", + blockCommentEndDelimiter), resource); + } + } + 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 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 + */ + 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. + * <p>Lines <em>beginning</em> 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 + * @throws IOException in case of I/O errors + */ + private 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. + * <p>Lines <em>beginning</em> 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 + * @throws IOException in case of I/O errors + */ + public static String readScript(LineNumberReader lineNumberReader, String commentPrefix, String separator) + throws IOException { + String currentStatement = lineNumberReader.readLine(); + StringBuilder scriptBuilder = new StringBuilder(); + while (currentStatement != null) { + if (commentPrefix != null && !currentStatement.startsWith(commentPrefix)) { + if (scriptBuilder.length() > 0) { + scriptBuilder.append('\n'); + } + scriptBuilder.append(currentStatement); + } + currentStatement = lineNumberReader.readLine(); + } + appendSeparatorToScriptIfNecessary(scriptBuilder, separator); + return scriptBuilder.toString(); + } + + private static void appendSeparatorToScriptIfNecessary(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 String delimiting each statement - typically a ';' character + */ + 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 using default settings for separator separators, + * comment delimiters, and exception handling flags. + * <p>Statement separators and comments will be removed before executing + * individual statements within the supplied script. + * <p><b>Do not use this method to execute DDL if you expect rollback.</b> + * @param connection the JDBC connection to use to execute the script; already + * configured and ready to use + * @param resource the resource to load the SQL script from; encoded with the + * current platform's default encoding + * @throws ScriptException if an error occurred while executing the SQL script + * @see #executeSqlScript(Connection, EncodedResource, boolean, boolean, String, String, String, String) + * @see #DEFAULT_COMMENT_PREFIX + * @see #DEFAULT_STATEMENT_SEPARATOR + * @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER + * @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER + */ + public static void executeSqlScript(Connection connection, Resource resource) throws ScriptException { + executeSqlScript(connection, new EncodedResource(resource)); + } + + /** + * Execute the given SQL script using default settings for separator separators, + * comment delimiters, and exception handling flags. + * <p>Statement separators and comments will be removed before executing + * individual statements within the supplied script. + * <p><b>Do not use this method to execute DDL if you expect rollback.</b> + * @param 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 + * @throws ScriptException if an error occurred while executing the SQL script + * @see #executeSqlScript(Connection, EncodedResource, boolean, boolean, String, String, String, String) + * @see #DEFAULT_COMMENT_PREFIX + * @see #DEFAULT_STATEMENT_SEPARATOR + * @see #DEFAULT_BLOCK_COMMENT_START_DELIMITER + * @see #DEFAULT_BLOCK_COMMENT_END_DELIMITER + */ + public static void executeSqlScript(Connection connection, EncodedResource resource) throws ScriptException { + executeSqlScript(connection, resource, false, false, DEFAULT_COMMENT_PREFIX, DEFAULT_STATEMENT_SEPARATOR, + DEFAULT_BLOCK_COMMENT_START_DELIMITER, DEFAULT_BLOCK_COMMENT_END_DELIMITER); + } + + /** + * Execute the given SQL script. + * <p>Statement separators and comments will be removed before executing + * individual statements within the supplied script. + * <p><b>Do not use this method to execute DDL if you expect rollback.</b> + * @param 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 and falls back to + * {@value #FALLBACK_STATEMENT_SEPARATOR} as a last resort; may be set to + * {@value #EOF_STATEMENT_SEPARATOR} to signal that the script contains a + * single statement without a separator + * @param blockCommentStartDelimiter the <em>start</em> block comment delimiter; never + * {@code null} or empty + * @param blockCommentEndDelimiter the <em>end</em> block comment delimiter; never + * {@code null} or empty + * @throws ScriptException if an error occurred while executing the SQL script + * @see #DEFAULT_STATEMENT_SEPARATOR + * @see #FALLBACK_STATEMENT_SEPARATOR + * @see #EOF_STATEMENT_SEPARATOR + */ + public static void executeSqlScript(Connection connection, EncodedResource resource, boolean continueOnError, + boolean ignoreFailedDrops, String commentPrefix, String separator, String blockCommentStartDelimiter, + String blockCommentEndDelimiter) throws ScriptException { + + try { + if (logger.isInfoEnabled()) { + logger.info("Executing SQL script from " + resource); + } + + long startTime = System.currentTimeMillis(); + List<String> statements = new LinkedList<String>(); + String script; + try { + script = readScript(resource, commentPrefix, separator); + } + catch (IOException ex) { + throw new CannotReadScriptException(resource, ex); + } + + if (separator == null) { + separator = DEFAULT_STATEMENT_SEPARATOR; + } + if (!EOF_STATEMENT_SEPARATOR.equals(separator) && !containsSqlScriptDelimiters(script, separator)) { + separator = FALLBACK_STATEMENT_SEPARATOR; + } + + splitSqlScript(resource, script, separator, commentPrefix, blockCommentStartDelimiter, + blockCommentEndDelimiter, 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("Executed SQL script from " + resource + " in " + elapsedTime + " ms."); + } + } + catch (Exception ex) { + if (ex instanceof ScriptException) { + throw (ScriptException) ex; + } + + throw new UncategorizedScriptException( + "Failed to execute database script from resource [" + resource + "]", ex); + } + } + +} diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/UncategorizedScriptException.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/UncategorizedScriptException.java new file mode 100644 index 00000000..bf50befb --- /dev/null +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/UncategorizedScriptException.java @@ -0,0 +1,47 @@ +/* + * 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; + +/** + * Thrown when we cannot determine anything more specific than "something went + * wrong while processing an SQL script": for example, a {@link java.sql.SQLException} + * from JDBC that we cannot pinpoint more precisely. + * + * @author Sam Brannen + * @since 4.0.3 + */ +@SuppressWarnings("serial") +public class UncategorizedScriptException extends ScriptException { + + /** + * Construct a new {@code UncategorizedScriptException}. + * @param message detailed message + */ + public UncategorizedScriptException(String message) { + super(message); + } + + /** + * Construct a new {@code UncategorizedScriptException}. + * @param message detailed message + * @param cause the root cause + */ + public UncategorizedScriptException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/package-info.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/package-info.java index 5b10ffff..0e849199 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/package-info.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/package-info.java @@ -1,8 +1,6 @@ /** - * * Provides extensible support for initializing databases through scripts. - * */ package org.springframework.jdbc.datasource.init; |