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}