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}