summaryrefslogtreecommitdiff
path: root/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init
diff options
context:
space:
mode:
Diffstat (limited to 'spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init')
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CannotReadScriptException.java8
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/CompositeDatabasePopulator.java16
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DataSourceInitializer.java56
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulator.java25
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/DatabasePopulatorUtils.java22
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java362
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptException.java48
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptParseException.java54
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptStatementFailedException.java12
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java503
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/UncategorizedScriptException.java47
-rw-r--r--spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/package-info.java2
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>"*&#47;"</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 &mdash; 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 &mdash; 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 &mdash; 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 &mdash; typically a ';' or
+ * newline character; never {@code null}
+ * @param commentPrefix the prefix that identifies SQL line comments &mdash;
+ * typically "--"; never {@code null} or empty
+ * @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
+ * never {@code null} or empty
+ * @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
+ * never {@code null} or empty
+ * @param statements the list that will contain the individual statements
+ * @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 &mdash; for example, within
+ * a statement &mdash; will be included in the results.
+ * @param resource the {@code EncodedResource} containing the script
+ * to be processed
+ * @param commentPrefix the prefix that identifies comments in the SQL script &mdash;
+ * typically "--"
+ * @param separator the statement separator in the SQL script &mdash; 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 &mdash; for example, within
+ * a statement &mdash; will be included in the results.
+ * @param lineNumberReader the {@code LineNumberReader} containing the script
+ * to be processed
+ * @param commentPrefix the prefix that identifies comments in the SQL script &mdash;
+ * typically "--"
+ * @param separator the statement separator in the SQL script &mdash; 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 &mdash;
+ * 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;