Added batchUpdate method taking a Collection, a batch size and a ParameterizedPreparedStatementSetter as arguments (SPR-6334)

This commit is contained in:
Thomas Risberg
2011-06-05 16:42:24 +00:00
parent cfb387383b
commit 0adcb2ad2e
5 changed files with 290 additions and 44 deletions

View File

@@ -16,6 +16,7 @@
package org.springframework.jdbc.core;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -1004,7 +1005,19 @@ public interface JdbcOperations {
* @return an array containing the numbers of rows affected by each update in the batch
*/
public int[] batchUpdate(String sql, List<Object[]> batchArgs, int[] argTypes);
/**
* Execute multiple batches using the supplied SQL statement with the collect of supplied arguments.
* The arguments' values will be set using the ParameterizedPreparedStatementSetter.
* Each batch should be of size indicated in 'batchSize'.
* @param sql the SQL statement to execute.
* @param batchArgs the List of Object arrays containing the batch of arguments for the query
* @param argTypes SQL types of the arguments
* (constants from <code>java.sql.Types</code>)
* @return an array containing for each batch another array containing the numbers of rows affected
* by each update in the batch
*/
public <T> int[][] batchUpdate(String sql, Collection<T> batchArgs, int batchSize, ParameterizedPreparedStatementSetter<T> pss);
//-------------------------------------------------------------------------
// Methods dealing with callable statements

View File

@@ -28,6 +28,7 @@ import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -929,7 +930,59 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
public int[] batchUpdate(String sql, List<Object[]> batchArgs, int[] argTypes) {
return BatchUpdateUtils.executeBatchUpdate(sql, batchArgs, argTypes, this);
}
/*
* (non-Javadoc)
* @see org.springframework.jdbc.core.JdbcOperations#batchUpdate(java.lang.String, java.util.Collection, int, org.springframework.jdbc.core.ParameterizedPreparedStatementSetter)
*
* Contribution by Nicolas Fabre
*/
public <T> int[][] batchUpdate(String sql, final Collection<T> batchArgs, final int batchSize, final ParameterizedPreparedStatementSetter<T> pss) {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL batch update [" + sql + "] with a batch size of " + batchSize);
}
return execute(sql, new PreparedStatementCallback<int[][]>() {
public int[][] doInPreparedStatement(PreparedStatement ps) throws SQLException {
List<int[]> rowsAffected = new ArrayList<int[]>();
try {
boolean batchSupported = true;
if (!JdbcUtils.supportsBatchUpdates(ps.getConnection())) {
batchSupported = false;
logger.warn("JDBC Driver does not support Batch updates; resorting to single statement execution");
}
int n = 0;
for (T obj : batchArgs) {
pss.setValues(ps, obj);
n++;
if (batchSupported) {
ps.addBatch();
if (n % batchSize == 0 || n == batchArgs.size()) {
if (logger.isDebugEnabled()) {
int batchIdx = (n % batchSize == 0) ? n / batchSize : (n / batchSize) + 1;
int items = n - ((n % batchSize == 0) ? n / batchSize - 1 : (n / batchSize)) * batchSize;
logger.debug("Sending SQL batch update #" + batchIdx + " with " + items + " items");
}
rowsAffected.add(ps.executeBatch());
}
}
else {
int i = ps.executeUpdate();
rowsAffected.add(new int[] {i});
}
}
int[][] result = new int[rowsAffected.size()][];
for (int i = 0; i < result.length; i++) {
result[i] = rowsAffected.get(i);
}
return result;
} finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}
});
}
//-------------------------------------------------------------------------
// Methods dealing with callable statements

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.jdbc.core;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* Parameterized callback interface used by the {@link JdbcTemplate} class for batch updates.
*
* <p>This interface sets values on a {@link java.sql.PreparedStatement} provided
* by the JdbcTemplate class, for each of a number of updates in a batch using the
* same SQL. Implementations are responsible for setting any necessary parameters.
* SQL with placeholders will already have been supplied.
*
* <p>Implementations <i>do not</i> need to concern themselves with SQLExceptions
* that may be thrown from operations they attempt. The JdbcTemplate class will
* catch and handle SQLExceptions appropriately.
*
* @author Nicolas Fabre
* @author Thomas Risberg
* @since 3.1
* @see JdbcTemplate#batchUpdate(String sql, Collection<T> objs, int batchSize, ParameterizedPreparedStatementSetter<T> pss)
*/
public interface ParameterizedPreparedStatementSetter<T> {
/**
* Set parameter values on the given PreparedStatement.
*
* @param ps the PreparedStatement to invoke setter methods on
* @param argument the object containing the values to be set
* @throws SQLException if a SQLException is encountered (i.e. there is no need to catch SQLException)
*/
void setValues(PreparedStatement ps, T argument) throws SQLException;
}

