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.ArrayDeque;
022import java.util.ArrayList;
023import java.util.Deque;
024import java.util.List;
025
026import javax.sql.DataSource;
027
028import org.springframework.dao.DataAccessException;
029import 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} 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} 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 */
047public class BatchSqlUpdate extends SqlUpdate {
048
049        /**
050         * Default number of inserts to accumulate before committing a batch (5000).
051         */
052        public static final int DEFAULT_BATCH_SIZE = 5000;
053
054
055        private int batchSize = DEFAULT_BATCH_SIZE;
056
057        private boolean trackRowsAffected = true;
058
059        private final Deque<Object[]> parameterQueue = new ArrayDeque<>();
060
061        private final List<Integer> rowsAffected = new ArrayList<>();
062
063
064        /**
065         * Constructor to allow use as a JavaBean. DataSource and SQL
066         * must be supplied before compilation and use.
067         * @see #setDataSource
068         * @see #setSql
069         */
070        public BatchSqlUpdate() {
071                super();
072        }
073
074        /**
075         * Construct an update object with a given DataSource and SQL.
076         * @param ds the DataSource to use to obtain connections
077         * @param sql the SQL statement to execute
078         */
079        public BatchSqlUpdate(DataSource ds, String sql) {
080                super(ds, sql);
081        }
082
083        /**
084         * Construct an update object with a given DataSource, SQL
085         * and anonymous parameters.
086         * @param ds the DataSource to use to obtain connections
087         * @param sql the SQL statement to execute
088         * @param types the SQL types of the parameters, as defined in the
089         * {@code java.sql.Types} class
090         * @see java.sql.Types
091         */
092        public BatchSqlUpdate(DataSource ds, String sql, int[] types) {
093                super(ds, sql, types);
094        }
095
096        /**
097         * Construct an update object with a given DataSource, SQL,
098         * anonymous parameters and specifying the maximum number of rows
099         * that may be affected.
100         * @param ds the DataSource to use to obtain connections
101         * @param sql the SQL statement to execute
102         * @param types the SQL types of the parameters, as defined in the
103         * {@code java.sql.Types} class
104         * @param batchSize the number of statements that will trigger
105         * an automatic intermediate flush
106         * @see java.sql.Types
107         */
108        public BatchSqlUpdate(DataSource ds, String sql, int[] types, int batchSize) {
109                super(ds, sql, types);
110                setBatchSize(batchSize);
111        }
112
113
114        /**
115         * Set the number of statements that will trigger an automatic intermediate
116         * flush. {@code update} calls or the given statement parameters will
117         * be queued until the batch size is met, at which point it will empty the
118         * queue and execute the batch.
119         * <p>You can also flush already queued statements with an explicit
120         * {@code flush} call. Note that you need to this after queueing
121         * all parameters to guarantee that all statements have been flushed.
122         */
123        public void setBatchSize(int batchSize) {
124                this.batchSize = batchSize;
125        }
126
127        /**
128         * Set whether to track the rows affected by batch updates performed
129         * by this operation object.
130         * <p>Default is "true". Turn this off to save the memory needed for
131         * the list of row counts.
132         * @see #getRowsAffected()
133         */
134        public void setTrackRowsAffected(boolean trackRowsAffected) {
135                this.trackRowsAffected = trackRowsAffected;
136        }
137
138        /**
139         * BatchSqlUpdate does not support BLOB or CLOB parameters.
140         */
141        @Override
142        protected boolean supportsLobParameters() {
143                return false;
144        }
145
146
147        /**
148         * Overridden version of {@code update} that adds the given statement
149         * parameters to the queue rather than executing them immediately.
150         * All other {@code update} methods of the SqlUpdate base class go
151         * through this method and will thus behave similarly.
152         * <p>You need to call {@code flush} to actually execute the batch.
153         * If the specified batch size is reached, an implicit flush will happen;
154         * you still need to finally call {@code flush} to flush all statements.
155         * @param params array of parameter objects
156         * @return the number of rows affected by the update (always -1,
157         * meaning "not applicable", as the statement is not actually
158         * executed by this method)
159         * @see #flush
160         */
161        @Override
162        public int update(Object... params) throws DataAccessException {
163                validateParameters(params);
164                this.parameterQueue.add(params.clone());
165
166                if (this.parameterQueue.size() == this.batchSize) {
167                        if (logger.isDebugEnabled()) {
168                                logger.debug("Triggering auto-flush because queue reached batch size of " + this.batchSize);
169                        }
170                        flush();
171                }
172
173                return -1;
174        }
175
176        /**
177         * Trigger any queued update operations to be added as a final batch.
178         * @return an array of the number of rows affected by each statement
179         */
180        public int[] flush() {
181                if (this.parameterQueue.isEmpty()) {
182                        return new int[0];
183                }
184
185                int[] rowsAffected = getJdbcTemplate().batchUpdate(
186                                resolveSql(),
187                                new BatchPreparedStatementSetter() {
188                                        @Override
189                                        public int getBatchSize() {
190                                                return parameterQueue.size();
191                                        }
192                                        @Override
193                                        public void setValues(PreparedStatement ps, int index) throws SQLException {
194                                                Object[] params = parameterQueue.removeFirst();
195                                                newPreparedStatementSetter(params).setValues(ps);
196                                        }
197                                });
198
199                for (int rowCount : rowsAffected) {
200                        checkRowsAffected(rowCount);
201                        if (this.trackRowsAffected) {
202                                this.rowsAffected.add(rowCount);
203                        }
204                }
205
206                return rowsAffected;
207        }
208
209        /**
210         * Return the current number of statements or statement parameters
211         * in the queue.
212         */
213        public int getQueueCount() {
214                return this.parameterQueue.size();
215        }
216
217        /**
218         * Return the number of already executed statements.
219         */
220        public int getExecutionCount() {
221                return this.rowsAffected.size();
222        }
223
224        /**
225         * Return the number of affected rows for all already executed statements.
226         * Accumulates all of {@code flush}'s return values until
227         * {@code reset} is invoked.
228         * @return an array of the number of rows affected by each statement
229         * @see #reset
230         */
231        public int[] getRowsAffected() {
232                int[] result = new int[this.rowsAffected.size()];
233                for (int i = 0; i < this.rowsAffected.size(); i++) {
234                        result[i] = this.rowsAffected.get(i);
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}