項目處理
ItemReader 和 ItemWriter 介面對於它們各自的任務都非常有用,但是如果您想在寫入之前插入業務邏輯該怎麼辦? 讀取和寫入的一個選項是使用組合模式:建立一個包含另一個 ItemWriter
的 ItemWriter
,或建立一個包含另一個 ItemReader
的 ItemReader
。 以下程式碼顯示了一個範例
public class CompositeItemWriter<T> implements ItemWriter<T> {
ItemWriter<T> itemWriter;
public CompositeItemWriter(ItemWriter<T> itemWriter) {
this.itemWriter = itemWriter;
}
public void write(Chunk<? extends T> items) throws Exception {
//Add business logic here
itemWriter.write(items);
}
public void setDelegate(ItemWriter<T> itemWriter){
this.itemWriter = itemWriter;
}
}
前面的類別包含另一個 ItemWriter
,在提供一些業務邏輯後,它會委派給該 ItemWriter
。 這種模式也很容易用於 ItemReader
,也許可以根據主要 ItemReader
提供的輸入來取得更多參考資料。 如果您需要自己控制對 write
的呼叫,這也很有用。 但是,如果您只想「轉換」傳入用於寫入的項目,然後再實際寫入,則無需自己 write
。 您可以修改項目即可。 對於這種情況,Spring Batch 提供了 ItemProcessor
介面,如下面的介面定義所示
public interface ItemProcessor<I, O> {
O process(I item) throws Exception;
}
ItemProcessor
很簡單。 給定一個物件,轉換它並傳回另一個物件。 提供的物件可能與否屬於同一類型。 重點是業務邏輯可以在處理過程中應用,並且完全由開發人員來建立該邏輯。 ItemProcessor
可以直接連接到步驟中。 例如,假設 ItemReader
提供 Foo
類型的類別,並且在寫出之前需要將其轉換為 Bar
類型。 以下範例顯示了一個執行轉換的 ItemProcessor
public class Foo {}
public class Bar {
public Bar(Foo foo) {}
}
public class FooProcessor implements ItemProcessor<Foo, Bar> {
public Bar process(Foo foo) throws Exception {
//Perform simple transformation, convert a Foo to a Bar
return new Bar(foo);
}
}
public class BarWriter implements ItemWriter<Bar> {
public void write(Chunk<? extends Bar> bars) throws Exception {
//write bars
}
}
在前面的範例中,有一個名為 Foo
的類別、一個名為 Bar
的類別和一個名為 FooProcessor
的類別,它們都遵循 ItemProcessor
介面。 轉換很簡單,但任何類型的轉換都可以在此處完成。 BarWriter
寫入 Bar
物件,如果提供了任何其他類型,則會拋出例外。 同樣地,如果提供了除 Foo
以外的任何內容,FooProcessor
也會拋出例外。 然後,可以將 FooProcessor
注入到 Step
中,如下面的範例所示
-
Java
-
XML
@Bean
public Job ioSampleJob(JobRepository jobRepository, Step step1) {
return new JobBuilder("ioSampleJob", jobRepository)
.start(step1)
.build();
}
@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("step1", jobRepository)
.<Foo, Bar>chunk(2, transactionManager)
.reader(fooReader())
.processor(fooProcessor())
.writer(barWriter())
.build();
}
<job id="ioSampleJob">
<step name="step1">
<tasklet>
<chunk reader="fooReader" processor="fooProcessor" writer="barWriter"
commit-interval="2"/>
</tasklet>
</step>
</job>
ItemProcessor
與 ItemReader
或 ItemWriter
之間的一個區別是,ItemProcessor
對於 Step
來說是可選的。
鏈接 ItemProcessors
執行單一轉換在許多情況下都很有用,但是如果您想將多個 ItemProcessor
實作「鏈接」在一起怎麼辦? 您可以使用前面提到的組合模式來做到這一點。 為了更新先前的單一轉換範例,Foo
被轉換為 Bar
,然後轉換為 Foobar
並寫出,如下面的範例所示
public class Foo {}
public class Bar {
public Bar(Foo foo) {}
}
public class Foobar {
public Foobar(Bar bar) {}
}
public class FooProcessor implements ItemProcessor<Foo, Bar> {
public Bar process(Foo foo) throws Exception {
//Perform simple transformation, convert a Foo to a Bar
return new Bar(foo);
}
}
public class BarProcessor implements ItemProcessor<Bar, Foobar> {
public Foobar process(Bar bar) throws Exception {
return new Foobar(bar);
}
}
public class FoobarWriter implements ItemWriter<Foobar>{
public void write(Chunk<? extends Foobar> items) throws Exception {
//write items
}
}
FooProcessor
和 BarProcessor
可以「鏈接」在一起以產生最終的 Foobar
,如下面的範例所示
CompositeItemProcessor<Foo,Foobar> compositeProcessor =
new CompositeItemProcessor<Foo,Foobar>();
List itemProcessors = new ArrayList();
itemProcessors.add(new FooProcessor());
itemProcessors.add(new BarProcessor());
compositeProcessor.setDelegates(itemProcessors);
與之前的範例一樣,您可以將組合處理器設定到 Step
中
-
Java
-
XML
@Bean
public Job ioSampleJob(JobRepository jobRepository, Step step1) {
return new JobBuilder("ioSampleJob", jobRepository)
.start(step1)
.build();
}
@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("step1", jobRepository)
.<Foo, Foobar>chunk(2, transactionManager)
.reader(fooReader())
.processor(compositeProcessor())
.writer(foobarWriter())
.build();
}
@Bean
public CompositeItemProcessor compositeProcessor() {
List<ItemProcessor> delegates = new ArrayList<>(2);
delegates.add(new FooProcessor());
delegates.add(new BarProcessor());
CompositeItemProcessor processor = new CompositeItemProcessor();
processor.setDelegates(delegates);
return processor;
}
<job id="ioSampleJob">
<step name="step1">
<tasklet>
<chunk reader="fooReader" processor="compositeItemProcessor" writer="foobarWriter"
commit-interval="2"/>
</tasklet>
</step>
</job>
<bean id="compositeItemProcessor"
class="org.springframework.batch.item.support.CompositeItemProcessor">
<property name="delegates">
<list>
<bean class="..FooProcessor" />
<bean class="..BarProcessor" />
</list>
</property>
</bean>
篩選記錄
項目處理器的一個典型用途是在將記錄傳遞到 ItemWriter
之前篩選掉記錄。 篩選是一種與跳過不同的動作。 跳過表示記錄無效,而篩選表示記錄不應寫入。
例如,考慮一個批次 Job,它讀取一個包含三種不同類型記錄的檔案:要插入的記錄、要更新的記錄和要刪除的記錄。 如果系統不支援記錄刪除,我們就不希望將任何可刪除的記錄發送到 ItemWriter
。 但是,由於這些記錄實際上並不是壞記錄,因此我們希望篩選掉它們,而不是跳過它們。 因此,ItemWriter
將僅接收可插入和可更新的記錄。
要篩選記錄,您可以從 ItemProcessor
傳回 null
。 框架偵測到結果為 null
,並避免將該項目添加到傳遞到 ItemWriter
的記錄列表中。 從 ItemProcessor
拋出的例外會導致跳過。
驗證輸入
ItemReaders 和 ItemWriters 章節討論了多種解析輸入的方法。 每個主要實作都會在「格式不正確」時拋出例外。 如果缺少資料範圍,FixedLengthTokenizer
會拋出例外。 同樣地,嘗試存取 RowMapper
或 FieldSetMapper
中不存在或格式與預期格式不同的索引會導致拋出例外。 所有這些類型的例外都是在 read
傳回之前拋出的。 但是,它們並未解決傳回的項目是否有效的問題。 例如,如果其中一個欄位是年齡,則它不能為負數。 它可能會正確解析,因為它存在並且是一個數字,但它不會導致例外。 由於已經有很多驗證框架,Spring Batch 並不打算再提供另一個。 相反,它提供了一個簡單的介面,稱為 Validator
,您可以透過任意數量的框架來實作它,如下面的介面定義所示
public interface Validator<T> {
void validate(T value) throws ValidationException;
}
合約是,如果物件無效,validate
方法會拋出例外,如果物件有效,則正常傳回。 Spring Batch 提供了 ValidatingItemProcessor
,如下面的 Bean 定義所示
-
Java
-
XML
@Bean
public ValidatingItemProcessor itemProcessor() {
ValidatingItemProcessor processor = new ValidatingItemProcessor();
processor.setValidator(validator());
return processor;
}
@Bean
public SpringValidator validator() {
SpringValidator validator = new SpringValidator();
validator.setValidator(new TradeValidator());
return validator;
}
<bean class="org.springframework.batch.item.validator.ValidatingItemProcessor">
<property name="validator" ref="validator" />
</bean>
<bean id="validator" class="org.springframework.batch.item.validator.SpringValidator">
<property name="validator">
<bean class="org.springframework.batch.samples.domain.trade.internal.validator.TradeValidator"/>
</property>
</bean>
您也可以使用 BeanValidatingItemProcessor
來驗證使用 Bean Validation API (JSR-303) 註解註解的項目。 例如,考慮以下 Person
類型
class Person {
@NotEmpty
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
您可以透過在應用程式上下文中宣告 BeanValidatingItemProcessor
Bean,並將其註冊為區塊導向步驟中的處理器來驗證項目
@Bean
public BeanValidatingItemProcessor<Person> beanValidatingItemProcessor() throws Exception {
BeanValidatingItemProcessor<Person> beanValidatingItemProcessor = new BeanValidatingItemProcessor<>();
beanValidatingItemProcessor.setFilter(true);
return beanValidatingItemProcessor;
}