Skip to content

Commit 0adcb2a

Browse files
author
Thomas Risberg
committed
Added batchUpdate method taking a Collection, a batch size and a ParameterizedPreparedStatementSetter as arguments (SPR-6334)
1 parent cfb3873 commit 0adcb2a

File tree

5 files changed

+290
-44
lines changed

5 files changed

+290
-44
lines changed

org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.jdbc.core;
1818

19+
import java.util.Collection;
1920
import java.util.List;
2021
import java.util.Map;
2122

@@ -1004,7 +1005,19 @@ <T>List<T> queryForList(String sql, Object[] args, int[] argTypes, Class<T> elem
10041005
* @return an array containing the numbers of rows affected by each update in the batch
10051006
*/
10061007
public int[] batchUpdate(String sql, List<Object[]> batchArgs, int[] argTypes);
1007-
1008+
1009+
/**
1010+
* Execute multiple batches using the supplied SQL statement with the collect of supplied arguments.
1011+
* The arguments' values will be set using the ParameterizedPreparedStatementSetter.
1012+
* Each batch should be of size indicated in 'batchSize'.
1013+
* @param sql the SQL statement to execute.
1014+
* @param batchArgs the List of Object arrays containing the batch of arguments for the query
1015+
* @param argTypes SQL types of the arguments
1016+
* (constants from <code>java.sql.Types</code>)
1017+
* @return an array containing for each batch another array containing the numbers of rows affected
1018+
* by each update in the batch
1019+
*/
1020+
public <T> int[][] batchUpdate(String sql, Collection<T> batchArgs, int batchSize, ParameterizedPreparedStatementSetter<T> pss);
10081021

10091022
//-------------------------------------------------------------------------
10101023
// Methods dealing with callable statements

org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.sql.SQLWarning;
2929
import java.sql.Statement;
3030
import java.util.ArrayList;
31+
import java.util.Collection;
3132
import java.util.Collections;
3233
import java.util.HashMap;
3334
import java.util.LinkedHashMap;
@@ -929,7 +930,59 @@ public int[] batchUpdate(String sql, List<Object[]> batchArgs) {
929930
public int[] batchUpdate(String sql, List<Object[]> batchArgs, int[] argTypes) {
930931
return BatchUpdateUtils.executeBatchUpdate(sql, batchArgs, argTypes, this);
931932
}
932-
933+
934+
/*
935+
* (non-Javadoc)
936+
* @see org.springframework.jdbc.core.JdbcOperations#batchUpdate(java.lang.String, java.util.Collection, int, org.springframework.jdbc.core.ParameterizedPreparedStatementSetter)
937+
*
938+
* Contribution by Nicolas Fabre
939+
*/
940+
public <T> int[][] batchUpdate(String sql, final Collection<T> batchArgs, final int batchSize, final ParameterizedPreparedStatementSetter<T> pss) {
941+
if (logger.isDebugEnabled()) {
942+
logger.debug("Executing SQL batch update [" + sql + "] with a batch size of " + batchSize);
943+
}
944+
return execute(sql, new PreparedStatementCallback<int[][]>() {
945+
public int[][] doInPreparedStatement(PreparedStatement ps) throws SQLException {
946+
List<int[]> rowsAffected = new ArrayList<int[]>();
947+
try {
948+
boolean batchSupported = true;
949+
if (!JdbcUtils.supportsBatchUpdates(ps.getConnection())) {
950+
batchSupported = false;
951+
logger.warn("JDBC Driver does not support Batch updates; resorting to single statement execution");
952+
}
953+
int n = 0;
954+
for (T obj : batchArgs) {
955+
pss.setValues(ps, obj);
956+
n++;
957+
if (batchSupported) {
958+
ps.addBatch();
959+
if (n % batchSize == 0 || n == batchArgs.size()) {
960+
if (logger.isDebugEnabled()) {
961+
int batchIdx = (n % batchSize == 0) ? n / batchSize : (n / batchSize) + 1;
962+
int items = n - ((n % batchSize == 0) ? n / batchSize - 1 : (n / batchSize)) * batchSize;
963+
logger.debug("Sending SQL batch update #" + batchIdx + " with " + items + " items");
964+
}
965+
rowsAffected.add(ps.executeBatch());
966+
}
967+
}
968+
else {
969+
int i = ps.executeUpdate();
970+
rowsAffected.add(new int[] {i});
971+
}
972+
}
973+
int[][] result = new int[rowsAffected.size()][];
974+
for (int i = 0; i < result.length; i++) {
975+
result[i] = rowsAffected.get(i);
976+
}
977+
return result;
978+
} finally {
979+
if (pss instanceof ParameterDisposer) {
980+
((ParameterDisposer) pss).cleanupParameters();
981+
}
982+
}
983+
}
984+
});
985+
}
933986

934987
//-------------------------------------------------------------------------
935988
// Methods dealing with callable statements
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2002-2011 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.jdbc.core;
18+
19+
import java.sql.PreparedStatement;
20+
import java.sql.SQLException;
21+
22+
/**
23+
* Parameterized callback interface used by the {@link JdbcTemplate} class for batch updates.
24+
*
25+
* <p>This interface sets values on a {@link java.sql.PreparedStatement} provided
26+
* by the JdbcTemplate class, for each of a number of updates in a batch using the
27+
* same SQL. Implementations are responsible for setting any necessary parameters.
28+
* SQL with placeholders will already have been supplied.
29+
*
30+
* <p>Implementations <i>do not</i> need to concern themselves with SQLExceptions
31+
* that may be thrown from operations they attempt. The JdbcTemplate class will
32+
* catch and handle SQLExceptions appropriately.
33+
*
34+
* @author Nicolas Fabre
35+
* @author Thomas Risberg
36+
* @since 3.1
37+
* @see JdbcTemplate#batchUpdate(String sql, Collection<T> objs, int batchSize, ParameterizedPreparedStatementSetter<T> pss)
38+
*/
39+
public interface ParameterizedPreparedStatementSetter<T> {
40+
41+
/**
42+
* Set parameter values on the given PreparedStatement.
43+
*
44+
* @param ps the PreparedStatement to invoke setter methods on
45+
* @param argument the object containing the values to be set
46+
* @throws SQLException if a SQLException is encountered (i.e. there is no need to catch SQLException)
47+
*/
48+
void setValues(PreparedStatement ps, T argument) throws SQLException;
49+
50+
}

org.springframework.jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateTests.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.sql.Statement;
2727
import java.sql.Types;
2828
import java.util.ArrayList;
29+
import java.util.Collection;
2930
import java.util.LinkedList;
3031
import java.util.List;
3132
import java.util.Map;
@@ -1189,6 +1190,76 @@ public void testBatchUpdateWithListOfObjectArraysPlusTypeInfo() throws Exception
11891190
BatchUpdateTestHelper.verifyBatchUpdateMocks(ctrlPreparedStatement, ctrlDatabaseMetaData);
11901191
}
11911192

1193+
public void testBatchUpdateWithCollectionOfObjects() throws Exception {
1194+
final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?";
1195+
final List<Integer> ids = new ArrayList<Integer>();
1196+
ids.add(Integer.valueOf(100));
1197+
ids.add(Integer.valueOf(200));
1198+
ids.add(Integer.valueOf(300));
1199+
final int[] rowsAffected1 = new int[] { 1, 2 };
1200+
final int[] rowsAffected2 = new int[] { 3 };
1201+
1202+
MockControl ctrlPreparedStatement = MockControl.createControl(PreparedStatement.class);
1203+
PreparedStatement mockPreparedStatement = (PreparedStatement) ctrlPreparedStatement.getMock();
1204+
mockPreparedStatement.getConnection();
1205+
ctrlPreparedStatement.setReturnValue(mockConnection);
1206+
mockPreparedStatement.setInt(1, ids.get(0));
1207+
ctrlPreparedStatement.setVoidCallable();
1208+
mockPreparedStatement.addBatch();
1209+
ctrlPreparedStatement.setVoidCallable();
1210+
mockPreparedStatement.setInt(1, ids.get(1));
1211+
ctrlPreparedStatement.setVoidCallable();
1212+
mockPreparedStatement.addBatch();
1213+
ctrlPreparedStatement.setVoidCallable();
1214+
mockPreparedStatement.setInt(1, ids.get(2));
1215+
ctrlPreparedStatement.setVoidCallable();
1216+
mockPreparedStatement.executeBatch();
1217+
ctrlPreparedStatement.setReturnValue(rowsAffected1);
1218+
mockPreparedStatement.addBatch();
1219+
ctrlPreparedStatement.setVoidCallable();
1220+
mockPreparedStatement.executeBatch();
1221+
ctrlPreparedStatement.setReturnValue(rowsAffected2);
1222+
if (debugEnabled) {
1223+
mockPreparedStatement.getWarnings();
1224+
ctrlPreparedStatement.setReturnValue(null);
1225+
}
1226+
mockPreparedStatement.close();
1227+
ctrlPreparedStatement.setVoidCallable();
1228+
1229+
MockControl ctrlDatabaseMetaData = MockControl.createControl(DatabaseMetaData.class);
1230+
DatabaseMetaData mockDatabaseMetaData = (DatabaseMetaData) ctrlDatabaseMetaData.getMock();
1231+
mockDatabaseMetaData.getDatabaseProductName();
1232+
ctrlDatabaseMetaData.setReturnValue("MySQL");
1233+
mockDatabaseMetaData.supportsBatchUpdates();
1234+
ctrlDatabaseMetaData.setReturnValue(true);
1235+
1236+
mockConnection.prepareStatement(sql);
1237+
ctrlConnection.setReturnValue(mockPreparedStatement);
1238+
mockConnection.getMetaData();
1239+
ctrlConnection.setReturnValue(mockDatabaseMetaData, 2);
1240+
1241+
ctrlPreparedStatement.replay();
1242+
ctrlDatabaseMetaData.replay();
1243+
replay();
1244+
1245+
ParameterizedPreparedStatementSetter setter = new ParameterizedPreparedStatementSetter<Integer>() {
1246+
public void setValues(PreparedStatement ps, Integer argument) throws SQLException {
1247+
ps.setInt(1, argument.intValue());
1248+
}
1249+
};
1250+
1251+
JdbcTemplate template = new JdbcTemplate(mockDataSource, false);
1252+
1253+
int[][] actualRowsAffected = template.batchUpdate(sql, ids, 2, setter);
1254+
assertTrue("executed 2 updates", actualRowsAffected[0].length == 2);
1255+
assertEquals(rowsAffected1[0], actualRowsAffected[0][0]);
1256+
assertEquals(rowsAffected1[1], actualRowsAffected[0][1]);
1257+
assertEquals(rowsAffected2[0], actualRowsAffected[1][0]);
1258+
1259+
ctrlPreparedStatement.verify();
1260+
ctrlDatabaseMetaData.verify();
1261+
}
1262+
11921263
public void testCouldntGetConnectionOrExceptionTranslator() throws SQLException {
11931264
SQLException sex = new SQLException("foo", "07xxx");
11941265

0 commit comments

Comments
 (0)