diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/BatchSqlUpdateItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/BatchSqlUpdateItemWriter.java index be08ab223..fffb83b83 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/BatchSqlUpdateItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/BatchSqlUpdateItemWriter.java @@ -17,14 +17,16 @@ package org.springframework.batch.item.database; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Iterator; -import java.util.Set; +import java.util.List; import org.springframework.batch.item.ClearFailedException; import org.springframework.batch.item.ItemWriter; import org.springframework.batch.repeat.RepeatContext; import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.PreparedStatementCallback; import org.springframework.util.Assert; @@ -67,6 +69,17 @@ public class BatchSqlUpdateItemWriter extends AbstractTransactionalResourceItemW private String sql; + private boolean assertUpdates = true; + + /** + * Public setter for the flag that determines whether an assertion is made + * that all items cause at least one row to be updated. + * @param assertUpdates the flag to set. Defaults to true; + */ + public void setAssertUpdates(boolean assertUpdates) { + this.assertUpdates = assertUpdates; + } + /** * Public setter for the query string to execute on write. The parameters * should correspond to those known to the @@ -104,11 +117,15 @@ public class BatchSqlUpdateItemWriter extends AbstractTransactionalResourceItemW /** * Create and execute batch prepared statement. + * @throws EmptyResultDataAccessException if any of the items does not cause an update */ - protected void doFlush() { - final Set processed = getProcessed(); + protected void doFlush() throws EmptyResultDataAccessException { + + final List processed = new ArrayList(getProcessed()); + if (!processed.isEmpty()) { - jdbcTemplate.execute(sql, new PreparedStatementCallback() { + + int[] values = (int[]) jdbcTemplate.execute(sql, new PreparedStatementCallback() { public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException { for (Iterator iterator = processed.iterator(); iterator.hasNext();) { Object item = (Object) iterator.next(); @@ -118,7 +135,19 @@ public class BatchSqlUpdateItemWriter extends AbstractTransactionalResourceItemW return ps.executeBatch(); } }); + + if (assertUpdates) { + for (int i = 0; i < values.length; i++) { + int value = values[i]; + if (value == 0) { + throw new EmptyResultDataAccessException("Item " + i + " of " + values.length + + " did not update any rows: [" + processed.get(i) + "]", 1); + } + } + } + } + } protected String getResourceKey() { @@ -130,7 +159,7 @@ public class BatchSqlUpdateItemWriter extends AbstractTransactionalResourceItemW */ protected void doWrite(Object output) { } - + /** * No-op. */ diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/BatchSqlUpdateItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/BatchSqlUpdateItemWriterTests.java index c2d950fe9..9c6346b95 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/BatchSqlUpdateItemWriterTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/BatchSqlUpdateItemWriterTests.java @@ -29,6 +29,7 @@ import org.springframework.batch.repeat.RepeatContext; import org.springframework.batch.repeat.context.RepeatContextSupport; import org.springframework.batch.repeat.support.RepeatSynchronizationManager; import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.UncategorizedSQLException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCallback; @@ -133,9 +134,14 @@ public class BatchSqlUpdateItemWriterTests extends TestCase { /** * Test method for * {@link org.springframework.batch.item.database.BatchSqlUpdateItemWriter#flush()}. + * @throws SQLException */ - public void testFlush() { + public void testFlush() throws SQLException { assertTrue(TransactionSynchronizationManager.hasResource(writer.getResourceKey())); + ps.addBatch(); // there is one item in the buffer to start + control.setVoidCallable(); + control.expectAndReturn(ps.executeBatch(), new int[0]); + control.replay(); writer.flush(); assertFalse(TransactionSynchronizationManager.hasResource(writer.getResourceKey())); assertEquals(2, list.size()); @@ -149,6 +155,10 @@ public class BatchSqlUpdateItemWriterTests extends TestCase { */ public void testWriteAndFlush() throws Exception { assertTrue(TransactionSynchronizationManager.hasResource(writer.getResourceKey())); + ps.addBatch(); + control.setVoidCallable(2); + control.expectAndReturn(ps.executeBatch(), new int[] { 123 }); + control.replay(); writer.write("bar"); writer.flush(); assertFalse(TransactionSynchronizationManager.hasResource(writer.getResourceKey())); @@ -156,6 +166,31 @@ public class BatchSqlUpdateItemWriterTests extends TestCase { assertTrue(list.contains("SQL")); } + /** + * Test method for + * {@link org.springframework.batch.item.database.BatchSqlUpdateItemWriter#flush()}. + * @throws Exception + */ + public void testWriteAndFlushWithEmptyUpdate() throws Exception { + assertTrue(TransactionSynchronizationManager.hasResource(writer.getResourceKey())); + ps.addBatch(); + control.setVoidCallable(2); + control.expectAndReturn(ps.executeBatch(), new int[] { 0 }); + control.replay(); + writer.write("bar"); + try { + writer.flush(); + fail("Expected EmptyResultDataAccessException"); + } catch (EmptyResultDataAccessException e) { + // expected + String message = e.getMessage(); + assertTrue("Wrong message: "+message, message.indexOf("did not update")>=0); + } + assertFalse(TransactionSynchronizationManager.hasResource(writer.getResourceKey())); + assertEquals(3, list.size()); + assertTrue(list.contains("SQL")); + } + public void testWriteAndFlushWithFailure() throws Exception { final RuntimeException ex = new RuntimeException("bar"); writer.setItemPreparedStatementSetter(new ItemPreparedStatementSetter() {