|
| 1 | +## 6.13.1 Custom ItemReader Example ## |
| 2 | + |
| 3 | +For the purpose of this example, a simple *ItemReader* implementation that reads from a provided list will be created. We'll start out by implementing the most basic contract of *ItemReader*, read: |
| 4 | + |
| 5 | + public class CustomItemReader<T> implements ItemReader<T>{ |
| 6 | + |
| 7 | + List<T> items; |
| 8 | + |
| 9 | + public CustomItemReader(List<T> items) { |
| 10 | + this.items = items; |
| 11 | + } |
| 12 | + |
| 13 | + public T read() throws Exception, UnexpectedInputException, |
| 14 | + NoWorkFoundException, ParseException { |
| 15 | + |
| 16 | + if (!items.isEmpty()) { |
| 17 | + return items.remove(0); |
| 18 | + } |
| 19 | + return null; |
| 20 | + } |
| 21 | + } |
| 22 | + |
| 23 | +This very simple class takes a list of items, and returns them one at a time, removing each from the list. When the list is empty, it returns null, thus satisfying the most basic requirements of an *ItemReader*, as illustrated below: |
| 24 | + |
| 25 | + List<String> items = new ArrayList<String>(); |
| 26 | + items.add("1"); |
| 27 | + items.add("2"); |
| 28 | + items.add("3"); |
| 29 | + |
| 30 | + ItemReader itemReader = new CustomItemReader<String>(items); |
| 31 | + assertEquals("1", itemReader.read()); |
| 32 | + assertEquals("2", itemReader.read()); |
| 33 | + assertEquals("3", itemReader.read()); |
| 34 | + assertNull(itemReader.read()); |
| 35 | + |
| 36 | + |
| 37 | +**Making the *ItemReader* Restartable** |
| 38 | + |
| 39 | +The final challenge now is to make the *ItemReader* restartable. Currently, if the power goes out, and processing begins again, the *ItemReader* must start at the beginning. This is actually valid in many scenarios, but it is sometimes preferable that a batch job starts where it left off. The key discriminant is often whether the reader is stateful or stateless. A stateless reader does not need to worry about restartability, but a stateful one has to try and reconstitute its last known state on restart. For this reason, we recommend that you keep custom readers stateless if possible, so you don't have to worry about restartability. |
| 40 | + |
| 41 | +If you do need to store state, then the ItemStream interface should be used: |
| 42 | + |
| 43 | + public class CustomItemReader<T> implements ItemReader<T>, ItemStream { |
| 44 | + |
| 45 | + List<T> items; |
| 46 | + int currentIndex = 0; |
| 47 | + private static final String CURRENT_INDEX = "current.index"; |
| 48 | + |
| 49 | + public CustomItemReader(List<T> items) { |
| 50 | + this.items = items; |
| 51 | + } |
| 52 | + |
| 53 | + public T read() throws Exception, UnexpectedInputException, |
| 54 | + ParseException { |
| 55 | + |
| 56 | + if (currentIndex < items.size()) { |
| 57 | + return items.get(currentIndex++); |
| 58 | + } |
| 59 | + |
| 60 | + return null; |
| 61 | + } |
| 62 | + |
| 63 | + public void open(ExecutionContext executionContext) throws ItemStreamException { |
| 64 | + if(executionContext.containsKey(CURRENT_INDEX)){ |
| 65 | + currentIndex = new Long(executionContext.getLong(CURRENT_INDEX)).intValue(); |
| 66 | + } |
| 67 | + else{ |
| 68 | + currentIndex = 0; |
| 69 | + } |
| 70 | + } |
| 71 | + |
| 72 | + public void update(ExecutionContext executionContext) throws ItemStreamException { |
| 73 | + executionContext.putLong(CURRENT_INDEX, new Long(currentIndex).longValue()); |
| 74 | + } |
| 75 | + |
| 76 | + public void close() throws ItemStreamException {} |
| 77 | + } |
| 78 | + |
| 79 | +On each call to the *ItemStream* update method, the current index of the *ItemReader* will be stored in the provided *ExecutionContext* with a key of 'current.index'. When the *ItemStream* open method is called, the *ExecutionContext* is checked to see if it contains an entry with that key. If the key is found, then the current index is moved to that location. This is a fairly trivial example, but it still meets the general contract: |
| 80 | + |
| 81 | + ExecutionContext executionContext = new ExecutionContext(); |
| 82 | + ((ItemStream)itemReader).open(executionContext); |
| 83 | + assertEquals("1", itemReader.read()); |
| 84 | + ((ItemStream)itemReader).update(executionContext); |
| 85 | + |
| 86 | + List<String> items = new ArrayList<String>(); |
| 87 | + items.add("1"); |
| 88 | + items.add("2"); |
| 89 | + items.add("3"); |
| 90 | + itemReader = new CustomItemReader<String>(items); |
| 91 | + |
| 92 | + ((ItemStream)itemReader).open(executionContext); |
| 93 | + assertEquals("2", itemReader.read()); |
| 94 | + |
| 95 | + |
| 96 | +Most ItemReaders have much more sophisticated restart logic. The *JdbcCursorItemReader*, for example, stores the row id of the last processed row in the Cursor. |
| 97 | + |
| 98 | +It is also worth noting that the key used within the *ExecutionContext* should not be trivial. That is because the same *ExecutionContext* is used for all *ItemStreams* within a *Step*. In most cases, simply prepending the key with the class name should be enough to guarantee uniqueness. However, in the rare cases where two of the same type of *ItemStream* are used in the same step (which can happen if two files are need for output) then a more unique name will be needed. For this reason, many of the Spring Batch *ItemReader* and ItemWriter implementations have a *setName()* property that allows this key name to be overridden. |
| 99 | + |
0 commit comments