001: /*
002: * Copyright 2002-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://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, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016:
017: package org.springframework.jdbc.object;
018:
019: import java.sql.PreparedStatement;
020: import java.sql.SQLException;
021: import java.util.ArrayList;
022: import java.util.Iterator;
023: import java.util.LinkedList;
024: import java.util.List;
025:
026: import javax.sql.DataSource;
027:
028: import org.springframework.dao.DataAccessException;
029: import org.springframework.jdbc.core.BatchPreparedStatementSetter;
030:
031: /**
032: * SqlUpdate subclass that performs batch update operations. Encapsulates
033: * queuing up records to be updated, and adds them as a single batch once
034: * <code>flush</code> is called or the given batch size has been met.
035: *
036: * <p>Note that this class is a <b>non-thread-safe object</b>, in contrast
037: * to all other JDBC operations objects in this package. You need to create
038: * a new instance of it for each use, or call <code>reset</code> before
039: * reuse within the same thread.
040: *
041: * @author Keith Donald
042: * @author Juergen Hoeller
043: * @since 1.1
044: * @see #flush
045: * @see #reset
046: */
047: public class BatchSqlUpdate extends SqlUpdate {
048:
049: /**
050: * Default number of inserts to accumulate before commiting a batch (5000).
051: */
052: public static int DEFAULT_BATCH_SIZE = 5000;
053:
054: private int batchSize = DEFAULT_BATCH_SIZE;
055:
056: private boolean trackRowsAffected = true;
057:
058: private final LinkedList parameterQueue = new LinkedList();
059:
060: private final List rowsAffected = new ArrayList();
061:
062: /**
063: * Constructor to allow use as a JavaBean. DataSource and SQL
064: * must be supplied before compilation and use.
065: * @see #setDataSource
066: * @see #setSql
067: */
068: public BatchSqlUpdate() {
069: super ();
070: }
071:
072: /**
073: * Construct an update object with a given DataSource and SQL.
074: * @param ds DataSource to use to obtain connections
075: * @param sql SQL statement to execute
076: */
077: public BatchSqlUpdate(DataSource ds, String sql) {
078: super (ds, sql);
079: }
080:
081: /**
082: * Construct an update object with a given DataSource, SQL
083: * and anonymous parameters.
084: * @param ds DataSource to use to obtain connections
085: * @param sql SQL statement to execute
086: * @param types SQL types of the parameters, as defined in the
087: * <code>java.sql.Types</code> class
088: * @see java.sql.Types
089: */
090: public BatchSqlUpdate(DataSource ds, String sql, int[] types) {
091: super (ds, sql, types);
092: }
093:
094: /**
095: * Construct an update object with a given DataSource, SQL,
096: * anonymous parameters and specifying the maximum number of rows
097: * that may be affected.
098: * @param ds DataSource to use to obtain connections
099: * @param sql SQL statement to execute
100: * @param types SQL types of the parameters, as defined in the
101: * <code>java.sql.Types</code> class
102: * @param batchSize the number of statements that will trigger
103: * an automatic intermediate flush
104: * @see java.sql.Types
105: */
106: public BatchSqlUpdate(DataSource ds, String sql, int[] types,
107: int batchSize) {
108: super (ds, sql, types);
109: setBatchSize(batchSize);
110: }
111:
112: /**
113: * Set the number of statements that will trigger an automatic intermediate
114: * flush. <code>update</code> calls or the given statement parameters will
115: * be queued until the batch size is met, at which point it will empty the
116: * queue and execute the batch.
117: * <p>You can also flush already queued statements with an explicit
118: * <code>flush</code> call. Note that you need to this after queueing
119: * all parameters to guarantee that all statements have been flushed.
120: */
121: public void setBatchSize(int batchSize) {
122: this .batchSize = batchSize;
123: }
124:
125: /**
126: * Set whether to track the rows affected by batch updates performed
127: * by this operation object.
128: * <p>Default is "true". Turn this off to save the memory needed for
129: * the list of row counts.
130: * @see #getRowsAffected()
131: */
132: public void setTrackRowsAffected(boolean trackRowsAffected) {
133: this .trackRowsAffected = trackRowsAffected;
134: }
135:
136: /**
137: * BatchSqlUpdate does not support BLOB or CLOB parameters.
138: */
139: protected boolean supportsLobParameters() {
140: return false;
141: }
142:
143: /**
144: * Overridden version of <code>update</code> that adds the given statement
145: * parameters to the queue rather than executing them immediately.
146: * All other <code>update</code> methods of the SqlUpdate base class go
147: * through this method and will thus behave similarly.
148: * <p>You need to call <code>flush</code> to actually execute the batch.
149: * If the specified batch size is reached, an implicit flush will happen;
150: * you still need to finally call <code>flush</code> to flush all statements.
151: * @param params array of parameter objects
152: * @return the number of rows affected by the update (always -1,
153: * meaning "not applicable", as the statement is not actually
154: * executed by this method)
155: * @see #flush
156: */
157: public int update(Object[] params) throws DataAccessException {
158: validateParameters(params);
159: this .parameterQueue.add(params.clone());
160:
161: if (this .parameterQueue.size() == this .batchSize) {
162: if (logger.isDebugEnabled()) {
163: logger
164: .debug("Triggering auto-flush because queue reached batch size of "
165: + this .batchSize);
166: }
167: flush();
168: }
169:
170: return -1;
171: }
172:
173: /**
174: * Trigger any queued update operations to be added as a final batch.
175: * @return an array of the number of rows affected by each statement
176: */
177: public int[] flush() {
178: if (this .parameterQueue.isEmpty()) {
179: return new int[0];
180: }
181:
182: int[] rowsAffected = getJdbcTemplate().batchUpdate(getSql(),
183: new BatchPreparedStatementSetter() {
184: public int getBatchSize() {
185: return parameterQueue.size();
186: }
187:
188: public void setValues(PreparedStatement ps,
189: int index) throws SQLException {
190: Object[] params = (Object[]) parameterQueue
191: .removeFirst();
192: newPreparedStatementSetter(params)
193: .setValues(ps);
194: }
195: });
196:
197: if (this .trackRowsAffected) {
198: for (int i = 0; i < rowsAffected.length; i++) {
199: this .rowsAffected.add(new Integer(rowsAffected[i]));
200: }
201: }
202: for (int i = 0; i < rowsAffected.length; i++) {
203: checkRowsAffected(rowsAffected[i]);
204: }
205: return rowsAffected;
206: }
207:
208: /**
209: * Return the current number of statements or statement parameters
210: * in the queue.
211: */
212: public int getQueueCount() {
213: return this .parameterQueue.size();
214: }
215:
216: /**
217: * Return the number of already executed statements.
218: */
219: public int getExecutionCount() {
220: return this .rowsAffected.size();
221: }
222:
223: /**
224: * Return the number of affected rows for all already executed statements.
225: * Accumulates all of <code>flush</code>'s return values until
226: * <code>reset</code> is invoked.
227: * @return an array of the number of rows affected by each statement
228: * @see #reset
229: */
230: public int[] getRowsAffected() {
231: int[] result = new int[this .rowsAffected.size()];
232: int i = 0;
233: for (Iterator it = this .rowsAffected.iterator(); it.hasNext(); i++) {
234: Integer rowCount = (Integer) it.next();
235: result[i] = rowCount.intValue();
236: }
237: return result;
238: }
239:
240: /**
241: * Reset the statement parameter queue, the rows affected cache,
242: * and the execution count.
243: */
244: public void reset() {
245: this.parameterQueue.clear();
246: this.rowsAffected.clear();
247: }
248:
249: }
|