ytnk531の日記

日々調べたことを書きます。

Spring BatchのReaderである程度複雑なことをする

やりたいこと

Readerである程度複雑なことをしたい。 ある程度複雑なこととは、例えば

  • 複数のテーブルからデータを読み込んでJavaで結合したい
  • テーブルからすべてのレコードを読み込んだ
  • DBのデータとRest APIのデータを結合したい

などです。

Readerでできること

Readerは、ItemReaderインターフェースの実装クラスです。 よく使いそうなItemReaderの実装クラスはSpring Batchで17種類提供されていますが、一回の読み込みで複数回クエリを発行するようなReaderやREST APIを叩くはものはありません。

このため、複雑なReaderは自分で実装しなくてはいけません。 ItemReaderインターフェースは、T read()を提供します。では、単純にread()にデータ取得処理を書けば良いのかというとそうでもありません。

Readerはreadメソッドが呼ばれるたびに新しいデータを一つ返します。 これは、一回のクエリ発行で一つのデータが返るような処理をすればいいということではありません。 read()はnullを返すまで実行されるので、そのようなクエリを発行したら無限ループしてしまうことになるからです。 ということは、簡単に言うと次のような実装が必要になります。

public class MyItemReader<T> implements ItemReader<T> {
    private List<T> data;
    
    private List <T> getData() {
        // なんか複雑な処理でデータをとってくる
        return data;
    }

    T read() {
        if (data == null) {
             data = getData();
        }

       if (data.size() < 1) {
             return null;
       }

        return data.remove(0); 
    }
}

Spring Batchはリスタート機能も備えているのですが、このままでは外部からReaderを再開してあげる方法がないので、このクラスはリスタートに対応していません。

AbstractItemCountingItemStreamItemReader

リスタートできるように、どこまで読み込んだとかの管理をうまくやってくれるクラスはないのかなー。ありました。

抽象クラスAbstractItemCountingItemStreamItemReaderは、Readerが返している要素の数と返せる要素の数(最大値)を管理してくれます。 Readerで読み込んだ要素の管理は自分でしないといけませんが… JdbcPagingItemReaderなどは、この抽象クラスを継承して実装しているので、参考になるでしょう。

public class MyItemReader extends AbstractItemCountingItemStreamItemReader<T> {
    private List<T> data;
    
    private List <T> getData() {
        // なんか複雑な処理でデータをとってくる
        return data;
    }

    @Override
    protected void doOpen() throws Exception {}

    @Override
    protected Employee doRead() throws Exception {
        if (results == null) {
              data = getData();
        }

        return results.get(getCurrentItemCount() - 1);
    }

    @Override
    protected void doClose() throws Exception {}
}

実はこのままだと、やっぱりdataを復帰させる方法がないので、やはりリスタートには対応していません。近いうちにリスタート対応版を追記します。