001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jdbc.datasource.init; 018 019import java.sql.Connection; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.List; 023 024import javax.sql.DataSource; 025 026import org.springframework.core.io.Resource; 027import org.springframework.core.io.support.EncodedResource; 028import org.springframework.lang.Nullable; 029import org.springframework.util.Assert; 030import org.springframework.util.StringUtils; 031 032/** 033 * Populates, initializes, or cleans up a database using SQL scripts defined in 034 * external resources. 035 * 036 * <ul> 037 * <li>Call {@link #addScript} to add a single SQL script location. 038 * <li>Call {@link #addScripts} to add multiple SQL script locations. 039 * <li>Consult the setter methods in this class for further configuration options. 040 * <li>Call {@link #populate} or {@link #execute} to initialize or clean up the 041 * database using the configured scripts. 042 * </ul> 043 * 044 * @author Keith Donald 045 * @author Dave Syer 046 * @author Juergen Hoeller 047 * @author Chris Beams 048 * @author Oliver Gierke 049 * @author Sam Brannen 050 * @author Chris Baldwin 051 * @author Phillip Webb 052 * @since 3.0 053 * @see DatabasePopulatorUtils 054 * @see ScriptUtils 055 */ 056public class ResourceDatabasePopulator implements DatabasePopulator { 057 058 List<Resource> scripts = new ArrayList<>(); 059 060 @Nullable 061 private String sqlScriptEncoding; 062 063 private String separator = ScriptUtils.DEFAULT_STATEMENT_SEPARATOR; 064 065 private String[] commentPrefixes = ScriptUtils.DEFAULT_COMMENT_PREFIXES; 066 067 private String blockCommentStartDelimiter = ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER; 068 069 private String blockCommentEndDelimiter = ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER; 070 071 private boolean continueOnError = false; 072 073 private boolean ignoreFailedDrops = false; 074 075 076 /** 077 * Construct a new {@code ResourceDatabasePopulator} with default settings. 078 * @since 4.0.3 079 */ 080 public ResourceDatabasePopulator() { 081 } 082 083 /** 084 * Construct a new {@code ResourceDatabasePopulator} with default settings 085 * for the supplied scripts. 086 * @param scripts the scripts to execute to initialize or clean up the database 087 * (never {@code null}) 088 * @since 4.0.3 089 */ 090 public ResourceDatabasePopulator(Resource... scripts) { 091 setScripts(scripts); 092 } 093 094 /** 095 * Construct a new {@code ResourceDatabasePopulator} with the supplied values. 096 * @param continueOnError flag to indicate that all failures in SQL should be 097 * logged but not cause a failure 098 * @param ignoreFailedDrops flag to indicate that a failed SQL {@code DROP} 099 * statement can be ignored 100 * @param sqlScriptEncoding the encoding for the supplied SQL scripts 101 * (may be {@code null} or <em>empty</em> to indicate platform encoding) 102 * @param scripts the scripts to execute to initialize or clean up the database 103 * (never {@code null}) 104 * @since 4.0.3 105 */ 106 public ResourceDatabasePopulator(boolean continueOnError, boolean ignoreFailedDrops, 107 @Nullable String sqlScriptEncoding, Resource... scripts) { 108 109 this.continueOnError = continueOnError; 110 this.ignoreFailedDrops = ignoreFailedDrops; 111 setSqlScriptEncoding(sqlScriptEncoding); 112 setScripts(scripts); 113 } 114 115 116 /** 117 * Add a script to execute to initialize or clean up the database. 118 * @param script the path to an SQL script (never {@code null}) 119 */ 120 public void addScript(Resource script) { 121 Assert.notNull(script, "'script' must not be null"); 122 this.scripts.add(script); 123 } 124 125 /** 126 * Add multiple scripts to execute to initialize or clean up the database. 127 * @param scripts the scripts to execute (never {@code null}) 128 */ 129 public void addScripts(Resource... scripts) { 130 assertContentsOfScriptArray(scripts); 131 this.scripts.addAll(Arrays.asList(scripts)); 132 } 133 134 /** 135 * Set the scripts to execute to initialize or clean up the database, 136 * replacing any previously added scripts. 137 * @param scripts the scripts to execute (never {@code null}) 138 */ 139 public void setScripts(Resource... scripts) { 140 assertContentsOfScriptArray(scripts); 141 // Ensure that the list is modifiable 142 this.scripts = new ArrayList<>(Arrays.asList(scripts)); 143 } 144 145 private void assertContentsOfScriptArray(Resource... scripts) { 146 Assert.notNull(scripts, "'scripts' must not be null"); 147 Assert.noNullElements(scripts, "'scripts' must not contain null elements"); 148 } 149 150 /** 151 * Specify the encoding for the configured SQL scripts, 152 * if different from the platform encoding. 153 * @param sqlScriptEncoding the encoding used in scripts 154 * (may be {@code null} or empty to indicate platform encoding) 155 * @see #addScript(Resource) 156 */ 157 public void setSqlScriptEncoding(@Nullable String sqlScriptEncoding) { 158 this.sqlScriptEncoding = (StringUtils.hasText(sqlScriptEncoding) ? sqlScriptEncoding : null); 159 } 160 161 /** 162 * Specify the statement separator, if a custom one. 163 * <p>Defaults to {@code ";"} if not specified and falls back to {@code "\n"} 164 * as a last resort; may be set to {@link ScriptUtils#EOF_STATEMENT_SEPARATOR} 165 * to signal that each script contains a single statement without a separator. 166 * @param separator the script statement separator 167 */ 168 public void setSeparator(String separator) { 169 this.separator = separator; 170 } 171 172 /** 173 * Set the prefix that identifies single-line comments within the SQL scripts. 174 * <p>Defaults to {@code "--"}. 175 * @param commentPrefix the prefix for single-line comments 176 * @see #setCommentPrefixes(String...) 177 */ 178 public void setCommentPrefix(String commentPrefix) { 179 Assert.hasText(commentPrefix, "'commentPrefix' must not be null or empty"); 180 this.commentPrefixes = new String[] { commentPrefix }; 181 } 182 183 /** 184 * Set the prefixes that identify single-line comments within the SQL scripts. 185 * <p>Defaults to {@code ["--"]}. 186 * @param commentPrefixes the prefixes for single-line comments 187 * @since 5.2 188 */ 189 public void setCommentPrefixes(String... commentPrefixes) { 190 Assert.notEmpty(commentPrefixes, "'commentPrefixes' must not be null or empty"); 191 Assert.noNullElements(commentPrefixes, "'commentPrefixes' must not contain null elements"); 192 this.commentPrefixes = commentPrefixes; 193 } 194 195 /** 196 * Set the start delimiter that identifies block comments within the SQL 197 * scripts. 198 * <p>Defaults to {@code "/*"}. 199 * @param blockCommentStartDelimiter the start delimiter for block comments 200 * (never {@code null} or empty) 201 * @since 4.0.3 202 * @see #setBlockCommentEndDelimiter 203 */ 204 public void setBlockCommentStartDelimiter(String blockCommentStartDelimiter) { 205 Assert.hasText(blockCommentStartDelimiter, "'blockCommentStartDelimiter' must not be null or empty"); 206 this.blockCommentStartDelimiter = blockCommentStartDelimiter; 207 } 208 209 /** 210 * Set the end delimiter that identifies block comments within the SQL 211 * scripts. 212 * <p>Defaults to <code>"*/"</code>. 213 * @param blockCommentEndDelimiter the end delimiter for block comments 214 * (never {@code null} or empty) 215 * @since 4.0.3 216 * @see #setBlockCommentStartDelimiter 217 */ 218 public void setBlockCommentEndDelimiter(String blockCommentEndDelimiter) { 219 Assert.hasText(blockCommentEndDelimiter, "'blockCommentEndDelimiter' must not be null or empty"); 220 this.blockCommentEndDelimiter = blockCommentEndDelimiter; 221 } 222 223 /** 224 * Flag to indicate that all failures in SQL should be logged but not cause a failure. 225 * <p>Defaults to {@code false}. 226 * @param continueOnError {@code true} if script execution should continue on error 227 */ 228 public void setContinueOnError(boolean continueOnError) { 229 this.continueOnError = continueOnError; 230 } 231 232 /** 233 * Flag to indicate that a failed SQL {@code DROP} statement can be ignored. 234 * <p>This is useful for a non-embedded database whose SQL dialect does not 235 * support an {@code IF EXISTS} clause in a {@code DROP} statement. 236 * <p>The default is {@code false} so that if the populator runs accidentally, it will 237 * fail fast if a script starts with a {@code DROP} statement. 238 * @param ignoreFailedDrops {@code true} if failed drop statements should be ignored 239 */ 240 public void setIgnoreFailedDrops(boolean ignoreFailedDrops) { 241 this.ignoreFailedDrops = ignoreFailedDrops; 242 } 243 244 245 /** 246 * {@inheritDoc} 247 * @see #execute(DataSource) 248 */ 249 @Override 250 public void populate(Connection connection) throws ScriptException { 251 Assert.notNull(connection, "'connection' must not be null"); 252 for (Resource script : this.scripts) { 253 EncodedResource encodedScript = new EncodedResource(script, this.sqlScriptEncoding); 254 ScriptUtils.executeSqlScript(connection, encodedScript, this.continueOnError, this.ignoreFailedDrops, 255 this.commentPrefixes, this.separator, this.blockCommentStartDelimiter, this.blockCommentEndDelimiter); 256 } 257 } 258 259 /** 260 * Execute this {@code ResourceDatabasePopulator} against the given 261 * {@link DataSource}. 262 * <p>Delegates to {@link DatabasePopulatorUtils#execute}. 263 * @param dataSource the {@code DataSource} to execute against (never {@code null}) 264 * @throws ScriptException if an error occurs 265 * @since 4.1 266 * @see #populate(Connection) 267 */ 268 public void execute(DataSource dataSource) throws ScriptException { 269 DatabasePopulatorUtils.execute(this, dataSource); 270 } 271 272}