アイテム処理

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 に挿入できます。

XML Configuration
<job id="ioSampleJob">
    <step name="step1">
        <tasklet>
            <chunk reader="fooReader" processor="fooProcessor" writer="barWriter"
                   commit-interval="2"/>
        </tasklet>
    </step>
</job>
Java Configuration
@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 に構成できます。

XML Configuration
<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>
Java Configuration
@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 を提供します。

XML Configuration
<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>
Java Configuration
@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;
}

フォールトトレランス

チャンクがロールバックされると、読み取り中にキャッシュされたアイテムが再処理される場合があります。ステップがフォールトトレラントになるように構成されている場合(通常、スキップまたは再試行処理を使用して)、使用される ItemProcessor は、べき等の方法で実装する必要があります。通常、ItemProcessor の入力項目で変更を実行せず、結果のインスタンスのみを更新することで構成されます。