11/*
2- * Copyright 2002-2009 the original author or authors.
2+ * Copyright 2002-2012 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1818
1919import java .io .IOException ;
2020import java .io .LineNumberReader ;
21+ import java .util .LinkedList ;
2122import java .util .List ;
2223
24+ import org .apache .commons .logging .Log ;
25+ import org .apache .commons .logging .LogFactory ;
26+
27+ import org .springframework .core .io .Resource ;
28+ import org .springframework .core .io .ResourceLoader ;
29+ import org .springframework .core .io .support .EncodedResource ;
30+ import org .springframework .dao .DataAccessException ;
31+ import org .springframework .dao .DataAccessResourceFailureException ;
32+ import org .springframework .jdbc .core .JdbcTemplate ;
2333import org .springframework .util .StringUtils ;
2434
2535/**
26- * JdbcTestUtils is a collection of JDBC related utility methods for use in unit
27- * and integration testing scenarios.
36+ * {@code JdbcTestUtils} is a collection of JDBC related utility functions
37+ * intended to simplify standard database testing scenarios.
2838 *
39+ * <p>As of Spring 3.2, {@code JdbcTestUtils} supersedes {@link SimpleJdbcTestUtils}.
40+ *
2941 * @author Thomas Risberg
42+ * @author Sam Brannen
43+ * @author Juergen Hoeller
3044 * @since 2.5.4
3145 */
3246public class JdbcTestUtils {
3347
48+ private static final Log logger = LogFactory .getLog (JdbcTestUtils .class );
49+
50+
51+ /**
52+ * Count the rows in the given table.
53+ *
54+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
55+ * @param tableName name of the table to count rows in
56+ * @return the number of rows in the table
57+ * @since 3.2
58+ */
59+ public static int countRowsInTable (JdbcTemplate jdbcTemplate , String tableName ) {
60+ return jdbcTemplate .queryForInt ("SELECT COUNT(0) FROM " + tableName );
61+ }
62+
63+ /**
64+ * Count the rows in the given table, using the provided {@code WHERE} clause.
65+ *
66+ * <p>If the provided {@code WHERE} clause contains text, it will be prefixed
67+ * with {@code " WHERE "} and then appended to the generated {@code SELECT}
68+ * statement. For example, if the provided table name is {@code "person"} and
69+ * the provided where clause is {@code "name = 'Bob' and age > 25"}, the
70+ * resulting SQL statement to execute will be
71+ * {@code "SELECT COUNT(0) FROM person WHERE name = 'Bob' and age > 25"}.
72+ *
73+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
74+ * @param tableName the name of the table to count rows in
75+ * @param whereClause the {@code WHERE} clause to append to the query
76+ * @return the number of rows in the table that match the provided
77+ * {@code WHERE} clause
78+ * @since 3.2
79+ */
80+ public static int countRowsInTableWhere (JdbcTemplate jdbcTemplate , String tableName , String whereClause ) {
81+ String sql = "SELECT COUNT(0) FROM " + tableName ;
82+
83+ if (StringUtils .hasText (whereClause )) {
84+ sql += " WHERE " + whereClause ;
85+ }
86+
87+ return jdbcTemplate .queryForInt (sql );
88+ }
89+
90+ /**
91+ * Delete all rows from the specified tables.
92+ *
93+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
94+ * @param tableNames the names of the tables to delete from
95+ * @return the total number of rows deleted from all specified tables
96+ * @since 3.2
97+ */
98+ public static int deleteFromTables (JdbcTemplate jdbcTemplate , String ... tableNames ) {
99+ int totalRowCount = 0 ;
100+ for (String tableName : tableNames ) {
101+ int rowCount = jdbcTemplate .update ("DELETE FROM " + tableName );
102+ totalRowCount += rowCount ;
103+ if (logger .isInfoEnabled ()) {
104+ logger .info ("Deleted " + rowCount + " rows from table " + tableName );
105+ }
106+ }
107+ return totalRowCount ;
108+ }
109+
110+ /**
111+ * Drop the specified tables.
112+ *
113+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
114+ * @param tableNames the names of the tables to drop
115+ * @since 3.2
116+ */
117+ public static void dropTables (JdbcTemplate jdbcTemplate , String ... tableNames ) {
118+ for (String tableName : tableNames ) {
119+ jdbcTemplate .execute ("DROP TABLE " + tableName );
120+ if (logger .isInfoEnabled ()) {
121+ logger .info ("Dropped table " + tableName );
122+ }
123+ }
124+ }
125+
34126 /**
35- * Read a script from the LineNumberReader and build a String containing the
36- * lines.
37- *
38- * @param lineNumberReader the <code>LineNumberReader</code> containing the
39- * script to be processed
40- * @return <code>String</code> containing the script lines
41- * @throws IOException
127+ * Execute the given SQL script.
128+ *
129+ * <p>The script will typically be loaded from the classpath. There should
130+ * be one statement per line. Any semicolons will be removed.
131+ *
132+ * <p><b>Do not use this method to execute DDL if you expect rollback.</b>
133+ *
134+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
135+ * @param resourceLoader the resource loader with which to load the SQL script
136+ * @param sqlResourcePath the Spring resource path for the SQL script
137+ * @param continueOnError whether or not to continue without throwing an
138+ * exception in the event of an error
139+ * @throws DataAccessException if there is an error executing a statement
140+ * and {@code continueOnError} is {@code false}
141+ * @since 3.2
142+ */
143+ public static void executeSqlScript (JdbcTemplate jdbcTemplate , ResourceLoader resourceLoader ,
144+ String sqlResourcePath , boolean continueOnError ) throws DataAccessException {
145+ Resource resource = resourceLoader .getResource (sqlResourcePath );
146+ executeSqlScript (jdbcTemplate , resource , continueOnError );
147+ }
148+
149+ /**
150+ * Execute the given SQL script.
151+ *
152+ * <p>The script will typically be loaded from the classpath. Statements
153+ * should be delimited with a semicolon. If statements are not delimited with
154+ * a semicolon then there should be one statement per line. Statements are
155+ * allowed to span lines only if they are delimited with a semicolon.
156+ *
157+ * <p><b>Do not use this method to execute DDL if you expect rollback.</b>
158+ *
159+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
160+ * @param resource the resource to load the SQL script from
161+ * @param continueOnError whether or not to continue without throwing an
162+ * exception in the event of an error
163+ * @throws DataAccessException if there is an error executing a statement
164+ * and {@code continueOnError} is {@code false}
165+ * @since 3.2
166+ */
167+ public static void executeSqlScript (JdbcTemplate jdbcTemplate , Resource resource , boolean continueOnError )
168+ throws DataAccessException {
169+ executeSqlScript (jdbcTemplate , new EncodedResource (resource ), continueOnError );
170+ }
171+
172+ /**
173+ * Execute the given SQL script.
174+ *
175+ * <p>The script will typically be loaded from the classpath. There should
176+ * be one statement per line. Any semicolons will be removed.
177+ *
178+ * <p><b>Do not use this method to execute DDL if you expect rollback.</b>
179+ *
180+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
181+ * @param resource the resource (potentially associated with a specific encoding)
182+ * to load the SQL script from
183+ * @param continueOnError whether or not to continue without throwing an
184+ * exception in the event of an error
185+ * @throws DataAccessException if there is an error executing a statement
186+ * and {@code continueOnError} is {@code false}
187+ * @since 3.2
188+ */
189+ public static void executeSqlScript (JdbcTemplate jdbcTemplate , EncodedResource resource , boolean continueOnError )
190+ throws DataAccessException {
191+
192+ if (logger .isInfoEnabled ()) {
193+ logger .info ("Executing SQL script from " + resource );
194+ }
195+
196+ long startTime = System .currentTimeMillis ();
197+ List <String > statements = new LinkedList <String >();
198+ LineNumberReader reader = null ;
199+ try {
200+ reader = new LineNumberReader (resource .getReader ());
201+ String script = readScript (reader );
202+ char delimiter = ';' ;
203+ if (!containsSqlScriptDelimiters (script , delimiter )) {
204+ delimiter = '\n' ;
205+ }
206+ splitSqlScript (script , delimiter , statements );
207+ for (String statement : statements ) {
208+ try {
209+ int rowsAffected = jdbcTemplate .update (statement );
210+ if (logger .isDebugEnabled ()) {
211+ logger .debug (rowsAffected + " rows affected by SQL: " + statement );
212+ }
213+ } catch (DataAccessException ex ) {
214+ if (continueOnError ) {
215+ if (logger .isWarnEnabled ()) {
216+ logger .warn ("SQL statement [" + statement + "] failed" , ex );
217+ }
218+ } else {
219+ throw ex ;
220+ }
221+ }
222+ }
223+ long elapsedTime = System .currentTimeMillis () - startTime ;
224+ if (logger .isInfoEnabled ()) {
225+ logger .info (String .format ("Executed SQL script from %s in %s ms." , resource , elapsedTime ));
226+ }
227+ } catch (IOException ex ) {
228+ throw new DataAccessResourceFailureException ("Failed to open SQL script from " + resource , ex );
229+ } finally {
230+ try {
231+ if (reader != null ) {
232+ reader .close ();
233+ }
234+ } catch (IOException ex ) {
235+ // ignore
236+ }
237+ }
238+ }
239+
240+ /**
241+ * Read a script from the provided {@code LineNumberReader} and build a
242+ * {@code String} containing the lines.
243+ *
244+ * @param lineNumberReader the {@code LineNumberReader} containing the script
245+ * to be processed
246+ * @return a {@code String} containing the script lines
42247 */
43248 public static String readScript (LineNumberReader lineNumberReader ) throws IOException {
44249 String currentStatement = lineNumberReader .readLine ();
@@ -56,11 +261,13 @@ public static String readScript(LineNumberReader lineNumberReader) throws IOExce
56261 }
57262
58263 /**
59- * Does the provided SQL script contain the specified delimiter?
60- *
264+ * Determine if the provided SQL script contains the specified delimiter.
265+ *
61266 * @param script the SQL script
62- * @param delim character delimiting each statement - typically a ';'
267+ * @param delim character delimiting each statement — typically a ';'
63268 * character
269+ * @return {@code true} if the script contains the delimiter; {@code false}
270+ * otherwise
64271 */
65272 public static boolean containsSqlScriptDelimiters (String script , char delim ) {
66273 boolean inLiteral = false ;
@@ -80,11 +287,11 @@ public static boolean containsSqlScriptDelimiters(String script, char delim) {
80287 * Split an SQL script into separate statements delimited with the provided
81288 * delimiter character. Each individual statement will be added to the
82289 * provided <code>List</code>.
83- *
290+ *
84291 * @param script the SQL script
85- * @param delim character delimiting each statement - typically a ';'
292+ * @param delim character delimiting each statement — typically a ';'
86293 * character
87- * @param statements the List that will contain the individual statements
294+ * @param statements the list that will contain the individual statements
88295 */
89296 public static void splitSqlScript (String script , char delim , List <String > statements ) {
90297 StringBuilder sb = new StringBuilder ();
@@ -99,8 +306,7 @@ public static void splitSqlScript(String script, char delim, List<String> statem
99306 statements .add (sb .toString ());
100307 sb = new StringBuilder ();
101308 }
102- }
103- else {
309+ } else {
104310 sb .append (content [i ]);
105311 }
106312 }
0 commit comments