View File

@@ -26,6 +26,7 @@ import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -1189,6 +1190,76 @@ public class JdbcTemplateTests extends AbstractJdbcTests {
BatchUpdateTestHelper.verifyBatchUpdateMocks(ctrlPreparedStatement, ctrlDatabaseMetaData);
}
public void testBatchUpdateWithCollectionOfObjects() throws Exception {
final String sql = "UPDATE NOSUCHTABLE SET DATE_DISPATCHED = SYSDATE WHERE ID = ?";
final List<Integer> ids = new ArrayList<Integer>();
ids.add(Integer.valueOf(100));
ids.add(Integer.valueOf(200));
ids.add(Integer.valueOf(300));
final int[] rowsAffected1 = new int[] { 1, 2 };
final int[] rowsAffected2 = new int[] { 3 };
MockControl ctrlPreparedStatement = MockControl.createControl(PreparedStatement.class);
PreparedStatement mockPreparedStatement = (PreparedStatement) ctrlPreparedStatement.getMock();
mockPreparedStatement.getConnection();
ctrlPreparedStatement.setReturnValue(mockConnection);
mockPreparedStatement.setInt(1, ids.get(0));
ctrlPreparedStatement.setVoidCallable();
mockPreparedStatement.addBatch();
ctrlPreparedStatement.setVoidCallable();
mockPreparedStatement.setInt(1, ids.get(1));
ctrlPreparedStatement.setVoidCallable();
mockPreparedStatement.addBatch();
ctrlPreparedStatement.setVoidCallable();
mockPreparedStatement.setInt(1, ids.get(2));
ctrlPreparedStatement.setVoidCallable();
mockPreparedStatement.executeBatch();
ctrlPreparedStatement.setReturnValue(rowsAffected1);
mockPreparedStatement.addBatch();
ctrlPreparedStatement.setVoidCallable();
mockPreparedStatement.executeBatch();
ctrlPreparedStatement.setReturnValue(rowsAffected2);
if (debugEnabled) {
mockPreparedStatement.getWarnings();
ctrlPreparedStatement.setReturnValue(null);
}
mockPreparedStatement.close();
ctrlPreparedStatement.setVoidCallable();
MockControl ctrlDatabaseMetaData = MockControl.createControl(DatabaseMetaData.class);
DatabaseMetaData mockDatabaseMetaData = (DatabaseMetaData) ctrlDatabaseMetaData.getMock();
mockDatabaseMetaData.getDatabaseProductName();
ctrlDatabaseMetaData.setReturnValue("MySQL");
mockDatabaseMetaData.supportsBatchUpdates();
ctrlDatabaseMetaData.setReturnValue(true);
mockConnection.prepareStatement(sql);
ctrlConnection.setReturnValue(mockPreparedStatement);
mockConnection.getMetaData();
ctrlConnection.setReturnValue(mockDatabaseMetaData, 2);
ctrlPreparedStatement.replay();
ctrlDatabaseMetaData.replay();
replay();
ParameterizedPreparedStatementSetter setter = new ParameterizedPreparedStatementSetter<Integer>() {
public void setValues(PreparedStatement ps, Integer argument) throws SQLException {
ps.setInt(1, argument.intValue());
}
};
JdbcTemplate template = new JdbcTemplate(mockDataSource, false);
int[][] actualRowsAffected = template.batchUpdate(sql, ids, 2, setter);
assertTrue("executed 2 updates", actualRowsAffected[0].length == 2);
assertEquals(rowsAffected1[0], actualRowsAffected[0][0]);
assertEquals(rowsAffected1[1], actualRowsAffected[0][1]);
assertEquals(rowsAffected2[0], actualRowsAffected[1][0]);
ctrlPreparedStatement.verify();
ctrlDatabaseMetaData.verify();
}
public void testCouldntGetConnectionOrExceptionTranslator() throws SQLException {
SQLException sex = new SQLException("foo", "07xxx");