From 7cd8bfdef6f71952b9c6c19eea2ca9c6fed011a6 Mon Sep 17 00:00:00 2001
From: lucasward
* Convenience class for driving query input sources. Input Sources of this type
- * use a 'driving query' to return back a list of keys. Upon each call to read,
- * a new key is returned. To use the input source, inject a key generation
- * strategy.
- * A cursor based solution would simply open up a cursor over ID, NAME, and CREDIT,
+ * and move it from one to the next. This can cause issues on databases with
+ * pessimistic locking strategies. A 'driving query' approach would be to
+ * return only the ID of the customer, then use a separate DAO to retrieve the
+ * name and credit for each ID. This means that there will be a call to a
+ * separate DAO for each call to {@link ItemReader#read()}.
* Mutability: Because this base class cannot guarantee that the keys returned
@@ -45,7 +61,6 @@ import org.springframework.util.Assert;
*
*
* @author Lucas Ward
- * @since 1.0
*/
public class DrivingQueryItemReader implements ItemReader, InitializingBean,
ItemStream {
@@ -60,7 +75,7 @@ public class DrivingQueryItemReader implements ItemReader, InitializingBean,
private int lastCommitIndex = 0;
- private KeyGenerator keyGenerator;
+ private KeyCollector keyGenerator;
private boolean saveState = false;
@@ -165,18 +180,10 @@ public class DrivingQueryItemReader implements ItemReader, InitializingBean,
*
* @param keyGenerator
*/
- public void setKeyGenerator(KeyGenerator keyGenerator) {
+ public void setKeyCollector(KeyCollector keyGenerator) {
this.keyGenerator = keyGenerator;
}
- protected void transactionCommitted() {
- mark();
- }
-
- protected void transactionRolledBack() {
- reset();
- }
-
/**
* Mark is supported as long as this {@link ItemStream} is used in a
* single-threaded environment. The state backing the mark is a single
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/IbatisDrivingQueryItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/IbatisDrivingQueryItemReader.java
index f093543aa..718da123b 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/IbatisDrivingQueryItemReader.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/IbatisDrivingQueryItemReader.java
@@ -15,7 +15,7 @@
*/
package org.springframework.batch.item.database;
-import org.springframework.batch.item.database.support.IbatisKeyGenerator;
+import org.springframework.batch.item.database.support.IbatisKeyCollector;
import org.springframework.orm.ibatis.SqlMapClientTemplate;
import com.ibatis.sqlmap.client.SqlMapClient;
@@ -25,7 +25,7 @@ import com.ibatis.sqlmap.client.SqlMapClient;
* objects. An iBatis query id must be set to map and return each 'detail record'.
*
* @author Lucas Ward
- * @see IbatisKeyGenerator
+ * @see IbatisKeyCollector
*/
public class IbatisDrivingQueryItemReader extends DrivingQueryItemReader {
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/KeyCollector.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/KeyCollector.java
new file mode 100644
index 000000000..da29e636f
--- /dev/null
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/KeyCollector.java
@@ -0,0 +1,47 @@
+package org.springframework.batch.item.database;
+
+import java.util.List;
+
+import org.springframework.batch.item.ExecutionContext;
+
+/**
+ * Strategy interface used to collect keys in driving query input.
+ *
+ * @author Lucas Ward
+ * @see DrivingQueryItemReader
+ */
+public interface KeyCollector {
+
+ /**
+ * Retrieve the keys to be iterated over. If the ExecutionContext
+ * provided includes any state relevant to this collector (because it was
+ * stored as part of saveState()) then it should start from the point
+ * indicated by that state. In the case of a restart, (i.e. the ExecutionContext contains relevant state)
+ * this method should return only the keys that are remaining to be processed. For
+ * example, if the are 1,000 keys, and the 500th is processed before the batch job
+ * terminates unexpectedly, upon restart keys 501 through 1,000 should be returned.
+ *
+ * CREATE TABLE CUSTOMER (
+ * ID BIGINT IDENTITY PRIMARY KEY,
+ * NAME VARCHAR(45),
+ * CREDIT FLOAT
+ * );
+ *
+ *
+ *
+ * Extension of the standard {@link RowMapper} interface to provide for + * converting an object returned from a RowMapper to {@link ExecutionContext} + * and back again. This interface serves three purposes: + *
+ * + *- * Jdbc implementation of the {@link KeyGenerator} interface that works for + * Jdbc implementation of the {@link KeyCollector} interface that works for * composite keys. (i.e. keys represented by multiple columns) A sql query to be * used to return the keys and a {@link ExecutionContextRowMapper} to map each * row in the resultset to an Object must be set in order to work correctly. @@ -34,9 +34,9 @@ import org.springframework.util.StringUtils; * * @author Lucas Ward * @see DrivingQueryItemReader - * @since 1.0 + * @see ExecutionContextRowMapper */ -public class MultipleColumnJdbcKeyGenerator implements KeyGenerator { +public class MultipleColumnJdbcKeyCollector implements KeyCollector { private JdbcTemplate jdbcTemplate; @@ -46,7 +46,7 @@ public class MultipleColumnJdbcKeyGenerator implements KeyGenerator { private String restartSql; - public MultipleColumnJdbcKeyGenerator() { + public MultipleColumnJdbcKeyCollector() { } /** @@ -57,11 +57,10 @@ public class MultipleColumnJdbcKeyGenerator implements KeyGenerator { * @param keyMapper - RowMapper that maps each row of the ResultSet to an * object. */ - public MultipleColumnJdbcKeyGenerator(JdbcTemplate jdbcTemplate, String sql) { + public MultipleColumnJdbcKeyCollector(JdbcTemplate jdbcTemplate, String sql) { this(); Assert.notNull(jdbcTemplate, "The JdbcTemplate must not be null."); - Assert.hasText(sql, "The DrivingQuery must not be null or empty."); - Assert.notNull(keyMapper, "The key RowMapper must not be null."); + Assert.hasText(sql, "The sql statement must not be null or empty."); this.jdbcTemplate = jdbcTemplate; this.sql = sql; } @@ -90,6 +89,7 @@ public class MultipleColumnJdbcKeyGenerator implements KeyGenerator { */ public void saveState(Object key, ExecutionContext executionContext) { Assert.state(keyMapper != null, "Key mapper must not be null."); + Assert.notNull(key, "The key must not be null"); keyMapper.mapKeys(key, executionContext); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SingleColumnJdbcKeyGenerator.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SingleColumnJdbcKeyCollector.java similarity index 83% rename from spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SingleColumnJdbcKeyGenerator.java rename to spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SingleColumnJdbcKeyCollector.java index 035fe14df..8687131a1 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SingleColumnJdbcKeyGenerator.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SingleColumnJdbcKeyCollector.java @@ -19,7 +19,7 @@ import java.util.List; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ExecutionContextUserSupport; -import org.springframework.batch.item.database.KeyGenerator; +import org.springframework.batch.item.database.KeyCollector; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.SingleColumnRowMapper; @@ -28,7 +28,7 @@ import org.springframework.util.StringUtils; /** *
- * Jdbc {@link KeyGenerator} implementation that only works for a single column + * Jdbc {@link KeyCollector} implementation that only works for a single column * key. A sql query must be passed in which will be used to return a list of * keys. Each key will be mapped by a {@link RowMapper} that returns a mapped * key. By default, the {@link SingleColumnRowMapper} is used, and will convert @@ -36,7 +36,7 @@ import org.springframework.util.StringUtils; * only one column should be mapped to an object and returned as a key. If * multiple columns are returned as a key in this strategy, then restart will * not function properly. Instead a strategy that supports keys comprised of - * multiple columns should be used. + * multiple columns should be used.
* ** Restartability: Because the key is only one column, restart is made much more @@ -48,9 +48,9 @@ import org.springframework.util.StringUtils; *
* * @author Lucas Ward - * @since 1.0 + * @see SingleColumnRowMapper */ -public class SingleColumnJdbcKeyGenerator extends ExecutionContextUserSupport implements KeyGenerator { +public class SingleColumnJdbcKeyCollector extends ExecutionContextUserSupport implements KeyCollector { private static final String RESTART_KEY = "key"; @@ -62,8 +62,8 @@ public class SingleColumnJdbcKeyGenerator extends ExecutionContextUserSupport im private RowMapper keyMapper = new SingleColumnRowMapper(); - public SingleColumnJdbcKeyGenerator() { - setName(SingleColumnJdbcKeyGenerator.class.getSimpleName()); + public SingleColumnJdbcKeyCollector() { + setName(SingleColumnJdbcKeyCollector.class.getSimpleName()); } /** @@ -75,7 +75,7 @@ public class SingleColumnJdbcKeyGenerator extends ExecutionContextUserSupport im * @throws IllegalArgumentException if jdbcTemplate is null. * @throws IllegalArgumentException if sql string is empty or null. */ - public SingleColumnJdbcKeyGenerator(JdbcTemplate jdbcTemplate, String sql) { + public SingleColumnJdbcKeyCollector(JdbcTemplate jdbcTemplate, String sql) { this(); Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null."); Assert.hasText(sql, "The sql statement must not be null or empty."); @@ -89,12 +89,12 @@ public class SingleColumnJdbcKeyGenerator extends ExecutionContextUserSupport im */ public List retrieveKeys(ExecutionContext executionContext) { - Assert.notNull(executionContext, "The restart data must not be null."); + Assert.notNull(executionContext, "The ExecutionContext must not be null"); if (executionContext.containsKey(RESTART_KEY)) { - Assert.state(StringUtils.hasText(restartSql), "The RestartQuery must not be null or empty" + Assert.state(StringUtils.hasText(restartSql), "The restart sql query must not be null or empty" + " in order to restart."); - return jdbcTemplate.query(restartSql, new Object[] { executionContext.getString(RESTART_KEY) }, keyMapper); + return jdbcTemplate.query(restartSql, new Object[] { executionContext.get(RESTART_KEY) }, keyMapper); } else{ return jdbcTemplate.query(sql, keyMapper); @@ -104,12 +104,13 @@ public class SingleColumnJdbcKeyGenerator extends ExecutionContextUserSupport im /** * Get the restart data representing the last processed key. * - * @see KeyGenerator#saveState(Object) + * @see KeyCollector#saveState(Object) * @throws IllegalArgumentException if key is null. */ public void saveState(Object key, ExecutionContext executionContext) { Assert.notNull(key, "The key must not be null."); - executionContext.putString(RESTART_KEY, key.toString()); + Assert.notNull(executionContext, "The ExecutionContext must not be null"); + executionContext.put(RESTART_KEY, key); } /* diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/DrivingQueryItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/DrivingQueryItemReaderTests.java index 6daa708da..bc0da8a3d 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/DrivingQueryItemReaderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/DrivingQueryItemReaderTests.java @@ -32,7 +32,7 @@ public class DrivingQueryItemReaderTests extends TestCase { private DrivingQueryItemReader createItemReader() throws Exception{ DrivingQueryItemReader inputSource = new DrivingQueryItemReader(); - inputSource.setKeyGenerator(new MockKeyGenerator()); + inputSource.setKeyCollector(new MockKeyGenerator()); inputSource.setSaveState(true); return inputSource; @@ -157,7 +157,7 @@ public class DrivingQueryItemReaderTests extends TestCase { public void testRetriveZeroKeys(){ - itemReader.setKeyGenerator(new KeyGenerator(){ + itemReader.setKeyCollector(new KeyCollector(){ public List retrieveKeys(ExecutionContext executionContext) { return new ArrayList(); @@ -192,7 +192,7 @@ public class DrivingQueryItemReaderTests extends TestCase { return (ItemStream) source; } - private static class MockKeyGenerator implements KeyGenerator{ + private static class MockKeyGenerator implements KeyCollector{ static ExecutionContext streamContext; List keys; diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/IbatisItemReaderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/IbatisItemReaderIntegrationTests.java index d23cf5c59..e76564d53 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/IbatisItemReaderIntegrationTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/IbatisItemReaderIntegrationTests.java @@ -1,7 +1,7 @@ package org.springframework.batch.item.database; import org.springframework.batch.item.ItemReader; -import org.springframework.batch.item.database.support.IbatisKeyGenerator; +import org.springframework.batch.item.database.support.IbatisKeyCollector; import org.springframework.core.io.ClassPathResource; import org.springframework.orm.ibatis.SqlMapClientFactoryBean; @@ -23,13 +23,13 @@ public class IbatisItemReaderIntegrationTests extends AbstractDataSourceItemRead SqlMapClient sqlMapClient = (SqlMapClient) factory.getObject(); IbatisDrivingQueryItemReader inputSource = new IbatisDrivingQueryItemReader(); - IbatisKeyGenerator keyGenerator = new IbatisKeyGenerator(); + IbatisKeyCollector keyGenerator = new IbatisKeyCollector(); keyGenerator.setDrivingQueryId("getAllFooIds"); inputSource.setDetailsQueryId("getFooById"); keyGenerator.setRestartQueryId("getAllFooIdsRestart"); keyGenerator.setSqlMapClient(sqlMapClient); inputSource.setSqlMapClient(sqlMapClient); - inputSource.setKeyGenerator(keyGenerator); + inputSource.setKeyCollector(keyGenerator); inputSource.setSaveState(true); return inputSource; diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/MultipleColumnJdbcDrivingQueryItemReaderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/MultipleColumnJdbcDrivingQueryItemReaderIntegrationTests.java index aa6a8a567..9c930c89d 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/MultipleColumnJdbcDrivingQueryItemReaderIntegrationTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/MultipleColumnJdbcDrivingQueryItemReaderIntegrationTests.java @@ -16,7 +16,7 @@ package org.springframework.batch.item.database; import org.springframework.batch.item.ItemReader; -import org.springframework.batch.item.database.support.MultipleColumnJdbcKeyGenerator; +import org.springframework.batch.item.database.support.MultipleColumnJdbcKeyCollector; /** * @author Lucas Ward @@ -27,14 +27,14 @@ public class MultipleColumnJdbcDrivingQueryItemReaderIntegrationTests extends protected ItemReader createItemReader() throws Exception { - MultipleColumnJdbcKeyGenerator keyGenerator = - new MultipleColumnJdbcKeyGenerator(getJdbcTemplate(), + MultipleColumnJdbcKeyCollector keyGenerator = + new MultipleColumnJdbcKeyCollector(getJdbcTemplate(), "SELECT ID, VALUE from T_FOOS order by ID, VALUE"); keyGenerator.setRestartSql("SELECT ID, VALUE from T_FOOS where ID > ? and VALUE > ? order by ID"); DrivingQueryItemReader inputSource = new DrivingQueryItemReader(); inputSource.setSaveState(true); - inputSource.setKeyGenerator(keyGenerator); + inputSource.setKeyCollector(keyGenerator); FooItemReader fooItemReader = new FooItemReader(inputSource, getJdbcTemplate()); fooItemReader.setFooDao(new CompositeKeyFooDao(getJdbcTemplate())); return fooItemReader; diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/SingleColumnJdbcDrivingQueryItemReaderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/SingleColumnJdbcDrivingQueryItemReaderIntegrationTests.java index 3052e6846..58f4ac5bc 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/SingleColumnJdbcDrivingQueryItemReaderIntegrationTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/SingleColumnJdbcDrivingQueryItemReaderIntegrationTests.java @@ -1,7 +1,7 @@ package org.springframework.batch.item.database; import org.springframework.batch.item.ItemReader; -import org.springframework.batch.item.database.support.SingleColumnJdbcKeyGenerator; +import org.springframework.batch.item.database.support.SingleColumnJdbcKeyCollector; public class SingleColumnJdbcDrivingQueryItemReaderIntegrationTests extends AbstractJdbcItemReaderIntegrationTests { @@ -13,11 +13,11 @@ public class SingleColumnJdbcDrivingQueryItemReaderIntegrationTests extends Abst */ protected ItemReader createItemReader() throws Exception { - SingleColumnJdbcKeyGenerator keyStrategy = new SingleColumnJdbcKeyGenerator(getJdbcTemplate(), + SingleColumnJdbcKeyCollector keyStrategy = new SingleColumnJdbcKeyCollector(getJdbcTemplate(), "SELECT ID from T_FOOS order by ID"); keyStrategy.setRestartSql("SELECT ID from T_FOOS where ID > ? order by ID"); DrivingQueryItemReader inputSource = new DrivingQueryItemReader(); - inputSource.setKeyGenerator(keyStrategy); + inputSource.setKeyCollector(keyStrategy); inputSource.setSaveState(true); return new FooItemReader(inputSource, getJdbcTemplate()); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MultipleColumnJdbcKeyGeneratorIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MultipleColumnJdbcKeyGeneratorIntegrationTests.java index 2431082db..91621d0cc 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MultipleColumnJdbcKeyGeneratorIntegrationTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MultipleColumnJdbcKeyGeneratorIntegrationTests.java @@ -22,7 +22,7 @@ import org.springframework.test.AbstractTransactionalDataSourceSpringContextTest */ public class MultipleColumnJdbcKeyGeneratorIntegrationTests extends AbstractTransactionalDataSourceSpringContextTests { - MultipleColumnJdbcKeyGenerator keyStrategy; + MultipleColumnJdbcKeyCollector keyStrategy; ExecutionContext executionContext; @@ -33,7 +33,7 @@ public class MultipleColumnJdbcKeyGeneratorIntegrationTests extends AbstractTran protected void onSetUpBeforeTransaction() throws Exception { super.onSetUpBeforeTransaction(); - keyStrategy = new MultipleColumnJdbcKeyGenerator(getJdbcTemplate(), + keyStrategy = new MultipleColumnJdbcKeyCollector(getJdbcTemplate(), "SELECT ID, VALUE from T_FOOS order by ID"); keyStrategy.setRestartSql("SELECT ID, VALUE from T_FOOS where ID > ? and VALUE > ? order by ID"); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SingleColumnJdbcKeyGeneratorIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SingleColumnJdbcKeyGeneratorIntegrationTests.java index 34939aa61..5b5101145 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SingleColumnJdbcKeyGeneratorIntegrationTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SingleColumnJdbcKeyGeneratorIntegrationTests.java @@ -12,7 +12,7 @@ import org.springframework.test.AbstractTransactionalDataSourceSpringContextTest */ public class SingleColumnJdbcKeyGeneratorIntegrationTests extends AbstractTransactionalDataSourceSpringContextTests { - SingleColumnJdbcKeyGenerator keyStrategy; + SingleColumnJdbcKeyCollector keyStrategy; ExecutionContext executionContext; @@ -24,7 +24,7 @@ public class SingleColumnJdbcKeyGeneratorIntegrationTests extends AbstractTransa protected void onSetUpBeforeTransaction() throws Exception { super.onSetUpBeforeTransaction(); - keyStrategy = new SingleColumnJdbcKeyGenerator(getJdbcTemplate(), + keyStrategy = new SingleColumnJdbcKeyCollector(getJdbcTemplate(), "SELECT ID from T_FOOS order by ID"); keyStrategy.setRestartSql("SELECT ID from T_FOOS where ID > ? order by ID");