SPR-7364: added separator property to database populator to deal with things like PL/SQL

This commit is contained in:
David Syer
2011-06-02 16:22:26 +00:00
parent ac735d73ac
commit 14edc9fc03
21 changed files with 288 additions and 72 deletions

View File

@@ -16,16 +16,20 @@
package org.springframework.jdbc.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.jdbc.datasource.init.CompositeDatabasePopulator;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
@@ -60,21 +64,36 @@ public class InitializeDatabaseBeanDefinitionParser extends AbstractBeanDefiniti
}
private BeanDefinition createDatabasePopulator(Element element, List<Element> scripts, ParserContext context) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ResourceDatabasePopulator.class);
builder.addPropertyValue("ignoreFailedDrops", element.getAttribute("ignore-failures").equals("DROPS"));
builder.addPropertyValue("continueOnError", element.getAttribute("ignore-failures").equals("ALL"));
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CompositeDatabasePopulator.class);
boolean ignoreFailedDrops = element.getAttribute("ignore-failures").equals("DROPS");
boolean continueOnError = element.getAttribute("ignore-failures").equals("ALL");
ManagedList<BeanMetadataElement> delegates = new ManagedList<BeanMetadataElement>();
List<String> locations = new ArrayList<String>();
for (Element scriptElement : scripts) {
String location = scriptElement.getAttribute("location");
locations.add(location);
}
// Use a factory bean for the resources so they can be given an order if a pattern is used
BeanDefinitionBuilder resourcesFactory = BeanDefinitionBuilder
.genericBeanDefinition(SortedResourcesFactoryBean.class);
resourcesFactory.addConstructorArgValue(locations);
builder.addPropertyValue("scripts", resourcesFactory.getBeanDefinition());
BeanDefinitionBuilder delegate = BeanDefinitionBuilder.genericBeanDefinition(ResourceDatabasePopulator.class);
delegate.addPropertyValue("ignoreFailedDrops", ignoreFailedDrops);
delegate.addPropertyValue("continueOnError", continueOnError);
List<String> locations = Arrays.asList(scriptElement.getAttribute("location"));
// Use a factory bean for the resources so they can be given an order if a pattern is used
BeanDefinitionBuilder resourcesFactory = BeanDefinitionBuilder
.genericBeanDefinition(SortedResourcesFactoryBean.class);
resourcesFactory.addConstructorArgValue(locations);
delegate.addPropertyValue("scripts", resourcesFactory.getBeanDefinition());
if (StringUtils.hasLength(scriptElement.getAttribute("separator"))) {
delegate.addPropertyValue("separator", scriptElement.getAttribute("separator"));
}
delegates.add(delegate.getBeanDefinition());
}
builder.addPropertyValue("populators", delegates);
return builder.getBeanDefinition();
}

View File

@@ -23,7 +23,6 @@ import java.sql.Types;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.SqlInOutParameter;
/**
* Oracle specific implementation for the {@link CallMetaDataProvider} interface.

View File

@@ -18,8 +18,6 @@ package org.springframework.jdbc.core.namedparam;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import org.springframework.jdbc.core.SqlParameterValue;
/**

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2002-2011 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.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* {@link DatabasePopulator} implementation that delegates to a list of other implementations, executing alll scripts.
*
* @author Dave Syer
*
*/
public class CompositeDatabasePopulator implements DatabasePopulator {
private List<DatabasePopulator> populators = new ArrayList<DatabasePopulator>();
/**
* @param populators the populators to set
*/
public void setPopulators(List<DatabasePopulator> populators) {
this.populators.clear();
this.populators.addAll(populators);
}
/**
* @param populator the populator to add
*/
public void addPopulator(DatabasePopulator populator) {
this.populators.add(populator);
}
/* (non-Javadoc)
* @see org.springframework.jdbc.datasource.init.DatabasePopulator#populate(java.sql.Connection)
*/
public void populate(Connection connection) throws SQLException {
for (DatabasePopulator populator : populators) {
populator.populate(connection);
}
}
}

View File

