« CSRを楽に作る | トップページ | Spring + Thymeleafで検索 + ページング + ソートを同時にやる件 »

2020年4月 3日 (金)

Spring Batchで複数ファイルをItemReaderに使う

0. Spring Batchと格闘中です

現職では、自力でバッチアプリを書いていて、なぜか思いのほかJavaと格闘している。

利用シーンとしては、CSV提供されるマスタデータを複数のテーブルに格納する、というありがちな奴なんだけど、このCSVが一本ではなく複数ある。

ということなので、最初はタスクレットモデルで複数ファイルを一気に扱う方式で実装していたけれど、メモリ使用量とかの問題でチャンクモデルで実装しなおすことに。

と、ここで問題が。
普通のFlatFileItemReaderでは複数のファイルを同時に処理できない。

そこでMultiResourceItemReaderの出番です。

1. MultiResourceItemReader

Reads items from multiple resources sequentially (複数のリソースから項目を順番に読み取る) とあるとおり、処理を移譲されたItemReaderに順番にResourceを渡す。

ソース全体はhttps://github.com/f97one/MultiFileItemReaderBatchExを参照いただくとして、肝になりそうな部分を抜粋してみる。

ここでは、同じ書式の複数のファイルから二つのテーブルにデータを振り分ける処理を書いてみた。

ItemReaderまわりのBean
  @Bean
  fun directoryOrgReader(orgReader: FlatFileItemReader<OrgFile>): MultiResourceItemReader<OrgFile> {
      // Resourceを配列として返す
      // 渡したくないファイルがあるならここでフィルタしておく
      val dir = Path.of("C:", "work", "readtest")
      val dirFiles = Files.list(dir).collect(Collectors.toList())
      val csvResList = mutableListOf<Resource>()
      for (p in dirFiles) {
          csvResList.add(FileSystemResource(p))  // プロジェクト外のファイルなので FileSystemResource を使う
      }

      val reader = MultiResourceItemReader<OrgFile>()
      reader.setResources(csvResList.toTypedArray())
      reader.setDelegate(orgReader)  // ここに入っているBeanで実際に処理される
      return reader
  }

  @Bean
  fun orgReader(): FlatFileItemReader<OrgFile> {
      val mapper = DefaultLineMapper<OrgFile>()
      val delimitedLineTokenizer = DelimitedLineTokenizer()
      delimitedLineTokenizer.setNames("id", "subject", "itemType")
      mapper.setLineTokenizer(delimitedLineTokenizer)
      val fieldSetMapper = BeanWrapperFieldSetMapper<OrgFile>()
      fieldSetMapper.setTargetType(OrgFile::class.java)
      mapper.setFieldSetMapper(fieldSetMapper)

      // 本来のFlatFileItemReaderには処理するResourceを指定する必要があるが、
      // delegateしてくるMultiResourceItemReaderからResourceを供給されるので
      // ここには何も書かなくてよい
      return FlatFileItemReaderBuilder<OrgFile>()
              .name("orgFileReader")
              .linesToSkip(1)
              .encoding("windows-31j")
              .lineMapper(mapper)
              .build()
  }
実行ステップ定義
  @Bean
  fun step1(directoryOrgReader: MultiResourceItemReader<OrgFile>, orgItemWriter: MultiTblItemWriter): Step {
      // ここではItemProcessorは定義していないが、必要に応じて追加しよう
      return stepBuilderFactory.get("step1")
              .chunk<OrgFile, OrgFile>(10)
              .reader(directoryOrgReader)  // MultiResourceItemReaderのほうをreaderにする
              .writer(orgItemWriter)       // MultiTableItemWriterはItemWriterを実装したクラス
              .build()
  }

ItemWriterについてはよくあるやつなので、GitHubのほうを観てもらえば雰囲気はわかると思う。

え? 読み込ませるファイルに順序がある? そんなときはComapratorを実装すればいい。

  @Bean
  fun directoryOrgReader(orgReader: FlatFileItemReader<OrgFile>): MultiResourceItemReader<OrgFile> {
      // Resourceを配列として返す
      // 渡したくないファイルがあるならここでフィルタしておく
      val dir = Path.of("C:", "work", "readtest")
      val dirFiles = Files.list(dir).collect(Collectors.toList())
      val csvResList = mutableListOf<Resource>()
      for (p in dirFiles) {
          csvResList.add(FileSystemResource(p))  // プロジェクト外のファイルなので FileSystemResource を使う
      }

      val reader = MultiResourceItemReader<OrgFile>()
      reader.setResources(csvResList.toTypedArray())
      reader.setDelegate(orgReader)  // ここに入っているBeanで実際に処理される
      reader.setComparator { o1, o2 ->
          // o1, o2 とも nullable な Resource のインスタンスなので、
          // getFile() なり getFileName() なりして比較結果を
          // Int で返してやろう
          return 0
      }
      return reader
  }

« CSRを楽に作る | トップページ | Spring + Thymeleafで検索 + ページング + ソートを同時にやる件 »

コメント

こんにちは。

じっくり勉強させていただきます!

コメントを書く

(ウェブ上には掲載しません)

« CSRを楽に作る | トップページ | Spring + Thymeleafで検索 + ページング + ソートを同時にやる件 »

2023年12月
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            

最近のトラックバック

無料ブログはココログ