アイテム処理
ItemReader および ItemWriter インターフェースはどちらも特定のタスクに非常に役立ちますが、作成する前にビジネスロジックを挿入する場合はどうでしょうか。読み取りと書き込みの両方の 1 つのオプションは、複合パターンを使用することです。別の 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(List<? extends T> items) throws Exception {
//Add business logic here
itemWriter.write(items);
}
public void setDelegate(ItemWriter<T> itemWriter){
this.itemWriter = itemWriter;
}
} 上記のクラスには、ビジネスロジックを提供した後に委譲する別の ItemWriter が含まれています。このパターンは、おそらくメイン ItemReader によって提供された入力に基づいてより多くの参照データを取得するために、ItemReader にも簡単に使用できます。write の呼び出しを自分で制御する必要がある場合にも役立ちます。ただし、実際に書き込まれる前に、書き込みのために渡された項目のみを「変換」したい場合は、write を自分で作成する必要はありません。アイテムを変更するだけです。このシナリオでは、Spring Batch は、次のインターフェース定義に示されているように、ItemProcessor インターフェースを提供します。
public interface ItemProcessor<I, O> {
O process(I item) throws Exception;
}ItemProcessor は簡単です。1 つのオブジェクトが与えられたら、それを変換して別のオブジェクトを返します。指定されたオブジェクトは、同じ型である場合とそうでない場合があります。ポイントは、ビジネスロジックをプロセス内で適用できることであり、そのロジックの作成は完全に開発者の責任です。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(List<? extends Bar> bars) throws Exception {
//write bars
}
} 前の例では、ItemProcessor インターフェースに準拠するクラス Foo、クラス Bar、クラス FooProcessor があります。変換は簡単ですが、ここでは任意の型の変換を実行できます。BarWriter は Bar オブジェクトを書き込み、他の型が提供されている場合は例外をスローします。同様に、Foo 以外が提供された場合、FooProcessor は例外をスローします。次に、次の例に示すように、FooProcessor を Step に挿入できます。
<job id="ioSampleJob">
<step name="step1">
<tasklet>
<chunk reader="fooReader" processor="fooProcessor" writer="barWriter"
commit-interval="2"/>
</tasklet>
</step>
</job>@Bean
public Job ioSampleJob() {
return this.jobBuilderFactory.get("ioSampleJob")
.start(step1())
.build();
}
@Bean
public Step step1() {
return this.stepBuilderFactory.get("step1")
.<Foo, Bar>chunk(2)
.reader(fooReader())
.processor(fooProcessor())
.writer(barWriter())
.build();
}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(List<? 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 に構成できます。
<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>@Bean
public Job ioSampleJob() {
return this.jobBuilderFactory.get("ioSampleJob")
.start(step1())
.build();
}
@Bean
public Step step1() {
return this.stepBuilderFactory.get("step1")
.<Foo, Foobar>chunk(2)
.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;
}レコードのフィルタリング
アイテムプロセッサーの一般的な使用箇所の 1 つは、ItemWriter に渡される前にレコードをフィルターで除外することです。フィルタリングは、スキップとは異なるアクションです。スキップはレコードが無効であることを示し、フィルタリングは単にレコードを書き込むべきではないことを示します。
例: 挿入するレコード、更新するレコード、削除するレコードの 3 種類のレコードを含むファイルを読み取るバッチジョブを検討します。システムでレコードの削除がサポートされていない場合、ItemWriter に「削除」レコードを送信したくないでしょう。ただし、これらのレコードは実際には不良レコードではないため、スキップするのではなく、除外することをお勧めします。その結果、ItemWriter は「挿入」および「更新」レコードのみを受信します。
レコードをフィルタリングするには、ItemProcessor から null を返すことができます。フレームワークは、結果が null であることを検出し、そのアイテムを ItemWriter に配信されたレコードのリストに追加することを避けます。通常、ItemProcessor からスローされた例外はスキップされます。
入力の検証
ItemReader および ItemWriter の章では、入力を解析するための複数のアプローチについて説明しました。主要な実装はそれぞれ、「整形式」でない場合に例外をスローします。データの範囲が欠落している場合、FixedLengthTokenizer は例外をスローします。同様に、存在しないか、予想とは異なる形式の RowMapper または FieldSetMapper の索引にアクセスしようとすると、例外がスローされます。これらの型の例外はすべて、read が戻る前にスローされます。ただし、return された項目が有効かどうかの課題には対応していません。例: フィールドの 1 つが年齢である場合、明らかに負の値にすることはできません。存在し、数値であるため、正しく解析される可能性がありますが、例外は発生しません。すでに多数の検証フレームワークが存在するため、Spring Batch はさらに別のフレームワークを提供しようとはしません。むしろ、次のインターフェース定義に示すように、Validator と呼ばれる、任意の数のフレームワークで実装できる単純なインターフェースを提供します。
public interface Validator<T> {
void validate(T value) throws ValidationException;
} 契約は、オブジェクトが無効な場合は validate メソッドが例外をスローし、有効な場合は正常に戻るというものです。Spring Batch は、次の Bean 定義に示すように、すぐに使用できる ValidatingItemProcessor を提供します。
<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.sample.domain.trade.internal.validator.TradeValidator"/>
</property>
</bean>@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;
}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;
}