@@ -50,7 +50,6 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
private static final Log logger = LogFactory.getLog(ResourceDatabasePopulator.class);
private List<Resource> scripts = new ArrayList<Resource>();
private String sqlScriptEncoding;
@@ -61,6 +60,14 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
private boolean ignoreFailedDrops = false;
private String separator = null;
/**
* @param separator the statement separator
*/
public void setSeparator(String separator) {
this.separator = separator;
}
/**
* Add a script to execute to populate the database.
@@ -114,7 +121,6 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
this.ignoreFailedDrops = ignoreFailedDrops;
}
public void populate(Connection connection) throws SQLException {
for (Resource script : this.scripts) {
executeSqlScript(connection, applyEncodingIfNecessary(script), this.continueOnError, this.ignoreFailedDrops);
@@ -124,8 +130,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
private EncodedResource applyEncodingIfNecessary(Resource script) {
if (script instanceof EncodedResource) {
return (EncodedResource) script;
}
else {
} else {
return new EncodedResource(script, this.sqlScriptEncoding);
}
}
@@ -140,8 +145,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
* @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</code>
*/
private void executeSqlScript(Connection connection, EncodedResource resource,
boolean continueOnError, boolean ignoreFailedDrops) throws SQLException {
private void executeSqlScript(Connection connection, EncodedResource resource, boolean continueOnError,
boolean ignoreFailedDrops) throws SQLException {
if (logger.isInfoEnabled()) {
logger.info("Executing SQL script from " + resource);
@@ -151,13 +156,15 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
String script;
try {
script = readScript(resource);
}
catch (IOException ex) {
} catch (IOException ex) {
throw new CannotReadScriptException(resource, ex);
}
char delimiter = ';';
if (!containsSqlScriptDelimiters(script, delimiter)) {
delimiter = '\n';
String delimiter = separator;
if (delimiter == null) {
delimiter = ";";
if (!containsSqlScriptDelimiters(script, delimiter)) {
delimiter = "\n";
}
}
splitSqlScript(script, delimiter, statements);
int lineNumber = 0;
@@ -170,26 +177,22 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
if (logger.isDebugEnabled()) {
logger.debug(rowsAffected + " rows affected by SQL: " + statement);
}
}
catch (SQLException ex) {
} catch (SQLException ex) {
boolean dropStatement = StringUtils.startsWithIgnoreCase(statement.trim(), "drop");
if (continueOnError || (dropStatement && ignoreFailedDrops)) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to execute SQL script statement at line " + lineNumber +
" of resource " + resource + ": " + statement, ex);
logger.debug("Failed to execute SQL script statement at line " + lineNumber
+ " of resource " + resource + ": " + statement, ex);
}
}
else {
} else {
throw new ScriptStatementFailedException(statement, lineNumber, resource, ex);
}
}
}
}
finally {
} finally {
try {
stmt.close();
}
catch (Throwable ex) {
} catch (Throwable ex) {
logger.debug("Could not close JDBC Statement", ex);
}
}
@@ -210,8 +213,8 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
String currentStatement = lnr.readLine();
StringBuilder scriptBuilder = new StringBuilder();
while (currentStatement != null) {
if (StringUtils.hasText(currentStatement) &&
(this.commentPrefix != null && !currentStatement.startsWith(this.commentPrefix))) {
if (StringUtils.hasText(currentStatement)
&& (this.commentPrefix != null && !currentStatement.startsWith(this.commentPrefix))) {
if (scriptBuilder.length() > 0) {
scriptBuilder.append('\n');
}
@@ -219,22 +222,34 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
}
currentStatement = lnr.readLine();
}
maybeAddSeparatorToScript(scriptBuilder);
return scriptBuilder.toString();
}
private void maybeAddSeparatorToScript(StringBuilder scriptBuilder) {
if (separator==null || separator.trim().length()==separator.length()) {
return;
}
String trimmed = separator.trim();
// separator ends in whitespace, so we might want to see if the script is trying to end the same way
if (scriptBuilder.lastIndexOf(trimmed)==scriptBuilder.length()-trimmed.length()) {
scriptBuilder.append(separator.substring(trimmed.length()));
}
}
/**
* Does the provided SQL script contain the specified delimiter?
* @param script the SQL script
* @param delim character delimiting each statement - typically a ';' character
*/
private boolean containsSqlScriptDelimiters(String script, char delim) {
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 (content[i] == delim && !inLiteral) {
if (!inLiteral && script.substring(i).startsWith(delim)) {
return true;
}
}
@@ -248,7 +263,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
* @param delim character delimiting each statement (typically a ';' character)
* @param statements the List that will contain the individual statements
*/
private void splitSqlScript(String script, char delim, List<String> statements) {
private void splitSqlScript(String script, String delim, List<String> statements) {
StringBuilder sb = new StringBuilder();
boolean inLiteral = false;
boolean inEscape = false;
@@ -258,7 +273,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
if (inEscape) {
inEscape = false;
sb.append(c);
continue;
continue;
}
// MySQL style escapes
if (c == '\\') {
@@ -270,14 +285,14 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
inLiteral = !inLiteral;
}
if (!inLiteral) {
if (c == delim) {
if (script.substring(i).startsWith(delim)) {
if (sb.length() > 0) {
statements.add(sb.toString());
sb = new StringBuilder();
}
i += delim.length() - 1;
continue;
}
else if (c == '\n' || c == '\t') {
} else if (c == '\n' || c == '\t') {
c = ' ';
}
}