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