Overview

本節では、ファイルの入出力を行う方法について説明する。

本機能は、チャンクモデルとタスクレットモデルとで同じ使い方になる。

扱えるファイルの種類

扱えるファイルの種類

TERASOLUNA Batch 5.xで扱えるファイルは以下のとおりである。
これは、Spring Batchにて扱えるものと同じである。

  • フラットファイル

  • XML

ここではフラットファイルの入出力を行うための方法について説明したのち、 XMLについてHow To Extendで説明する。

まず、TERASOLUNA Batch 5.xで扱えるフラットファイルの種類を示す。
フラットファイルにおける行をここではレコードと呼び、 ファイルの種類はレコードの形式にもとづく、とする。

レコード形式
形式 概要

可変長レコード

CSVやTSVに代表される区切り文字により各項目を区切ったレコード形式。各項目の長さが可変である。

固定長レコード

項目の長さ(バイト数)により各項目を区切ったレコード形式。各項目の長さが固定である。

単一文字列レコード

1レコードを1文字列として扱う形式。

扱えるファイルの構造

フラットファイルの基本構造は以下の2点から構成される。

  • レコード区分

  • レコードフォーマット

フラットファイルのフォーマットを構成する要素
要素 概要

レコード区分

レコードの種類、役割を指す。ヘッダ、データ、トレーラなどがある。
詳しくは後述する。

レコードフォーマット

ヘッダ、データ、トレーラレコードがそれぞれ何行あるのか、ヘッダ部~トレーラ部が複数回繰り返されるかなど、レコードの構造を指す。
シングルフォーマットとマルチフォーマットがある。詳しくは後述する。

TERASOLUNA Batch 5.xでは、各種レコード区分をもつシングルフォーマットおよびマルチフォーマットのフラットファイルを扱うことができる。

各種レコード区分およびレコードフォーマットについて説明する。

各種レコード区分の概要を以下に示す。

レコード区分ごとの特徴
レコード区分 概要

ヘッダレコード

ファイル(データ部)の先頭に付与されるレコードである。
フィールド名、ファイル共通の事項、データ部の集計情報などをもつ。

データレコード

ファイルの主な処理対象となるデータをもつレコードである。

トレーラ/フッタレコード

ファイル(データ部)の末尾に付与されるレコードである。
ファイル共通の事項、データ部の集計情報などをもつ。
シングルフォーマットの場合、フッタレコードと呼ばれることもある。

フッタ/エンドレコード

マルチフォーマットの場合にファイルの末尾に付与されるレコードである。
ファイル共通の事項、ファイル全体の集計情報などをもつ。

レコード区分を示すフィールドについて

ヘッダレコードやトレーラレコードをもつフラットファイルでは、レコード区分を示すフィールドをもたせる場合がある。
TERASOLUNA Batch 5.xでは特にマルチフォーマットファイルの処理において、レコード区分ごとに異なる処理を実施する場合などにレコード区分のフィールドを活用する。
レコード区分によって実行する処理を選択する場合の実装は、マルチフォーマットを参考にすること。

ファイルフォーマット関連の名称について

個々のシステムにおけるファイルフォーマットの定義によっては、 フッタレコードをエンドレコードと呼ぶなど等ガイドラインとは異なる名称が使われている場合がある。
適宜読み替えを行うこと。

シングルフォーマットおよびマルチフォーマットの概要を以下に示す。

シングルフォーマットおよびマルチフォーマットの概要
フォーマット 概要

シングルフォーマット

ヘッダn行 + データn行 + トレーラn行 の形式である。

マルチフォーマット

(ヘッダn行 + データn行 + トレーラn行)* n + フッタn行 の形式である。
シングルフォーマットを複数回繰り返した後にフッタレコードが付与されている形式である。

マルチフォーマットのレコード構成を図に表すと下記のようになる。

Multi format file layout
マルチフォーマットのレコード構成図

シングルフォーマット、マルチフォーマットフラットファイルの例を以下に示す。
なお、ファイルの内容説明に用いるコメントアウトを示す文字として//を使用する。

シングルフォーマット、レコード区分なしフラットファイル(CSV形式)の例
branchId,year,month,customerId,amount  // (1)
000001,2016,1,0000000001,100000000  // (2)
000001,2016,1,0000000002,200000000  // (2)
000001,2016,1,0000000003,300000000  // (2)
000001,3,600000000  // (3)
ファイルの内容の項目一覧
項番 説明

(1)

ヘッダレコードである。
データ部のフィールド名を示している。

(2)

データレコードである。

(3)

トレーラレコードである。
データ部の集計情報を保持している。

シングルフォーマット、レコード区分ありのフラットファイル(CSV形式)の例
// (1)
H,branchId,year,month,customerId,amount  // (2)
D,000001,2016,1,0000000001,100000000
D,000001,2016,1,0000000002,200000000
D,000001,2016,1,0000000003,300000000
T,000001,3,600000000
H,branchId,year,month,customerId,amount  // (2)
D,00002,2016,1,0000000004,400000000
D,00002,2016,1,0000000005,500000000
D,00002,2016,1,0000000006,600000000
T,00002,3,1500000000
H,branchId,year,month,customerId,amount  // (2)
D,00003,2016,1,0000000007,700000000
D,00003,2016,1,0000000008,800000000
D,00003,2016,1,0000000009,900000000
T,00003,3,2400000000
F,3,9,4500000000  // (3)
ファイルの内容の項目一覧
項番 説明

(1)

レコードの先頭にレコード区分を示すフィールドをもっている。
それぞれ下記のレコード区分を示す。
H:ヘッダレコード
D:データレコード
T:トレーラレコード
F:フッタレコード

(2)

branchIdが変わるごとにヘッダ、データ、トレーラを3回繰り返している。

(3)

フッタレコードである。
ファイル全体の集計情報を保持している。

データ部のフォーマットに関する前提

How to useでは、データ部のレイアウトは同一のフォーマットである事を前提して説明する。
これは、データ部のレコードはすべて同じ変換対象クラスへマッピングされることを意味する。

マルチフォーマットファイルの説明について
  • How to useでは、シングルフォーマットファイルについて説明する。

  • マルチフォーマットや上記の構造にフッタ部を含む構造をもつフラットファイルについては、How To Extendを参照すること

フラットファイルの入出力を行うコンポーネント

フラットファイルを扱うためのクラスを示す。

入力

フラットファイルの入力を行うために使用するクラスの関連は以下のとおりである。

Component relationship FlatFileItemReader class diagram
フラットファイルの入力を行うために使用するクラスの関連

各コンポーネントの呼び出し関係は以下のとおりである。

Component relationship FlatFileItemReader sequence diagram
各コンポーネントの呼び出し関係

各コンポーネントの詳細を以下に示す。

org.springframework.batch.item.file.FlatFileItemReader

フラットファイルを読み込みに使用するItemReaderの実装クラス。以下のコンポーネントを利用する。
簡単な処理の流れは以下のとおり。
1.BufferedReaderFactoryを使用してBufferedReaderを取得する。
2.取得したBufferedReaderを使用してフラットファイルから1レコードを読み込む。
3.LineMapperを使用して1レコードを対象Beanへマッピングする。

org.springframework.batch.item.file.BufferedReaderFactory

ファイルを読み込むためのBufferedReaderを生成する。

org.springframework.batch.item.file.LineMapper

1レコードを対象Beanへマッピングする。以下のコンポーネントを利用する。
簡単な処理の流れは以下のとおり。
1.LineTokenizerを使用して1レコードを各項目に分割する。
2.FieldSetMapperによって分割した項目をBeanのプロパティにマッピングする。

org.springframework.batch.item.file.transform.LineTokenizer

ファイルから取得した1レコードを各項目に分割する。
分割された各項目はFieldSetクラスに格納される。

org.springframework.batch.item.file.mapping.FieldSetMapper

分割した1レコード内の各項目を対象Beanのプロパティへマッピングする。

出力

フラットファイルの出力を行うために使用するクラスの関連は以下のとおりである。

Component relationship FlatFileItemWriter class diagram
フラットファイルの出力を行うために使用するクラスの関連

各コンポーネントの呼び出し関係は以下のとおりである。

Component relationship FlatFileItemWriter sequence diagram
各コンポーネントの呼び出し関係
org.springframework.batch.item.file.FlatFileItemWriter

フラットファイルへの書き出しに使用するItemWriterの実装クラス。以下のコンポーネントを利用する。 LineAggregator対象Beanを1レコードへマッピングする。

org.springframework.batch.item.file.transform.LineAggregator

対象Beanを1レコードへマッピングするために使う。 Beanのプロパティとレコード内の各項目とのマッピングはFieldExtractorで行う。

org.springframework.batch.item.file.transform.FieldExtractor

対象Beanのプロパティを1レコード内の各項目へマッピングする。

How to use

フラットファイルのレコード形式別に使い方を説明する。

その後、以下の項目について説明する。

可変長レコード

可変長レコードファイルを扱う場合の定義方法を説明する。

入力

下記の入力ファイルを読み込むための設定例を示す。

入力ファイル例
000001,2016,1,0000000001,1000000000
000002,2017,2,0000000002,2000000000
000003,2018,3,0000000003,3000000000
変換対象クラス
public class SalesPlanDetail {

    private String branchId;
    private int year;
    private int month;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}

上記のファイルを読む込むための設定は以下のとおり。

Bean定義例
<!-- (1) (2) (3) -->
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}"
      p:encoding="MS932"
      p:strict="true">
  <property name="lineMapper">  <!-- (4) -->
    <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
      <property name="lineTokenizer">  <!-- (5) -->
        <!-- (6) (7) (8) -->
        <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
              p:names="branchId,year,month,customerId,amount"
              p:delimiter=","
              p:quoteCharacter='"'/>
      </property>
      <property name="fieldSetMapper">  <!-- (9) -->
        <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
              p:targetType="org.terasoluna.batch.functionaltest.app.model.plan.SalesPlanDetail"/>
      </property>
    </bean>
  </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

入力ファイルを設定する。

なし

(2)

encoding

入力ファイルの文字コードを設定する。

JavaVMのデフォルト文字セット

(3)

strict

trueを設定すると、入力ファイルが存在しない(開けない)場合に例外が発生する。

true

(4)

lineMapper

org.springframework.batch.item.file.mapping.DefaultLineMapperを設定する。
DefaultLineMapperは、設定されたLineTokenizerFieldSetMapperを用いてレコードを変換対象クラスへ変換する基本的な動作を提供するLineMapperである。

なし

(5)

lineTokenizer

org.springframework.batch.item.file.transform.DelimitedLineTokenizerを設定する。
DelimitedLineTokenizerは、区切り文字を指定してレコードを分割するLineTokenizerの実装クラス。
CSV形式の一般的書式とされるRFC-4180の仕様に定義されている、エスケープされた改行、区切り文字、囲み文字の読み込みに対応している。

なし

(6)

names

1レコードの各項目に名前を付与する。
FieldSetMapperで使われるFieldSetで設定した名前を用いて各項目を取り出すことができるようになる。
レコードの先頭から各名前をカンマ区切りで設定する。
BeanWrapperFieldSetMapperを利用する場合は、必須設定である。

なし

(7)

delimiter

区切り文字を設定する

カンマ

(8)

quoteCharacter

囲み文字を設定する

なし

(9)

fieldSetMapper

文字列や数字など特別な変換処理が不要な場合は、org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapperを利用し、 プロパティtargetTypeに変換対象クラスを指定する。 これにより、(5)で設定した各項目の名前と一致するフィールドに値を自動的に設定したインスタンスを生成する。
変換処理が必要な場合は、org.springframework.batch.item.file.mapping.FieldSetMapperの実装クラスを設定する。

なし

FieldSetMapperの独自実装について

FieldSetMapperを独自に実装する場合については、How To Extendを参照すること。

TSV形式ファイルの入力方法

TSVファイルの読み込みを行う場合には、区切り文字にタブを設定することで実現可能である。

TSVファイル読み込み時:区切り文字設定例(定数による設定)
<property name="delimiter">
    <util:constant
            static-field="org.springframework.batch.item.file.transform.DelimitedLineTokenizer.DELIMITER_TAB"/>
</property>

または、以下のようにしてもよい。

TSVファイル読み込み時:区切り文字設定例(文字参照による設定)
<property name="delimiter" value="&#09;"/>

出力

下記の出力ファイルを書き出すための設定例を示す。

出力ファイル例
001,CustomerName001,CustomerAddress001,11111111111,001
002,CustomerName002,CustomerAddress002,11111111111,002
003,CustomerName003,CustomerAddress003,11111111111,003
変換対象クラス
public class Customer {

    private String customerId;
    private String customerName;
    private String customerAddress;
    private String customerTel;
    private String chargeBranchId;
    private Timestamp createDate;
    private Timestamp updateDate;

    // omitted getter/setter
}

上記のファイルを書き出すための設定は以下のとおり。

Bean定義例
<!-- Writer -->
<!-- (1) (2) (3) (4) (5) (6) (7) -->
<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step"
      p:resource="file:#{jobParameters[outputFile]}"
      p:encoding="MS932"
      p:lineSeparator="&#x0A;"
      p:appendAllowed="true"
      p:shouldDeleteIfEmpty="true"
      p:shouldDeleteIfExists="false"
      p:transactional="true">
  <property name="lineAggregator">  <!-- (8) -->
    <bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator"
          p:delimiter=",">  <!-- (9) -->
      <property name="fieldExtractor">  <!-- (10) -->
        <!-- (11) -->
        <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor"
              p:names="customerId,customerName,customerAddress,customerTel,chargeBranchId">
      </property>
    </bean>
  </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

出力ファイルを設定する。

なし

(2)

encoding

入力ファイルの文字コードを設定する。

JavaVMのデフォルト文字セット

(3)

lineSeparator

レコード区切り(改行コード)を設定する。

システムプロパティのline.separator

(4)

appendAllowed

trueの場合、既存のファイルに追記をする。

false

(5)

shouldDeleteIfEmpty

trueの場合、出力結果が空ファイルであれば削除する。

false

(6)

shouldDeleteIfExists

trueの場合、既にファイルが存在すれば削除する。
falseの場合、既にファイルが存在すれば例外をスローする。

true

(7)

transactional

トランザクション制御を行うかを設定する。詳細は、トランザクション制御を参照のこと。

true

(8)

lineAggregator

org.springframework.batch.item.file.transform.DelimitedLineAggregatorを設定する。
フィールドを囲み文字で囲む場合は、org.terasoluna.batch.item.file.transform.EnclosableDelimitedLineAggregatorを設定する。
EnclosableDelimitedLineAggregatorの使用方法は後述する。

なし

(9)

delimiter

区切り文字を設定する。

カンマ

(10)

fieldExtractor

文字列や数字など特別な変換処理が不要な場合は、org.springframework.batch.item.file.transform.BeanWrapperFieldExtractorが利用できる。
変換処理が必要な場合は、org.springframework.batch.item.file.transform.FieldExtractorの実装クラスを設定する。
FieldExtractorの実装例は固定長ファイルの出力にて、全角文字のフォーマットを例に説明しているためそちらを参照すること。

なし

(11)

names

1レコードの各項目に名前を付与する。 レコードの先頭から各名前をカンマ区切りで設定する。

なし

EnclosableDelimitedLineAggregatorの使用方法

フィールドを囲み文字で囲む場合は、TERASOLUNA Batch 5.xが提供するorg.terasoluna.batch.item.file.transform.EnclosableDelimitedLineAggregatorを使用する。
EnclosableDelimitedLineAggregatorの仕様は以下のとおり。

  • 囲み文字、区切り文字を任意に指定可能

    • デフォルトはCSV形式で一般的に使用される以下の値である

      • 囲み文字:"(ダブルクォート)

      • 区切り文字:,(カンマ)

  • フィールドに行頭復帰、改行、囲み文字、区切り文字が含まれている場合、囲み文字でフィールドを囲む

    • 囲み文字が含まれている場合、直前に囲み文字を付与しエスケープする

    • 設定によってすべてのフィールドを囲み文字で囲むことが可能

EnclosableDelimitedLineAggregatorの使用方法を以下に示す。

出力ファイル例
"001","CustomerName""001""","CustomerAddress,001","11111111111","001"
"002","CustomerName""002""","CustomerAddress,002","11111111111","002"
"003","CustomerName""003""","CustomerAddress,003","11111111111","003"
変換対象クラス
// 上記の例と同様
Bean定義例(lineAggregatorの設定のみ)
<property name="lineAggregator">  <!-- (1) -->
  <!-- (2) (3) (4) -->
  <bean class="org.terasoluna.batch.item.file.transform.EnclosableDelimitedLineAggregator"
        p:delimiter=","
        p:enclosure='"'
        p:allEnclosing="true">
      <property name="fieldExtractor">
        <!-- omitted settings -->
      </property>
  </bean>
</property>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

lineAggregator

org.terasoluna.batch.item.file.transform.EnclosableDelimitedLineAggregatorを設定する。

なし

(2)

delimiter

区切り文字を設定する。

カンマ

(3)

enclosure

囲み文字を設定する。
囲み文字がフィールドに含まれる場合は、エスケープ処理として囲み文字を2つ連結されたものへ置換される。

ダブルクォート

(4)

allEnclosing

trueの場合、すべてのフィールドが囲み文字で囲まれる。
falseの場合フィールド内に行頭復帰(CR)、改行(LF)、区切り文字、囲み文字が含まれるフィールドのみ囲み文字で囲まれる。

false

EnclosableDelimitedLineAggregatorの提供について

TERASOLUNA Batch 5.xでは、RFC-4180の仕様を満たすことを目的として拡張クラスorg.terasoluna.batch.item.file.transform.EnclosableDelimitedLineAggregatorを提供している。

Spring Batchが提供しているorg.springframework.batch.item.file.transform.DelimitedLineAggregatorはフィールドを囲み文字で囲む処理に対応しておらず、RFC-4180の仕様を満たすことができないためである。 Spring Batch/BATCH-2463 を参照のこと。

CSV形式のフォーマットについて、CSV形式の一般的書式とされるRFC-4180では下記のように定義されている。

  • フィールドに改行、囲み文字、区切り文字が含まれていない場合、各フィールドはダブルクォート(囲み文字)で囲んでも囲わなくてもよい

  • 改行(CRLF)、ダブルクォート(囲み文字)、カンマ(区切り文字)を含むフィールドは、ダブルクォートで囲むべきである

  • フィールドがダブルクォート(囲み文字)で囲まれている場合、フィールドの値に含まれるダブルクォートは、その直前に1つダブルクォートを付加して、エスケープしなければならない

TSV形式ファイルの出力方法

TSVファイルの出力を行う場合には、区切り文字にタブを設定することで実現可能である。

TSVファイル出力時の区切り文字設定例(定数による設定)
<property name="delimiter">
    <util:constant
            static-field="org.springframework.batch.item.file.transform.DelimitedLineTokenizer.DELIMITER_TAB"/>
</property>

または、以下のようにしてもよい。

TSVファイル出力時の区切り文字設定例(文字参照による設定)
<property name="delimiter" value="&#09;"/>

固定長レコード

固定長レコードファイルを扱う場合の定義方法を説明する。

入力

下記の入力ファイルを読み込むための設定例を示す。

TERASOLUNA Batch 5.xでは、レコードの区切りを改行で判断する形式とバイト数で判断する形式 に対応している。

入力ファイル例1(レコードの区切りは改行)
売上012016 1   00000011000000000
売上022017 2   00000022000000000
売上032018 3   00000033000000000
入力ファイル例2(レコードの区切りはバイト数、32バイトで1レコード)
売上012016 1   00000011000000000売上022017 2   00000022000000000売上032018 3   00000033000000000
入力ファイル仕様
項番 フィールド名 データ型 バイト数

(1)

branchId

String

6

(2)

year

int

4

(3)

month

int

2

(4)

customerId

String

10

(5)

amount

BigDecimal

10

変換対象クラス
public class SalesPlanDetail {

    private String branchId;
    private int year;
    private int month;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}

上記のファイルを読む込むための設定は以下のとおり。

Bean定義例
<!-- (1) (2) (3) -->
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}"
      p:encoding="MS932"
      p:strict="true">
    <property name="bufferedReaderFactory">  <!-- (4) -->
        <bean class="org.springframework.batch.item.file.DefaultBufferedReaderFactory"/>
    </property>
    <property name="lineMapper">  <!-- (5) -->
        <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
            <property name="lineTokenizer">  <!-- (6) -->
                <!-- (7) -->
                <!-- (8) -->
                <!-- (9) -->
                <bean class="org.terasoluna.batch.item.file.transform.FixedByteLengthLineTokenizer"
                      p:names="branchId,year,month,customerId,amount"
                      c:ranges="1-6, 7-10, 11-12, 13-22, 23-32"
                      c:charset="MS932" />
            </property>
            <property name="fieldSetMapper">  <!-- (10) -->
              <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
                    p:targetType="org.terasoluna.batch.functionaltest.app.model.plan.SalesPlanDetail"/>
            </property>
        </bean>
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

入力ファイルを設定する。

なし

(2)

encoding

入力ファイルの文字コードを設定する。

JavaVMのデフォルト文字セット

(3)

strict

trueを設定すると、入力ファイルが存在しない(開けない)場合に例外が発生する。

true

(4)

bufferedReaderFactory

レコードの区切りを改行で判断する場合は、デフォルト値であるorg.springframework.batch.item.file.DefaultBufferedReaderFactoryを使用する。 DefaultBufferedReaderFactoryが生成するBufferedReaderは改行までを1レコードとして取得する。

レコードの区切りをバイト数で判断する場合は、TERASOLUNA Batch 5.xが提供するorg.terasoluna.batch.item.file.FixedByteLengthBufferedReaderFactoryを設定する。 FixedByteLengthBufferedReaderFactoryが生成するBufferedReaderは指定したバイト数までを1レコードとして取得する。
FixedByteLengthBufferedReaderFactoryの詳しい仕様および使用方法は後述する。

DefaultBufferedReaderFactory

(5)

lineMapper

org.springframework.batch.item.file.mapping.DefaultLineMapperを設定する。

なし

(6)

lineTokenizer

TERASOLUNA Batch 5.xが提供するorg.terasoluna.batch.item.file.transform.FixedByteLengthLineTokenizerを設定する。

なし

(7)

names

1レコードの各項目に名前を付与する。
FieldSetMapperで使われるFieldSetで設定した名前を用いて各項目を取り出すことができるようになる。
レコードの先頭から各名前をカンマ区切りで設定する。
BeanWrapperFieldSetMapperを利用する場合は、必須設定である。

なし

(8)

ranges
(コンストラクタ引数)

区切り位置を設定する。レコードの先頭から区切り位置をカンマ区切りで設定する。
各区切り位置の単位はバイトであり、開始位置-終了位置形式で指定する。
区切り位置を設定した順番でレコードから指定された範囲を取得し、FieldSetに格納される。
(6)のnamesを指定した場合は区切り位置を設定した順番でnamesと対応付けてFieldSetに格納される。

なし

(9)

charset
(コンストラクタ引数)

(2)で指定した文字コードと同じ値を設定する。

なし

(10)

fieldSetMapper

文字列や数字など特別な変換処理が不要な場合は、org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapperを利用し、 プロパティtargetTypeに変換対象クラスを指定する。 これにより、(6)で設定した各項目の名前と一致するフィールドに値を自動的に設定したインスタンスを生成する。
変換処理が必要な場合は、org.springframework.batch.item.file.mapping.FieldSetMapperの実装クラスを設定する。

なし

FieldSetMapperの独自実装について

FieldSetMapperを独自に実装する場合については、How To Extendを参照すること。

FixedByteLengthBufferedReaderFactoryの使用方法

レコードの区切りをバイト数で判断するファイルを読み込む場合は、TERASOLUNA Batch 5.xが提供するorg.terasoluna.batch.item.file.FixedByteLengthBufferedReaderFactoryを使用する。

FixedByteLengthBufferedReaderFactoryを使用することで指定したバイト数までを1レコードとして取得することができる。
FixedByteLengthBufferedReaderFactoryの仕様は以下のとおり。

  • コンストラクタ引数としてレコードのバイト数を指定する

  • 指定されたバイト数を1レコードとしてファイルを読み込むFixedByteLengthBufferedReaderを生成する

FixedByteLengthBufferedReaderの使用は以下のとおり。

  • インスタンス生成時に指定されたバイト長を1レコードとしてファイルを読み込む

  • 改行コードが存在する場合、破棄せず1レコードのバイト長に含めて読み込みを行う

  • 読み込み時に使用するファイルエンコーディングはFlatFileItemWriterに設定したものがBufferedReader生成時に設定される

FixedByteLengthBufferedReaderFactoryの定義方法を以下に示す。

<property name="bufferedReaderFactory">
    <bean class="org.terasoluna.batch.item.file.FixedByteLengthBufferedReaderFactory"
        c:byteLength="32"/>  <!-- (1) -->

</property>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

byteLength
(コンストラクタ引数)

1レコードあたりのバイト数を設定する。

なし

固定長ファイルを扱う場合に使用するコンポーネント

固定長ファイルを扱う場合は、TERASOLUNA Batch 5.xが提供するコンポーネントを使うことを前提にしている。

FixedByteLengthBufferedReaderFactory

改行なし固定長ファイルから、指定した文字コードのバイト数で1レコードを読み込むBufferedReader生成クラス

FixedByteLengthLineTokenizer

マルチバイト文字列に対応したバイト数区切りのFixedLengthTokenizer拡張クラス

マルチバイト文字列を含むレコードを処理する場合

マルチバイト文字列を含むレコードを処理する場合は、FixedByteLengthLineTokenizerを必ず利用する。
Spring Batchが提供するFixedLengthTokenizerは、レコードをバイト数ではなく文字数で区切ってしまうため、期待どおりの項目切り出しが行われない恐れがある。 この点についてはJIRAの Spring Batch/BATCH-2540 で報告しているため、今後不要になる可能性がある。

FieldSetMapperの実装については、How To Extendを参照すること。

出力

下記の出力ファイルを書き出すための設定例を示す。

固定長ファイルを書き出すためには、Beanから取得した値をフィールドのバイト数にあわせてフォーマットを行う必要がある。
フォーマットの実行方法は全角文字が含まれるか否かによって下記のように異なる。

  • 全角文字が含まれない場合(半角文字のみであり文字のバイト数が一定)

    • FormatterLineAggregatorにてフォーマットを行う。

    • フォーマットは、String.formatメソッドで使用する書式で設定する。

  • 全角文字が含まれる場合(文字コードによって文字のバイト数が一定ではない)

    • FieldExtractorの実装クラスにてフォーマットを行う。

まず、出力ファイルに全角文字が含まれない場合の設定例を示し、その後全角文字が含まれる場合の設定例を示す。

出力ファイルに全角文字が含まれない場合の設定について下記に示す。

出力ファイル例
   0012016 10000000001  10000000
   0022017 20000000002  20000000
   0032018 30000000003  30000000
出力ファイル仕様
項番 フィールド名 データ型 バイト数

(1)

branchId

String

6

(2)

year

int

4

(3)

month

int

2

(4)

customerId

String

10

(5)

amount

BigDecimal

10

フィールドのバイト数に満たない部分は半角スペース埋めとしている。

変換対象クラス
public class SalesPlanDetail {

    private String branchId;
    private int year;
    private int month;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}

上記のファイルを書き出すための設定は以下のとおり。

Bean定義
<!-- Writer -->
<!-- (1) (2) (3) (4) (5) (6) (7) -->
<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step"
      p:resource="file:#{jobParameters[outputFile]}"
      p:encoding="MS932"
      p:lineSeparator="&#x0A;"
      p:appendAllowed="true"
      p:shouldDeleteIfEmpty="true"
      p:shouldDeleteIfExists="false"
      p:transactional="true">
    <property name="lineAggregator">  <!-- (8) -->
        <bean class="org.springframework.batch.item.file.transform.FormatterLineAggregator"
              p:format="%6s%4s%2s%10s%10s"/>  <!-- (9) -->
            <property name="fieldExtractor">  <!-- (10) -->
              <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor"
                    p:names="branchId,year,month,customerId,amount"/>  <!-- (11) -->
            </property>
        </bean>
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

出力ファイルを設定する。

なし

(2)

encoding

入力ファイルの文字コードを設定する。

JavaVMのデフォルト文字セット

(3)

lineSeparator

レコード区切り(改行コード)を設定する。
改行なしにする場合は、空文字を設定する。

システムプロパティのline.separator

(4)

appendAllowed

trueの場合、既存のファイルに追記をする。

false

(5)

shouldDeleteIfEmpty

trueの場合、出力結果が空ファイルであれば削除する。

false

(6)

shouldDeleteIfExists

trueの場合、既にファイルが存在すれば削除する。
falseの場合、既にファイルが存在すれば例外をスローする。

true

(7)

transactional

トランザクション制御を行うかを設定する。詳細は、トランザクション制御を参照

true

(8)

lineAggregator

org.springframework.batch.item.file.transform.FormatterLineAggregatorを設定する。

なし

(9)

format

String.formatメソッドで使用する書式で出力フォーマットを設定する。

なし

(10)

fieldExtractor

文字列や数字など特別な変換処理、全角文字のフォーマットが不要な場合は、org.springframework.batch.item.file.transform.BeanWrapperFieldExtractorが利用できる。

値の変換処理や全角文字をフォーマットする等の対応が必要な場合は、org.springframework.batch.item.file.transform.FieldExtractorの実装クラスを設定する。
全角文字をフォーマットする場合におけるFieldExtractorの実装例は後述する。

PassThroughFieldExtractor

(11)

names

1レコードの各項目に名前を付与する。 レコードの先頭から各フィールドの名前をカンマ区切りで設定する。

なし

PassThroughFieldExtractorとは

FormatterLineAggregatorがもつプロパティfieldExtractorのデフォルト値はorg.springframework.batch.item.file.transform.PassThroughFieldExtractorである。

PassThroughFieldExtractorは、元のアイテムに対して処理を行わずに返すクラスであり、FieldExtractorにて何も処理を行わない場合に使用する。

アイテムが配列またはコレクションの場合はそのまま返されるが、それ以外の場合は、単一要素の配列にラップされる。

全角文字が含まれるフィールドに対してフォーマットを行う際の設定例

全角文字に対するフォーマットを行う場合、文字コードにより1文字あたりのバイト数が異なるため、FormatterLineAggregatorではなく、FieldExtractorの実装クラスを使用する。

FieldExtractorの実装クラスは以下の要領で実装する。

  • FieldExtractorクラスを実装し、extractメソッドをオーバーライドする

  • extractメソッドは以下の要領で実装する

    • item(処理対象のBean)から値を取得し、適宜変換処理等を行う

    • Object型の配列に格納し返す

FieldExtractorの実装クラスで行う全角文字を含むフィールドのフォーマットは以下の要領で実装する。

  • 文字コードに対するバイト数を取得する

  • 取得したバイト数を元にパディング・トリム処理で整形する

以下に全角文字を含むフィールドをフォーマットする場合の設定例を示す。

出力ファイル例
   0012016 10000000001  10000000
  番号2017 2 売上高002  20000000
 番号32018 3   売上003  30000000

出力ファイルの使用は上記の例と同様。

Bean定義(lineAggregatorの設定のみ)
<property name="lineAggregator">  <!-- (1) -->
    <bean class="org.springframework.batch.item.file.transform.FormatterLineAggregator"
          p:format="%s%4s%2s%s%10s"/>  <!-- (2) -->
        <property name="fieldExtractor">  <!-- (3) -->
            <bean class="org.terasoluna.batch.functionaltest.ch05.fileaccess.plan.SalesPlanFixedLengthFieldExtractor"/>
        </property>
    </bean>
</property>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

lineAggregator

org.springframework.batch.item.file.transform.FormatterLineAggregatorを設定する。

なし

(2)

format

String.formatメソッドで使用する書式で出力フォーマットを設定する。
全角文字が含まれないフィールドに対してのみ桁数の指定をしている。

なし

(3)

fieldExtractor

FieldExtractorの実装クラスを設定する。
実装例は後述する。

PassThroughFieldExtractor

変換対象クラス
public class SalesPlanDetail {

    private String branchId;
    private int year;
    private int month;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}
全角文字をフォーマットするFieldExtractorの実装例
public class SalesPlanFixedLengthFieldExtractor implements FieldExtractor<SalesPlanDetail> {
    // (1)
    @Override
    public Object[] extract(SalesPlanDetail item) {
        Object[] values = new Object[5];  // (2)

        // (3)
        values[0] = fillUpSpace(item.getBranchId(), 6);  // (4)
        values[1] = item.getYear();
        values[2] = item.getMonth();
        values[3] = fillUpSpace(item.getCustomerId(), 10);  // (4)
        values[4] = item.getAmount();

        return values; // (7)
    }

    // It is a simple impl for example
    private String fillUpSpace(String val, int num) {
        String charsetName = "MS932";
        int len;
        try {
            len = val.getBytes(charsetName).length;  // (5)
        } catch (UnsupportedEncodingException e) {
            // omitted exception handling
        }

        String fillStr = "";
        for (int i = 0; i < (num - len); i++) { // (6)
            fillStr += " ";
        }

        return fillStr + val;
    }
}
設定内容の項目一覧
項番 説明

(1)

FieldExtractorクラスを実装し、extractメソッドをオーバーライドする。
FieldExtractorの型引数には変換対象クラスを設定する。

(2)

変換処理等を行ったデータを格納するためのObject型配列を定義する。

(3)

引数で受けたitem(処理対象のBean)から値を取得し、適宜変換処理を行い、Object型の配列に格納する。

(4)

全角文字が含まれるフィールドに対してフォーマット処理を行う。
フォーマット処理の詳細は(5)、(6)を参照すること。

(5)

文字コードに対するバイト数を取得する。

(6)

取得したバイト数を元にパディング・トリム処理で整形する。
実装例では指定されたバイト数まで文字列の前に空白を付与している。

(7)

処理結果を保持しているObject型の配列を返す。

単一文字列レコード

単一文字列レコードファイルを扱う場合の定義方法を説明する

入力

下記の入力ファイルを読み込むための設定例を示す。

入力ファイル例
Summary1:4,000,000,000
Summary2:5,000,000,000
Summary3:6,000,000,000

上記のファイルを読む込むための設定は以下のとおり。

Bean定義
<!-- (1) (2) (3) -->
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}"
      p:encoding="MS932"
      p:strict="true">
    <property name="lineMapper">  <!-- (4) -->
        <bean class="org.springframework.batch.item.file.mapping.PassThroughLineMapper"/>
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

入力ファイルを設定する。

なし

(2)

encoding

入力ファイルの文字コードを設定する。

JavaVMのデフォルト文字セット

(3)

strict

trueを設定すると、入力ファイルが存在しない(開けない)場合に例外が発生する。

true

(4)

lineMapper

org.springframework.batch.item.file.mapping.PassThroughLineMapperを設定する。
PassThroughLineMapperは渡されたレコードをそのまま文字列として返すLineMapperの実装クラスである。

なし

出力

下記の出力ファイルを書き出すための設定例を示す。

出力ファイル例
Summary1:4,000,000,000
Summary2:5,000,000,000
Summary3:6,000,000,000
Bean定義
<!-- Writer -->
<!-- (1) (2) (3) (4) (5) (6) (7) -->
<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step"
      p:resource="file:#{jobParameters[outputFile]}"
      p:encoding="MS932"
      p:lineSeparator="&#x0A;"
      p:appendAllowed="true"
      p:shouldDeleteIfEmpty="true"
      p:shouldDeleteIfExists="false"
      p:transactional="true">
    <property name="lineAggregator">  <!-- (8) -->
        <bean class="org.springframework.batch.item.file.transform.PassThroughLineAggregator"/>
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

出力ファイルを設定する。

なし

(2)

encoding

入力ファイルの文字コードを設定する。

JavaVMのデフォルト文字セット

(3)

lineSeparator

レコード区切り(改行コード)を設定する。

システムプロパティのline.separator

(4)

appendAllowed

trueの場合、既存のファイルに追記をする。

false

(5)

shouldDeleteIfEmpty

trueの場合、出力結果が空ファイルであれば削除する。

false

(6)

shouldDeleteIfExists

trueの場合、既にファイルが存在すれば削除する。
falseの場合、既にファイルが存在すれば例外をスローする。

true

(7)

transactional

トランザクション制御を行うかを設定する。詳細は、トランザクション制御を参照。

true

(8)

lineAggregator

org.springframework.batch.item.file.transform.PassThroughLineAggregatorを設定する。
PassThroughLineAggregatorはitem(処理対象のBean)をそのまま文字列へ変換(item.toString()を実行)するLineAggregatorの実装クラスである。

なし

ヘッダとフッタ

ヘッダ・フッタがある場合の入出力方法を説明する。

ここでは行数指定にてヘッダ・フッタを読み飛ばす方法を説明する。
ヘッダ・フッタのレコード数が可変であり行数指定ができない場合は、マルチフォーマットの入力を参考にPatternMatchingCompositeLineMapperを使用すること。

入力

ヘッダの読み飛ばし

ヘッダレコードを読み飛ばす方法には以下に示す2パターンがある。

  • FlatFileItemReaderlinesToSkipにファイルの先頭から読み飛ばす行数を設定

  • OSコマンドによる前処理でヘッダレコードを取り除く

入力ファイル例
sales_plan_detail_11
branchId,year,month,customerId,amount
000001,2016,1,0000000001,1000000000
000002,2017,2,0000000002,2000000000
000003,2018,3,0000000003,3000000000

先頭から2行がヘッダレコードである。

上記のファイルを読む込むための設定は以下のとおり。

linesToSkipによる読み飛ばし
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}"
      p:linesToSkip=value="2">  <!-- (1) -->
    <property name="lineMapper">
        <!-- omitted settings -->
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

linesToSkip

読み飛ばすヘッダ行数を設定する。

0

OSコマンドによる読み飛ばし処理
# Remove number of lines in header from the top of input file
tail -n +`expr 2 + 1` input.txt > output.txt

tailコマンドを利用し、入力ファイルinput.txtの3行目以降を取得し、output.txtに出力している。 tailコマンドのオプション-n +Kに指定する値はヘッダレコードの数+1となるため注意すること。

ヘッダレコードとフッタレコードを読み飛ばすOSコマンド

headコマンドとtailコマンドをうまく活用することでヘッダレコードとフッタレコードを行数指定をして読み飛ばすことが可能である。

ヘッダレコードの読み飛ばし方

tailコマンドをオプション-n +Kを付与して実行することで、処理対象のK行目以降を取得する。

フッタレコードの読み飛ばし方

headコマンドをオプション-n -Kを付与して実行することで、処理対象の末尾からK行目より前を取得する。

ヘッダレコードとフッタレコードをそれぞれ読み飛ばすシェルスクリプト例を下記に示す。

ヘッダ/フッタから指定行数を取り除くシェルスクリプトの例
#!/bin/bash

if [ $# -ne 4 ]; then
  echo "The number of arguments must be 4, given is $#." 1>&2
  exit 1
fi

# Input file.
input=$1

# Output file.
output=$2

# Number of lines in header.
header=$3

# Number of lines in footer.
footer=$4

# Remove number of lines in header from the top of input file
# and number of lines in footer from the end,
# and save to output file.
tail -n +`expr ${header} + 1` ${input} | head -n -${footer} > ${output}
引数
項番 説明

(1)

入力ファイル

(2)

出力ファイル

(3)

読み飛ばすヘッダの行数

(4)

読み飛ばすフッタの行数

ヘッダ情報の取り出し

ヘッダレコードを認識し、ヘッダレコードの情報を取り出す方法を示す。

ヘッダ情報の取り出しは以下の要領で実装する。

設定
  • org.springframework.batch.item.file.LineCallbackHandlerの実装クラスにヘッダに対する処理を実装する

    • LineCallbackHandler#handleLine()内で取得したヘッダ情報をstepExecutionContextに格納する

  • FlatFileItemReaderskippedLinesCallbackLineCallbackHandlerの実装クラスを設定する

  • FlatFileItemReaderlinesToSkipにヘッダの行数を指定する

ファイル読み込みおよびヘッダ情報の取り出し
  • linesToSkipの設定によってスキップされるヘッダレコード1行ごとにLineCallbackHandler#handleLine()が呼び出される

    • ヘッダ情報がstepExecutionContextに格納される

取得したヘッダ情報を利用する
  • ヘッダ情報をstepExecutionContextから取得してデータ部の処理で利用する

ヘッダレコードの情報を取り出す際の実装例を示す。

Bean定義
<bean id="lineCallbackHandler"
      class="org.terasoluna.batch.functionaltest.ch05.fileaccess.module.HoldHeaderLineCallbackHandler"/>

<!-- (1) (2) -->
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:linesToSkip="2"
      p:skippedLinesCallback-ref="lineCallbackHandler"
      p:resource="file:#{jobParameters[inputFile]}">
    <property name="lineMapper">
        <!-- omitted settings -->
    </property>
</bean>

<batch:job id="jobReadCsvSkipAndReferHeader" job-repository="jobRepository">
    <batch:step id="jobReadCsvSkipAndReferHeader.step01">
        <batch:tasklet transaction-manager="jobTransactionManager">
            <batch:chunk reader="reader"
                         processor="loggingHeaderRecordItemProcessor"
                         writer="writer" commit-interval="10"/>
            <batch:listeners>
                <batch:listener ref="lineCallbackHandler"/>  <!-- (3) -->
            </batch:listeners>
        </batch:tasklet>
    </batch:step>
</batch:job>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

linesToSkip

読み飛ばすヘッダ行数を設定する。

0

(2)

skippedLinesCallback

LineCallbackHandlerの実装クラスを設定する。
実装例は後述する。

なし

(2)

listener

StepExecutionListenerの実装クラスを設定する。
FlatFileItemReaderskippedLinesCallbackに指定するLineCallbackHandlerは自動でListenerとして登録されないため設定が必須となる。
詳しい理由は後述する。

なし

リスナー設定について

下記の2つの場合は自動でListenerとして登録されないため、ジョブ定義時にListenersにも定義を追加する必要がある。
(リスナの定義を追加しないと、StepExecutionListener#beforeStep()が実行されない)

  • FlatFileItemReaderskippedLinesCallbackに指定するLineCallbackHandlerStepExecutionListener

  • Taskletの実装クラスに実装するStepExecutionListener

    <batch:job id="jobReadCsvSkipAndReferHeader" job-repository="jobRepository">
        <batch:step id="jobReadCsvSkipAndReferHeader.step01">
            <batch:tasklet transaction-manager="jobTransactionManager">
                <batch:chunk reader="reader"
                             processor="loggingHeaderRecordItemProcessor"
                             writer="writer" commit-interval="10"/>
                <batch:listeners>
                    <batch:listener ref="loggingItemReaderListener"/>
                    <!-- mandatory -->
                    <batch:listener ref="lineCallbackHandler"/>
                </batch:listeners>
            </batch:tasklet>
        </batch:step>
    </batch:job>

LineCallbackHandlerは以下の要領で実装する。

  • StepExecutionListener#beforeStep()の実装

    • 下記のいずれかの方法でStepExecutionListener#beforeStep()を実装する

      • StepExecutionListenerクラスを実装し、beforeStepメソッドをオーバーライドする

      • beforeStepメソッドを実装し、@BeforeStepアノテーションを付与する

    • beforeStepメソッドにてStepExecutionを取得してクラスフィールドに保持する

  • LineCallbackHandler#handleLine()の実装

    • LineCallbackHandlerクラスを実装し、handleLineメソッドをオーバーライドする

      • handleLineメソッドはスキップする1行ごとに1回呼ばれる点に注意すること。

    • StepExecutionからstepExecutionContextを取得し、stepExecutionContextにヘッダ情報を格納する。

LineCallbackHandlerの実装例
@Component
public class HoldHeaderLineCallbackHandler implements LineCallbackHandler {  // (!)
    private StepExecution stepExecution;  // (2)

    @BeforeStep  // (3)
    public void beforeStep(StepExecution stepExecution) {
        this.stepExecution = stepExecution;  // (4)
    }

    @Override  // (5)
    public void handleLine(String line) {
        this.stepExecution.getExecutionContext().putString("header", line);  // (6)
    }
}
設定内容の項目一覧
項番 説明

(1)

LineCallbackHandlerクラスを実装し、handleLineメソッドをオーバーライドする。

(2)

StepExecutionを保持するためのフィールドを定義する。

(3)

beforeStepメソッドを実装し、@BeforeStepアノテーションを付与する。
シグネチャはvoid beforeStep(StepExecution stepExecution)とする。
StepExecutionListenerクラスを実装し、beforeStepメソッドをオーバーライドする方法でもよい。

(4)

StepExecutionを取得してクラスフィールドに保持する。

(5)

LineCallbackHandlerクラスを実装し、handleLineメソッドをオーバーライドする。

(6)

StepExecutionからstepExecutionContextを取得し、headerというキーを指定してstepExecutionContextにヘッダ情報を格納する。
ここでは簡単のため、スキップする2行のうち、最後の1行だけを格納している。

ヘッダ情報をstepExecutionContextから取得してデータ部の処理で利用する例を示す。
ItemProcessorにてヘッダ情報を利用する場合を例にあげて説明する。
他のコンポーネントでヘッダ情報を利用する際も同じ要領で実現することができる。

ヘッダ情報を利用する処理は以下の要領で実装する。

  • LineCallbackHandlerの実装例と同様にStepExecutionListener#beforeStep()を実装する

  • beforeStepメソッドにてStepExecutionを取得してクラスフィールドに保持する

  • StepExecutionからstepExecutionContextおよびヘッダ情報を取得して利用する

ヘッダ情報の利用例
@Component
public class LoggingHeaderRecordItemProcessor implements
        ItemProcessor<SalesPlanDetail, SalesPlanDetail> {
    private StepExecution stepExecution;  // (1)

    @BeforeStep  // (2)
    public void beforeStep(StepExecution stepExecution) {
        this.stepExecution = stepExecution;  // (3)
    }

    @Override
    public SalesPlanDetail process(SalesPlanDetail item) throws Exception {
        String headerData = this.stepExecution.getExecutionContext()
                .getString("header");  // (4)
        // omitted business logic
        return item;
    }
}
設定内容の項目一覧
項番 説明

(1)

StepExecutionを保持するためのフィールドを定義する。

(2)

beforeStepメソッドを実装し、@BeforeStepアノテーションを付与する。
シグネチャはvoid beforeStep(StepExecution stepExecution)とする。
StepExecutionListenerクラスを実装し、beforeStepメソッドをオーバーライドする方法でもよい。

(3)

StepExecutionを取得してクラスフィールドに保持する。

(4)

StepExecutionからstepExecutionContextを取得し、headerというキーを指定してstepExecutionContextからヘッダ情報を取得する。

Job/StepのExecutionContextの使用について

ヘッダ(フッタ)情報の取出しでは、読み込んだヘッダ情報をStepExecutionExecutionContextに格納しておき、使用する際にExecutionContextから取り出す方式をとる。

下記の例では1つのステップ内でヘッダ情報の取得および利用を行うためStepExecutionExecutionContextへヘッダ情報を格納している。 ヘッダ情報の取得および利用にてステップが分かれる場合はJobExecutionExecutionContextを利用すること。

Job/StepのExecutionContextに関する詳細は、Spring Batchのアーキテクチャを参照すること。

フッタの呼び飛ばし

Spring BatchおよびTERASOLUNA Batch 5.xでは、フッタレコードの読み飛ばし機能は提供していないため、OSコマンドで対応する。

入力ファイル例
000001,2016,1,0000000001,1000000000
000002,2017,2,0000000002,2000000000
000003,2018,3,0000000003,3000000000
number of items,3
total of amounts,6000000000

末尾から2行がフッタレコードである。

上記のファイルを読む込むための設定は以下のとおり。

OSコマンドによる読み飛ばし処理
# Remove number of lines in footer from the end of input file
head -n -2 input.txt > output.txt

headコマンドを利用し、入力ファイルinput.txtの末尾から2行目より前を取得し、output.txtに出力している。

フッタレコードを読み飛ばすことは、Spring Batchでは機能をもっていないため、JIRAの Spring Batch/BATCH-2539 にて報告している。
よって、今後OSコマンドによる読み飛ばしだけではなく、Spring Batchでも対応できるようになる可能性がある。

フッタ情報の取り出し

Spring BatchおよびTERASOLUNA Batch 5.xでは、フッタレコードの読み飛ばし機能、フッタ情報の取得機能は提供していない。

そのため、処理を下記ようにOSコマンドによる前処理と2つのステップのに分割することで対応する。

  • OSコマンドによってフッタレコードを分割する

  • 1つめのステップにてフッタレコードを読み込み、フッタ情報をExecutionContextに格納する

  • 2つめのステップにてExecutionContextからフッタ情報を取得し、利用する

フッタ情報を取り出しは以下の要領で実装する。

OSコマンドによるフッタレコードの分割
  • OSコマンドを利用して入力ファイルをフッタ部とフッタ部以外に分割する

1つめのステップでフッタレコードを読み込み、フッタ情報を取得する
  • フッタレコードを読み込みjobExecutionContextに格納する

    • フッタ情報の格納と利用にてステップが異なるため、jobExecutionContextに格納する。

    • jobExecutionContextを利用する方法は、JobとStepのスコープに関する違い以外は、ヘッダ情報の取り出しにて説明したstepExecutionContextと同様である。

2つめのステップにて取得したフッタ情報を利用する
  • フッタ情報をjobExecutionContextから取得してデータ部の処理で利用する

以下に示すファイルのフッタ情報を取り出して利用する場合を例にあげて説明する。

入力ファイル例
000001,2016,1,0000000001,1000000000
000002,2017,2,0000000002,2000000000
000003,2018,3,0000000003,3000000000
number of items,3
total of amounts,6000000000

末尾から2行がフッタレコードである。

OSコマンドによるフッタレコードの分割

上記のファイルをOSコマンドを利用してフッタ部とフッタ部以外に分割する設定は以下のとおり。

OSコマンドによる読み飛ばし処理
# Extract non-footer record from input file and save to output file.
head -n -2 input.txt > input_data.txt

# Extract footer record from input file and save to output file.
tail -n 2 input.txt > input_footer.txt

headコマンドを利用し、入力ファイルinput.txtのフッタ部以外をinput_data.txtへ、フッタ部をinput_footer.txtに出力している。

出力ファイル例は以下のとおり。

出力ファイル例(input_data.txt)
000001,2016,1,0000000001,1000000000
000002,2017,2,0000000002,2000000000
000003,2018,3,0000000003,3000000000
出力ファイル例(input_footer.txt)
number of items,3
total of amounts,6000000000
フッタ情報の取得、利用

OSコマンドにて分割したフッタレコードからフッタ情報を取得、利用する方法を説明する。

フッタレコードを読み込むステップを前処理として主処理とステップを分割している。
ステップの分割に関する詳細は、フロー制御を参照すること。

下記の例ではフッタ情報を取得し、jobExecutionContextへフッタ情報を格納するまでの例を示す。
jobExecutionContextからフッタ情報を取得し利用する方法はヘッダ情報の取り出しと同じ要領で実現可能である。

データレコードの情報を保持するクラス
public class SalesPlanDetail {

    private String branchId;
    private int year;
    private int month;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}
フッタレコードの情報を保持するクラス
public class SalesPlanDetailFooter implements Serializable {

    // omitted serialVersionUID

    private String name;
    private String value;

    // omitted getter/setter
}

下記の要領でBean定義を行う。

  • フッタレコードを読み込むItemReaderを定義する

  • データレコードを読み込むItemReaderを定義する

  • フッタレコードを取得するビジネスロジックを定義する

    • 下記の例ではTaskletの実装クラスで実現している

  • ジョブを定義する

    • フッタ情報を取得する前処理ステップとデータレコードを読み込み朱処理を行うステップを定義する

Bean定義
<!-- ItemReader for reading footer records -->
<!-- (1) -->
<bean id="footerReader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[footerInputFile]}">
    <property name="lineMapper">
        <!-- omitted other settings -->
    </property>
</bean>

<!-- ItemReader for reading data records -->
<!-- (2) -->
<bean id="dataReader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[dataInputFile]}">
    <property name="lineMapper">
        <!-- omitted other settings -->
    </property>
</bean>

<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step">
  <!-- omitted settings -->
</bean>

<!-- Tasklet for reading footer records -->
<bean id="readFooterTasklet"
      class="org.terasoluna.batch.functionaltest.ch05.fileaccess.module.ReadFooterTasklet"/>

<batch:job id="jobReadAndWriteCsvWithFooter" job-repository="jobRepository">
    <!-- (3) -->
    <batch:step id="jobReadAndWriteCsvWithFooter.step01"
            next="jobReadAndWriteCsvWithFooter.step02">
        <batch:tasklet ref="readFooterTasklet"
                       transaction-manager="jobTransactionManager"/>
    </batch:step>
    <!-- (4) -->
    <batch:step id="jobReadAndWriteCsvWithFooter.step02">
        <batch:tasklet transaction-manager="jobTransactionManager">
            <batch:chunk reader="dataReader"
                         writer="writer" commit-interval="10"/>
        </batch:tasklet>
    </batch:step>
    <batch:listeners>
        <batch:listener ref="readFooterTasklet"/> <!-- (5) -->
    </batch:listeners>
</batch:job>
設定内容の項目一覧
項番 項目 設定内容 必須 デフォルト値

(1)

footerReader

フッタレコードを保持するファイルを読み込むためのItemReaderを定義する。
フッタ情報を取得するステップで実行されるreadFooterTaskletにてインジェクトして使用する。

(2)

dataReader

データレコードを保持するファイルを読み込むためのItemReaderを定義する。

(3)

前処理ステップ

フッタ情報を取得するステップを定義する。
処理はreadFooterTaskletに実装している。実装例は後述する。

(4)

主処理ステップ

データ情報を取得するとともにフッタ情報を利用するステップを定義する。
readerにはdataReaderを使用する。
例ではフッタ情報をjobExecutionContextから取得し利用する処理(ItemProcessor等)は実装していない。
フッタ情報を取得し利用する方法はヘッダ情報の取り出しと同じ要領で実現可能である。

(5)

listeners

readFooterTaskletを設定する。
この設定を行わないとreadFooterTasklet内に実装するJobExecutionListener#beforeJob()が実行されない。
詳しい理由は、ヘッダ情報の取り出しを参照すること。

なし

フッタレコードを保持するファイルを読み込み、jobExecutionContextに格納する処理を行う処理の例を示す。

Taskletの実装クラスとして実現する際の要領は以下のとおり。

  • Bean定義したfooterReader@Injectアノテーションと@Namedアノテーションを使用し名前指定でインジェクトする。

  • 読み込んだフッタ情報をjobExecutionContextに格納する

フッタ情報の取得
public class ReadFooterTasklet implements Tasklet {
    // (1)
    @Inject
    @Named("footerReader")
    ItemStreamReader<SalesPlanDetailFooter> itemReader;

    private JobExecution jobExecution;

    @BeforeJob
    public void beforeJob(JobExecution jobExecution) {
        this.jobExecution = jobExecution;
    }

    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {
        ArrayList<SalesPlanDetailFooter> footers = new ArrayList<>();

        // (2)
        itemReader.open(chunkContext.getStepContext().getStepExecution()
                .getExecutionContext());

        SalesPlanDetailFooter footer;
        while ((footer = itemReader.read()) != null) {
            footers.add(footer);
        }

        // (3)
        jobExecution.getExecutionContext().put("footers", footers);

        return RepeatStatus.FINISHED;
    }
}
設定内容の項目一覧
項番 説明

(1)

Bean定義したfooterReader@Injectアノテーションと@Namedアノテーションを使用し名前指定でインジェクトする。

(2)

footerReaderを使用してフッタレコードを保持したファイルを読み込みフッタ情報を取得する。
Taskletの実装クラス内でBean定義したItemReaderを使用する方法はタスクレット指向ジョブの作成を参照すること。

(3)

JobExecutionからjobExecutionContextを取得し、footersというキーを指定してjobExecutionContextへフッタ情報を格納する。

出力

ヘッダ情報の出力

フラットファイルでヘッダ情報を出力する際は以下の要領で実装する。

  • org.springframework.batch.item.file.FlatFileHeaderCallbackの実装を行う

  • 実装したFlatFileHeaderCallbackFlatFileItemWriterheaderCallbackに設定する

    • headerCallbackを設定するとFlatFileItemWriterの出力処理で、最初にFlatFileHeaderCallback#writeHeader()が実行される

FlatFileHeaderCallbackは以下の要領で実装する。

  • FlatFileHeaderCallbackクラスを実装し、writeFooterメソッドをオーバーライドする

  • 引数で受けるWriterを用いてフッタ情報を出力する。

下記にFlatFileHeaderCallbackクラスの実装例を示す。

FlatFileHeaderCallbackの実装例
@Component
// (1)
public class WriteHeaderFlatFileFooterCallback implements FlatFileHeaderCallback {
    @Override
    public void writeHeader(Writer writer) throws IOException {
        // (2)
        writer.write("omitted");
    }
}
設定内容の項目一覧
項番 説明

(1)

FlatFileHeaderCallbackクラスを実装し、writeHeaderメソッドをオーバーライドする。

(2)

引数で受けるWriterを用いてフッタ情報を出力する。
FlatFileHeaderCallback#writeHeader()の実行直後にFlatFileItemWriterが出力する処理を実行する。
そのため、ヘッダ情報末尾の改行は出力不要である。 出力される改行は、Bean定義時にFlatFileItemWriterに指定したものである。

Bean定義
<!-- (1) (2) -->
<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step"
      p:headerCallback-ref="writeHeaderFlatFileFooterCallback"
      p:lineSeparator="&#x0A;"
      p:resource="file:#{jobParameters[outputFile]}">
    <property name="lineAggregator">
        <!-- omitted settings -->
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

headerCallback

FlatFileHeaderCallbackの実装クラスを設定する。

(2)

lineSeparator

レコード区切り(改行コード)を設定する。

システムプロパティのline.separator

FlatFileHeaderCallback実装時にヘッダ情報末尾の改行は出力不要

FlatFileItemWriter内でFlatFileHeaderCallback#writeHeader()の実行直後にBean定義時に指定した改行を出力する処理が実行されるため、ヘッダ情報末尾の改行は出力不要である。

フッタ情報の出力

フラットファイルでフッタ情報を出力する際は以下の要領で実装する。

  • org.springframework.batch.item.file.FlatFileFooterCallbackの実装を行う

  • 実装したFlatFileFooterCallbackFlatFileItemWriterfooterCallbackに設定する

    • footerCallbackを設定するとFlatFileItemWriterの出力処理で、最後にFlatFileFooterCallback#writeFooter()が実行される

フラットファイルでフッタ情報を出力する方法について説明する。

FlatFileFooterCallbackは以下の要領で実装する。

  • 引数で受けるWriterを用いてフッタ情報を出力する。

  • FlatFileFooterCallbackクラスを実装し、writeFooterメソッドをオーバーライドする

下記にJobのExecutionContextからフッタ情報を取得し、ファイルへ出力するFlatFileFooterCallbackクラスの実装例を示す。

フッタレコードの情報を保持するクラス
public class SalesPlanDetailFooter implements Serializable {

    // omitted serialVersionUID

    private String name;
    private String value;

    // omitted getter/setter
}
FlatFileFooterCallbackの実装例
@Component
public class WriteFooterFlatFileFooterCallback implements FlatFileFooterCallback {  // (1)
    private JobExecution jobExecution;

    @BeforeJob
    public void beforeJob(JobExecution jobExecution) {
        this.jobExecution = jobExecution;
    }

    @Override
    public void writeFooter(Writer writer) throws IOException {
        @SuppressWarnings("unchecked")
        ArrayList<SalesPlanDetailFooter> footers = (ArrayList<SalesPlanDetailFooter>) this.jobExecution.getExecutionContext().get("footers");  // (2)

        BufferedWriter bufferedWriter = new BufferedWriter(writer);  // (3)
        // (4)
        for (SalesPlanDetailFooter footer : footers) {
            bufferedWriter.write(footer.getName() +" is " + footer.getValue());
            bufferedWriter.newLine();
            bufferedWriter.flush();
        }
    }
}
設定内容の項目一覧
項番 説明

(1)

FlatFileFooterCallbackクラスを実装し、writeFooterメソッドをオーバーライドする。

(2)

JobのExecutionContextからfootersというkeyを指定してフッタ情報を取得する。
例ではArrayListで複数のフッタ情報を取得している。

(3)

例では改行の出力にBufferedWriter.newLine()を使用するため、引数で受けるWriterを引数としてBufferedWriterを生成する。

(4)

引数で受けるWriterを用いてフッタ情報を出力する。

Bean定義
<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step"
      p:resource="file:#{jobParameters[outputFile]}"
      p:footerCallback-ref="writeFooterFlatFileFooterCallback">  <!-- (1) -->
    <property name="lineAggregator">
        <!-- omitted settings -->
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

footerCallback

FlatFileFooterCallbackの実装クラスを設定する。

複数ファイル

複数ファイルを扱う場合の定義方法を説明する。

入力

同一レコード形式の複数ファイルを読み込む場合は、org.springframework.batch.item.file.MultiResourceItemReaderを利用する。
MultiResourceItemReaderは指定されたItemReaderを使用し正規表現で指定された複数のファイルを読み込むことができる。

MultiResourceItemReaderは以下の要領で定義する。

  • MultiResourceItemReaderのBeanを定義する

    • プロパティresourcesに読み込み対象のファイルを指定する

      • 正規表現で複数ファイルを指定する

    • プロパティdelegateにファイル読み込みに利用するItemReaderを指定する

下記に示す複数のファイルを読み込むMultiResourceItemReaderの定義例は以下のとおりである。

読み込み対象ファイル(ファイル名)
sales_plan_detail_01.csv
sales_plan_detail_02.csv
sales_plan_detail_03.csv
Bean定義
<!-- (1) (2) -->
<bean id="multiResourceReader"
      class="org.springframework.batch.item.file.MultiResourceItemReader"
      scope="step"
      p:resources="file:input/sales_plan_detail_*.csv"
      p:delegate-ref="reader"/>
</bean>

<!-- (3) -->
<bean id="reader"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="lineMapper">
      <!-- omitted settings -->
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

正規表現で複数の入力ファイルを設定する。

なし

(2)

delegate

実際にファイルを読み込み処理するItemReaderを設定する。

なし

(3)

実際にファイルを読み込み処理するItemReader

プロパティresourceは、MultiResourceItemReaderから自動的に設定されるためBean定義に設定は不要である。

MultiResourceItemReaderが使用するItemReaderにresourceの指定は不要である

MultiResourceItemReaderから委譲されるItemReaderresourceは、MultiResourceItemReaderから自動的に設定されるためBean定義に設定は不要である。

出力

複数ファイルを扱う場合の定義方法を説明する。

一定の件数ごとに異なるファイルへ出力する場合は、org.springframework.batch.item.file.MultiResourceItemWriterを利用する。

MultiResourceItemWriterは指定されたItemWriterを使用して指定した件数ごとに複数ファイルへ出力することができる。
出力対象のファイル名は重複しないように一意にする必要があるが、そのための仕組みとしてResourceSuffixCreatorが提供されている。
ResourceSuffixCreatorはファイル名が一意となるようなサフィックスを生成するクラスである。

たとえば、出力対象ファイルをoutputDir/customer_list_01.csv(01の部分は連番)というファイル名にしたい場合は下記のように設定する。

  • MultiResourceItemWriteroutputDir/customer_list_と設定する

  • サフィックス01.csv(01の部分は連番)を生成する処理をResourceSuffixCreatorに実装する

    • 連番はMultiResourceItemWriterから自動で増分されて渡される値を使用することができる

  • 実施に使用されるItemWriterにはoutputDir/customer_list_01.csvが設定される

MultiResourceItemWriterは以下の要領で定義する。ResourceSuffixCreatorの実装方法は後述する。

  • ResourceSuffixCreatorの実装クラスを定義する

  • MultiResourceItemWriterのBeanを定義する

    • プロパティresourcesに出力対象のファイルを指定する

      • ResourceSuffixCreatorの実装クラスで付与するサフィックスまでを設定

    • プロパティresourceSuffixCreatorにサフィックスを生成するResourceSuffixCreatorの実装クラスを指定する

    • プロパティdelegateにファイル読み込みに利用するItemWriterを指定する

    • プロパティitemCountLimitPerResourceに1ファイルあたりの出力件数を指定する

Bean定義
<!-- (1) (2) (3) (4) -->
<bean id="multiResourceItemWriter"
      class="org.springframework.batch.item.file.MultiResourceItemWriter"
      scope="step"
      p:resource="file:#{jobParameters[outputDir]}"
      p:resourceSuffixCreator-ref="customerListResourceSuffixCreator"
      p:delegate-ref="writer"
      p:itemCountLimitPerResource="4"/>
</bean>

<!-- (5) -->
<bean id="writer"
      class="org.springframework.batch.item.file.FlatFileItemWriter">
    <property name="lineAggregator">
        <!-- omitted settings -->
    </property>
</bean>

<bean id="customerListResourceSuffixCreator"
      class="org.terasoluna.batch.functionaltest.ch05.fileaccess.module.CustomerListResourceSuffixCreator"/>  <!-- (6) -->
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

出力対象ファイルのサフィックスを付与する前の状態を設定する。
ItemWriterには、MultiResourceItemWriterが自動でサフィックスを付与したものが設定される。

なし

(2)

resourceSuffixCreator

ResourceSuffixCreatorの実装クラスを設定する。
デフォルト値は"." + indexというサフィックスを生成するorg.springframework.batch.item.file.SimpleResourceSuffixCreatorである。

SimpleResourceSuffixCreator

(3)

delegate

実際にファイルを読み込み処理するItemWriterを設定する。

なし

(4)

itemCountLimitPerResource

1ファイルあたりの出力件数を設定する。

Integer.MAX_VALUE

(5)

ResourceSuffixCreatorの実装クラス

サフィックスを生成するResourceSuffixCreatorの実装クラスを定義する。
実装方法は後述する。

(6)

実際にファイルを読み込み処理するItemWriter

プロパティresourceは、MultiResourceItemWriterから自動的に設定されるためBean定義に設定は不要である。

MultiResourceItemWriterが使用するItemWriterにresourceの指定は不要である

MultiResourceItemWriterから委譲されるItemWriterresourceは、MultiResourceItemWriterから自動的に設定されるためBean定義に設定は不要である。

ResourceSuffixCreatorは以下の要領で実装する。

  • ResourceSuffixCreatorクラスを実装し、getSuffixメソッドをオーバーライドする

  • 引数で受けるindexを用いてサフィックスを生成して返り値として返す

    • indexは初期値1で始まり出力対象ファイルごとにインデントされるint型の値である

ResourceSuffixCreatorの実装例
// (1)
public class CustomerListResourceSuffixCreator implements ResourceSuffixCreator {
    @Override
    public String getSuffix(int index) {
        return String.format("%02d", index) + ".csv";  // (2)
    }
}
設定内容の項目一覧
項番 説明

(1)

ResourceSuffixCreatorクラスを実装し、getSuffixメソッドをオーバーライドする。

(2)

引数で受けるindexを用いてサフィックスを生成して返り値として返す。 indexは初期値1で始まり出力対象ファイルごとにインデントされるint型の値である。

コントロールブレイク

コントロールブレイクの実現方法について説明する。

コントロールブレイクとは

コントロールブレイク処理(またはキーブレイク処理)とは、ソート済みのレコードを順次読み込み、 レコード内にある特定の項目(キー項目)が同じレコードを1つのグループとして処理する手法のことを指す。
主にデータを集計するときに用いられ、 キー項目が同じ値の間は集計を続け、キー項目が異なる値になる際に集計値を出力する、 というアルゴリズムになる。

コントロールブレイク処理をするためには、グループの変わり目を判定するために、レコードを先読みする必要がる。 org.springframework.batch.item.support.SingleItemPeekableItemReaderを使うことで先読みを実現できる。
また、コントロールブレイクはタスクレットモデルでのみ処理可能とする。 これは、チャンクが前提とする「1行で定義するデータ構造をN行処理する」や「一定件数ごとのトランザクション境界」といった点が、 コントロールブレイクの「グループの変わり目で処理をする」という点と合わないためである。

コントロールブレイク処理の実行タイミングと比較条件を以下に示す。

  • 対象レコード処理前にコントロールブレイク実施

    • 前回読み取ったレコードを保持し、前回レコードと現在読み込んだレコードとの比較

  • 対象レコード処理後にコントロールブレイク実施

    • SingleItemPeekableItemReaderにより次のレコードを先読みし、次レコードと現在読み込んだレコードとの比較

下記にの入力データから処理結果を出力するコントロールブレイクの実装例を示す。

入力データ
01,2016,10,1000
01,2016,11,1500
01,2016,12,1300
02,2016,12,900
02,2016,12,1200
処理結果
Header Branch Id : 01,,,
01,2016,10,1000
01,2016,11,1500
01,2016,12,1300
Summary Branch Id : 01,,,3800
Header Branch Id : 02,,,
02,2016,12,900
02,2016,12,1200
Summary Branch Id : 02,,,2100
コントロールブレイクの実装例
@Component
public class ControlBreakTasklet implements Tasklet {

    @Inject
    SingleItemPeekableItemReader<SalesPerformanceDetail> reader; // (1)

    @Inject
    ItemStreamWriter<SalesPerformanceDetail> writer;

    @Override
    public RepeatStatus execute(StepContribution contribution,
            ChunkContext chunkContext) throws Exception {

        // omitted.

        SalesPerformanceDetail previousData = null;   // (2)
        BigDecimal summary = new BigDecimal(0);  //(3)

        List<SalesPerformanceDetail> items = new ArrayList<>();   // (4)

        try {
            reader.open(executionContext);
            writer.open(executionContext);

            while (reader.peek() != null) {   // (5)
                SalesPerformanceDetail data = reader.read(); // (6)

                // (7)
                if (isBreakByBranchId(previousData, data)) {
                    SalesPerformanceDetail beforeBreakData =
                            new SalesPerformanceDetail();
                    beforeBreakData.setBranchId("Header Branch Id : "
                              + currentData.getBranchId());
                    items.add(beforeBreakData);
                }

                // omitted.
                items.add(data);  // (8)

                SalesPerformanceDetail nextData = reader.peek();  // (9)
                summary = summary.add(data.getAmount());

                // (10)
                SalesPerformanceDetail afterBreakData = null;
                if (isBreakByBranchId(nextData, data)) {
                    afterBreakData = new SalesPerformanceDetail();
                    afterBreakData.setBranchId("Summary Branch Id : "
                            + currentData.getBranchId());
                    afterBreakData.setAmount(summary);
                    items.add(afterBreakData);
                    summary = new BigDecimal(0);
                    writer.write(items);  // (11)
                    items.clear();
                }
                previousData = data;  // (12)
            }
        } finally {
            try {
                reader.close();
            } catch (ItemStreamException e) {
            }
            try {
                writer.close();
            } catch (ItemStreamException e) {
            }
        }
        return RepeatStatus.FINISHED;
    }
    // (13)
    private boolean isBreakByBranchId(SalesPerformanceDetail o1,
            SalesPerformanceDetail o2) {
        return (o1 == null || !o1.getBranchId().equals(o2.getBranchId()));
    }
}
設定内容の項目一覧
項番 説明

(1)

SingleItemPeekableItemReaderをInjectする。

(2)

前回読み取ったレコードを保持する変数を定義する。

(3)

グループごとの集計値を格納する変数を定義する。

(4)

コントロールブレイクの処理結果を含めたグループ単位のレコードを格納する変数を定義する。

(5)

入力データが無くなるまで処理を繰り返す。

(6)

処理対象のレコードを読み込む。

(7)

対象レコード処理前にコントロールブレイクを実施する。
ここではグループの先頭であれば見出しを設定して、(4)で定義した変数に格納する。

(8)

対象レコードへの処理結果を(4)で定義した変数に格納する。

(9)

次のレコードを先読みする。

(10)

対象レコード処理後にコントロールブレイクを実施する。 ここではグループの末尾であれば集計データをトレーラに設定して、(4)で定義した変数に格納する。

(11)

グループ単位で処理結果を出力する。

(12)

処理レコードを(2)で定義した変数に格納する。

(13)

キー項目が切り替わったか判定する。

Bean定義
<!-- (1) -->
<bean id="reader"
      class="org.springframework.batch.item.support.SingleItemPeekableItemReader"
      p:delegate-ref="delegateReader" />  <!-- (2) -->

<!-- (3) -->
<bean id="delegateReader"
      class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}">
    <property name="lineMapper">
        <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
            <property name="lineTokenizer">
                <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
                      p:names="branchId,year,month,customerId,amount"/>
            </property>
            <property name="fieldSetMapper">
                <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
                      p:targetType="org.terasoluna.batch.functionaltest.app.model.performance.SalesPerformanceDetail"/>
            </property>
        </bean>
    </property>
</bean>
設定内容の項目一覧
項番 説明

(1)

SingleItemPeekableItemReaderをBean定義する。TaskletへのInject対象。

(2)

delegateプロパティに実際にファイルを読み込むItemReaderのBeanを指定する。

(3)

実際にファイルを読み込むItemReaderのBeanを定義する。

How To Extend

ここでは、以下のケースについて説明する。

FieldSetMapperの実装

FieldSetMapperを自作で実装する方法について説明する。

FieldSetMapperの実装クラスは下記の要領で実装する。

  • FieldSetMapperクラスを実装し、mapFieldSetメソッドをオーバーライドする

  • 引数で受けたFieldSetから値を取得し、適宜変換処理を行い、変換対象のBeanに格納し返り値として返す

    • FieldSetクラスはJDBCにあるResultSetクラスのようにインデックスまたは名前と関連付けてデータを保持するクラスである

    • FieldSetクラスはLineTokenizerによって分割されたレコードの各フィールドの値を保持する

    • インデックスまたは名前を指定して値を格納および取得することができる

下記のような和暦フォーマットのDate型やカンマを含むBigDecimal型の変換を行うファイルを読み込む場合の実装例を示す。

入力ファイル例
"000001","平成28年1月1日","000000001","1,000,000,000"
"000002","平成29年2月2日","000000002","2,000,000,000"
"000003","平成30年3月3日","000000003","3,000,000,000"
入力ファイル仕様
項番 フィールド名 データ型 備考

(1)

branchId

String

(2)

日付

Date

和暦フォーマット

(3)

customerId

String

(4)

amount

BigDecimal

カンマを含む

変換対象クラス
public class UseDateSalesPlanDetail {

    private String branchId;
    private Date date;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}
FieldSetMapperの実装例
@Component
public class UseDateSalesPlanDetailFieldSetMapper implements FieldSetMapper<UseDateSalesPlanDetail> {  // (1)
    /**
     * {@inheritDoc}
     *
     * @param fieldSet {@inheritDoc}
     * @return Sales performance detail.
     * @throws BindException {@inheritDoc}
     */
    @Override
    public UseDateSalesPlanDetail mapFieldSet(FieldSet fieldSet) throws BindException {
        UseDateSalesPlanDetail item = new UseDateSalesPlanDetail();  // (2)

        item.setBranchId(fieldSet.readString("branchId"));  // (3)

        // (4)
        DateFormat japaneseFormat = new SimpleDateFormat("GGGGy年M月d日", new Locale("ja", "JP", "JP"));
        try {
            item.setDate(japaneseFormat.parse(fieldSet.readString("date")));
        } catch (ParseException e) {
            // omitted exception handling
        }

        // (5)
        item.setCustomerId(fieldSet.readString("customerId"));

        // (6)
        DecimalFormat decimalFormat = new DecimalFormat();
        decimalFormat.setParseBigDecimal(true);
        try {
            item.setAmount((BigDecimal) decimalFormat.parse(fieldSet.readString("amount")));
        } catch (ParseException e) {
            // omitted exception handling
        }

        return item;  // (7)
    }
}
設定内容の項目一覧
項番 説明

(1)

FieldSetMapperクラスを実装し、mapFieldSetメソッドをオーバーライドする。 FieldSetMapperの型引数には変換対象クラスを設定する。

(2)

変換処理等を行ったデータを格納するために変換対象クラスの変数を定義する。

(3)

引数で受けたFieldSetからbranchIdを取得し、変換対象クラスの変数へ格納する。
branchIdは変換処理が不要であるため、変換処理等は行っていない。

(4)

引数で受けたFieldSetからdateを取得し、変換対象クラスの変数へ格納する。
和暦フォーマットの日付をDate型へ変換するため、SimpleDateFormatでフォーマットを指定している。

(5)

引数で受けたFieldSetからcustomerIdを取得し、変換対象クラスの変数へ格納する。
customerIdは変換処理が不要であるため、変換処理等は行っていない。

(4)

引数で受けたFieldSetからamountを取得し、変換対象クラスの変数へ格納する。
カンマを含む値をBigDecimal型へ変換するため、DecimalFormatを使用している。

(7)

処理結果を保持している変換対象クラスを返す。

FieldSetクラスからの値取得

FieldSetクラスは、下記のような格納された値を取得するための様々なデータ型に対応したメソッドをもつ。
また、FieldSet生成時にフィールドの名前と関連付けられてデータを格納した場合は、名前指定でのデータ取得、名前を指定しない場合ではインデックスを指定してのデータ取得が可能である。

  • readString()

  • readInt()

  • readBigDecimal()

など

XMLファイル

XMLファイルを扱う場合の定義方法を説明する。

BeanとXML間の変換処理(O/X (Object/XML) マッピング)にはSpring Frameworkが提供するライブラリを使用する。
XMLファイルとオブジェクト間の変換処理を行うライブラリとして、XStreamやJAXBなどを利用したMarshallerおよびUnmarshallerを実装クラスが提供されている。
状況に応じて適しているものを使用すること。

JAXBとXStreamを例に特徴と採用する際のポイントを説明する。

JAXB
  • 変換対象のBeanはBean定義にて指定する

  • スキーマファイルを用いたバリデーションを行うことができる

  • 対外的にスキーマを定義しており、入力ファイルの仕様が厳密に決まっている場合に有用である

XStream
  • Bean定義にて柔軟にXMLの要素とBeanのフィールドをマッピングすることができる

  • 柔軟にBeanマッピングする必要がある場合に有用である

ここでは、JAXBを利用する例を示す。

入力

XMLファイルの入力にはSpring Batchが提供するorg.springframework.batch.item.xml.StaxEventItemReaderを使用する。
StaxEventItemReaderは指定したUnmarshallerを使用してXMLファイルをBeanにマッピングすることでXMLファイルを読み込むことができる。

StaxEventItemReaderは以下の要領で定義する。

  • XMLのルートエレメントとなる変換対象クラスに@XmlRootElementを付与する

  • StaxEventItemReaderに以下のプロパティを設定する

    • プロパティresourceに読み込み対象ファイルを設定する

    • プロパティfragmentRootElementNameにルートエレメントとなる要素の名前を設定する

    • プロパティunmarshallerorg.springframework.oxm.jaxb.Jaxb2Marshallerを設定する

  • Jaxb2Marshallerには以下のプロパティを設定する

    • プロパティclassesToBeBoundに変換対象のクラスをリスト形式で設定する

    • スキーマファイルを用いたバリデーションを行う場合は、以下に示す2つのプロパティを設定する

      • プロパティschemaにバリデーションにて使用するスキーマファイルを設定する

      • プロパティvalidationEventHandlerにバリデーションにて発生したイベントを処理するValidationEventHandlerの実装クラスを設定する

下記の入力ファイルを読み込むための設定例を示す。

入力ファイル例
<?xml version="1.0" encoding="UTF-8"?>
<records>
    <SalesPlanDetail>
        <branchId>000001</branchId>
        <year>2016</year>
        <month>1</month>
        <customerId>0000000001</customerId>
        <amount>1000000000</amount>
    </SalesPlanDetail>
    <SalesPlanDetail>
        <branchId>000002</branchId>
        <year>2017</year>
        <month>2</month>
        <customerId>0000000002</customerId>
        <amount>2000000000</amount>
    </SalesPlanDetail>
    <SalesPlanDetail>
        <branchId>000003</branchId>
        <year>2018</year>
        <month>3</month>
        <customerId>0000000003</customerId>
        <amount>3000000000</amount>
    </SalesPlanDetail>
</records>
変換対象クラス
@XmlRootElement(name = "SalesPlanDetail")  // (1)
public class SalesPlanDetailToJaxb {

    private String branchId;
    private int year;
    private int month;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}
設定内容の項目一覧
項番 説明

(1)

XML のルートタグとするため@XmlRootElementアノテーションを付与する。
タグの名前にSalesPlanDetailを設定する。

上記のファイルを読む込むための設定は以下のとおり。

Bean定義
<!-- (1) (2) (3) -->
<bean id="reader"
      class="org.springframework.batch.item.xml.StaxEventItemReader" scope="step"
      p:resource="file:#{jobParameters[inputFile]}"
      p:fragmentRootElementName="SalesPlanDetail"
      p:strict="true">
    <property name="unmarshaller">  <!-- (4) -->
        <!-- (5) (6) -->
        <bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller"
              p:schema="file:files/test/input/ch05/fileaccess/SalesPlanDetail.xsd"
              p:validationEventHandler-ref="salesPlanDetailValidationEventHandler">
            <property name="classesToBeBound">  <!-- (7) -->
                <list>
                    <value>org.terasoluna.batch.functionaltest.ch05.fileaccess.model.plan.SalesPlanDetailToJaxb</value>
                </list>
            </property>
        </bean>
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

入力ファイルを設定する。

なし

(2)

fragmentRootElementName

ルートエレメントとなる要素の名前を設定する。
対象となるオブジェクトが複数ある場合には、fragmentRootElementNamesを利用する。

なし

(3)

strict

trueを設定すると、入力ファイルが存在しない(開けない)場合に例外が発生する。

true

(4)

unmarshaller

アンマーシャラを設定する。
JAXBを利用する場合は、org.springframework.oxm.jaxb.Jaxb2MarshallerのBeanを設定する。

なし

(5)

schema

バリデーションにて使用するスキーマファイルを設定する。

(6)

validationEventHandler

バリデーションにて発生したイベントを処理するValidationEventHandlerの実装クラスを設定する。
ValidationEventHandlerの実装例は後述する。

(7)

classesToBeBound

変換対象のクラスをリスト形式で設定する。

なし

ValidationEventHandlerの実装例
@Component
// (1)
public class SalesPlanDetailValidationEventHandler implements ValidationEventHandler {
    /**
     * Logger.
     */
    private static final Logger logger =
            LoggerFactory.getLogger(SalesPlanDetailValidationEventHandler.class);

    @Override
    public boolean handleEvent(ValidationEvent event) {
        // (2)
        logger.error("[EVENT [SEVERITY:{}] [MESSAGE:{}] [LINKED EXCEPTION:{}]" +
                " [LOCATOR: [LINE NUMBER:{}] [COLUMN NUMBER:{}] [OFFSET:{}]" +
                " [OBJECT:{}] [NODE:{}] [URL:{}] ] ]",
                event.getSeverity(),
                event.getMessage(),
                event.getLinkedException(),
                event.getLocator().getLineNumber(),
                event.getLocator().getColumnNumber(),
                event.getLocator().getOffset(),
                event.getLocator().getObject(),
                event.getLocator().getNode(),
                event.getLocator().getURL());
        return false;  // (3)
    }
}
設定内容の項目一覧
項番 説明

(1)

ValidationEventHandlerクラスを実装し、handleEventメソッドをオーバーライドする。

(2)

引数で受けたevent(ValidationEvent)からイベントの情報を取得し、適宜処理を行う。
例ではイベントの情報をログ出力している。

(3)

検証処理を終了させるためfalseを返す。 検証処理を続行する場合はtrueを返す。
適切なUnmarshalExceptionValidationException、またはMarshalExceptionを生成して現在の操作を終了させる場合はfalseを返す。

依存ライブラリの追加

org.springframework.oxm.jaxb.Jaxb2Marshallerなど、 Spring Frameworkが提供するライブラリであるSpring Object/XML Marshallingを使用する場合は、 ライブラリの依存関係に以下の設定を追加する必要がある。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-oxm</artifactId>
</dependency>

出力

XMLファイルの出力にはSpring Batchが提供するorg.springframework.batch.item.xml.StaxEventItemWriterを使用する。
StaxEventItemWriterは指定したMarshallerを使用してBeanをXMLにマッピングすることでXMLファイルを出力することができる。

StaxEventItemWriterは以下の要領で定義する。

  • 変換対象クラスに以下の設定を行う

    • XMLのルートエレメントとなるためクラスに@XmlRootElementを付与する

    • @XmlTypeアノテーションを使用してフィールドを出力する順番を設定する

    • XMLへの変換対象外とするフィールドがある場合、対象フィールドのgetterに@XmlTransientアノテーションを付与する

  • StaxEventItemWriterに以下のプロパティを設定する

    • プロパティresourceに出力対象ファイルを設定する

    • プロパティmarshallerorg.springframework.oxm.jaxb.Jaxb2Marshallerを設定する

  • Jaxb2Marshallerには以下のプロパティを設定する

    • プロパティclassesToBeBoundに変換対象のクラスをリスト形式で設定する

下記の出力ファイルを書き出すための設定例を示す。

出力ファイル例
<?xml version="1.0" encoding="UTF-8"?>
<records>
  <Customer>
    <customerId>001</customerId>
    <customerName>CustomerName001</customerName>
    <customerAddress>CustomerAddress001</customerAddress>
    <customerTel>11111111111</customerTel>
    <chargeBranchId>001</chargeBranchId></Customer>
  <Customer>
    <customerId>002</customerId>
    <customerName>CustomerName002</customerName>
    <customerAddress>CustomerAddress002</customerAddress>
    <customerTel>11111111111</customerTel>
    <chargeBranchId>002</chargeBranchId></Customer>
  <Customer>
    <customerId>003</customerId>
    <customerName>CustomerName003</customerName>
    <customerAddress>CustomerAddress003</customerAddress>
    <customerTel>11111111111</customerTel>
    <chargeBranchId>003</chargeBranchId>
  </Customer>
</records>
XMLファイル出力時のフォーマット処理(改行およびインデント)について

上記の出力ファイル例ではフォーマット処理(改行およびインデント)済みのXMLを例示しているが、実際にはフォーマットされていないファイルが出力される。

Jaxb2MarshallerにはXML出力時にフォーマットを行う機能があるが期待どおり動作しない。
この件に関してはSpring Forumにて議論されているため、今後期待どおり動作するようになる可能性がある。

これを回避し、フォーマット済みの出力を行うためには、以下のようにmarshallerPropertiesに設定すればよい。

<property name="marshaller">
    <bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="classesToBeBound">
            <!-- omitted settings -->
        </property>
        <property name="marshallerProperties">
            <map>
                <entry>
                    <key>
                        <util:constant
                            static-field="javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT"/>
                    </key>
                    <value type="java.lang.Boolean">true</value>
                </entry>
            </map>
        </property>
    </bean>
</property>
変換対象クラス
@XmlRootElement(name = "Customer")  // (2)
@XmlType(propOrder={"customerId", "customerName", "customerAddress",
        "customerTel", "chargeBranchId"})  // (2)
public class CustomerToJaxb {

    private String customerId;
    private String customerName;
    private String customerAddress;
    private String customerTel;
    private String chargeBranchId;
    private Timestamp createDate;
    private Timestamp updateDate;

    // omitted getter/setter

    @XmlTransient  // (3)
    public Timestamp getCreateDate() { return createDate; }

    @XmlTransient  // (3)
    public Timestamp getUpdateDate() { return updateDate; }
}
設定内容の項目一覧
項番 説明

(1)

XML のルートタグとするため@XmlRootElementアノテーションを付与する。
タグの名前にCustomerを設定している。

(2)

@XmlTypeアノテーションを使用してフィールドを出力する順番を設定する。

(3)

XMLへの変換対象外とするフィールドのgetterに@XmlTransientアノテーションを付与する。

上記のファイルを書き出すための設定は以下のとおり。

Bean定義
<!-- (1) (2) (3) (4) (5) (6) -->
<bean id="writer"
      class="org.springframework.batch.item.xml.StaxEventItemWriter" scope="step"
      p:resource="file:#{jobParameters[outputFile]}"
      p:encoding="MS932"
      p:rootTagName="records"
      p:overwriteOutput="true"
      p:shouldDeleteIfEmpty="false"
      p:transactional="true">
    <property name="marshaller">  <!-- (7) -->
        <bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
            <property name="classesToBeBound">  <!-- (8) -->
                <list>
                    <value>org.terasoluna.batch.functionaltest.ch05.fileaccess.model.mst.CustomerToJaxb</value>
                </list>
            </property>
        </bean>
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

resource

出力ファイルを設定する

なし

(2)

encoding

入力ファイルの文字コードを設定する

JavaVMのデフォルト文字セット

(3)

rootTagName

XMLのルートタグを設定する。

(4)

overwriteOutput

trueの場合、既にファイルが存在すれば削除する。
falseの場合、既にファイルが存在すれば例外をスローする。

true

(5)

shouldDeleteIfEmpty

trueの場合、出力結果が空ファイルであれば削除する。

false

(6)

transactional

トランザクション制御を行うかを設定する。詳細は、トランザクション制御を参照。

true

(7)

marshaller

マーシャラを設定する。 JAXBを利用する場合は、org.springframework.oxm.jaxb.Jaxb2Marshallerを設定する。

なし

(8)

classesToBeBound

変換対象のクラスをリスト形式で設定する。

なし

依存ライブラリの追加

org.springframework.oxm.jaxb.Jaxb2Marshallerなど、 Spring Frameworkが提供するライブラリであるSpring Object/XML Marshallingを使用する場合は、 ライブラリの依存関係に以下の設定を追加する必要がある。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-oxm</artifactId>
</dependency>
ヘッダ・フッタの出力

ヘッダとフッタの出力には、org.springframework.batch.item.xml.StaxWriterCallbackの実装クラスを使用する。

ヘッダの出力は、headerCallback、フッタの出力は、footerCallbackStaxWriterCallbackの実装を設定する。

以下に出力されるファイルの例を示す。
ヘッダはルートエレメント開始タグの直後、フッタはルートエレメント終了タグの直前に出力される。

出力ファイル例
<?xml version="1.0" encoding="UTF-8"?>
<records>
<!-- Customer list header -->
  <Customer>
    <customerId>001</customerId>
    <customerName>CustomerName001</customerName>
    <customerAddress>CustomerAddress001</customerAddress>
    <customerTel>11111111111</customerTel>
    <chargeBranchId>001</chargeBranchId></Customer>
  <Customer>
    <customerId>002</customerId>
    <customerName>CustomerName002</customerName>
    <customerAddress>CustomerAddress002</customerAddress>
    <customerTel>11111111111</customerTel>
    <chargeBranchId>002</chargeBranchId></Customer>
  <Customer>
    <customerId>003</customerId>
    <customerName>CustomerName003</customerName>
    <customerAddress>CustomerAddress003</customerAddress>
    <customerTel>11111111111</customerTel>
    <chargeBranchId>003</chargeBranchId>
  </Customer>
<!-- Customer list footer -->
</records>
XMLファイル出力時のフォーマット処理(改行およびインデント)について

上記の出力ファイル例ではフォーマット処理(改行およびインデント)済みのXMLを例示しているが、実際にはフォーマットされていないファイルが出力される。

上記のようなファイルを出力する設定を以下に示す。

Bean定義
<!-- (1) (2) -->
<bean id="writer"
      class="org.springframework.batch.item.xml.StaxEventItemWriter" scope="step"
      p:resource="file:#{jobParameters[outputFile]}"
      p:headerCallback-ref="writeHeaderStaxWriterCallback"
      p:footerCallback-ref="writeFooterStaxWriterCallback">
    <property name="marshaller">
        <!-- omitted settings -->
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

headerCallback

StaxWriterCallbackの実装クラスを設定する。

(2)

footerCallback

StaxWriterCallbackの実装クラスを設定する。

StaxWriterCallbackは以下の要領で実装する。

  • StaxWriterCallbackクラスを実装し、writeメソッドをオーバーライドする

  • 引数で受けるXMLEventWriterを用いてヘッダ/フッタを出力する

StaxWriterCallbackの実装例
@Component
public class WriteHeaderStaxWriterCallback implements StaxWriterCallback { // (1)
    @Override
    public void write(XMLEventWriter writer) throws IOException {
        XMLEventFactory factory = XMLEventFactory.newInstance();
        try {
            writer.add(factory.createComment(" Customer list header ")); // (2)
        } catch (XMLStreamException e) {
            // omitted exception handling
        }
    }
}
設定内容の項目一覧
項番 説明

(1)

StaxWriterCallbackクラスを実装し、writeメソッドをオーバーライドする。

(2)

引数で受けるXMLEventWriterを用いてヘッダ/フッタを出力する。

XMLEventFactoryを使用したXMLの出力

XMLEventWriterクラスを用いたXMLファイルの出力ではXMLEventFactoryクラスを使用することで効率的にXMLEventを生成することができる。

XMLEventWriterクラスにはaddメソッドが定義されており、XMLEventオブジェクトを引数に取りXMLファイルの出力を行う。
XMLEventオブジェクトを都度生成するのは非常に手間が掛かるため、XMLEventを容易に生成することができるXMLEventFactoryクラスを使用する。
XMLEventFactoryクラスには createStartDocumentメソッドやcreateStartElementメソッドなど、作成するイベントに対応したメソッドが定義してある。

マルチフォーマット

マルチフォーマットファイルを扱う場合の定義方法を説明する。

マルチフォーマットは、Overviewで説明したとおり(ヘッダn行 + データn行 + トレーラn行)* n + フッタn行 の形式を基本とするが以下のようなパターンも存在する。

  • フッタレコードがある場合、ない場合

  • 同一レコード区分内でフォーマットが異なるレコードがある場合

    • 例)データ部は項目数が5と6のデータレコードが混在する

マルチフォーマットのパターンはいくつかあるが、実現方式は同じになる。

入力

マルチフォーマットファイルの読み込みには、Spring Batchが提供するorg.springframework.batch.item.file.mapping.PatternMatchingCompositeLineMapperを使用する。
マルチフォーマットファイルでは各レコードのフォーマットごとに異なるBeanにマッピングする必要がある。
PatternMatchingCompositeLineMapperは、正規表現によってレコードに対して使用するLineTokenizer およびFieldSetMapper を選択することができる。

たとえば、以下のような形で使用するLineTokenizers を選択することが可能である。

  • 正規表現USER*(レコードの先頭がUSERである)にマッチする場合はuserTokenizerを使用する

  • 正規表現LINEA*(レコードの先頭がLINEAである)にマッチする場合はlineATokenizerを使用する

マルチフォーマットファイルを読み込む際のレコードにかかるフォーマットの制約

マルチフォーマットファイルの読み込みを行うためには、正規表現でレコード区分を判別可能なフォーマットでなければならない。

PatternMatchingCompositeLineMapperは以下の要領で実装する。

  • 変換対象クラスはレコード区分をもつクラスを定義し、各レコード区分のクラスに継承させる

  • 各レコードをBeanにマッピングするためのLineTokenizerおよびFieldSetMapperを定義する

  • PatternMatchingCompositeLineMapperを定義する

    • プロパティtokenizersに各レコード区分に対応するLineTokenizerを設定する

    • プロパティfieldSetMappersに各レコード区分に対応するFieldSetMapperを設定する

変換対象クラスはレコード区分をもつクラスを定義し、各レコード区分のクラスに継承させる

ItemProcessorは1つの型を引数に取る仕様である。

しかし、単純にPatternMatchingCompositeLineMapperにてマルチフォーマットのファイルをレコード区分ごとに異なるBeanにマッピングすると、ItemProcessorは1つの型を引数に取るため複数の型を処理することができない。

そのため、変換対象のクラスに継承関係をもたせ、ItemProcessorの引数の型にスーパークラスを指定することで解決が可能である。

以下に変換対象クラスのクラス図とItemProcessorの定義例を示す。

Conversion target class definition when loading multiple formats
変換対象クラスのクラス図
ItemProcessorの定義例
public class MultiLayoutItemProcessor implements
        ItemProcessor<SalesPlanDetailMultiLayoutRecord, String> {
    @Override
    // (1)
    public String process(SalesPlanDetailMultiLayoutRecord item) throws Exception {
        String record = item.getRecord();  // (2)

        switch (record) {  // (3)
        case "H":
            // omitted business logic
        case "D":
            // omitted business logic
        case "T":
            // omitted business logic
        case "E":
            // omitted business logic
        default:
            // omitted exception handling
        }
    }
}
設定内容の項目一覧
項番 説明

(1)

ItemProcessorの引数に継承関係をもたせた変換対象クラスのスーパークラスを設定する。

(2)

itemからレコード区分を取得する。
各レコード区分によって実態のクラスは異なるが、ポリモーフィズムによってレコード区分を取得できる。

(3)

レコード区分を判定し、各レコード区分ごとの処理を行う。
適宜、クラスの変換処理等を行うこと。

以下に下記の入力ファイルを読み込むための設定例を示す。実装例を示す。

入力ファイル例
H,Sales_plan_detail header No.1
D,000001,2016,1,0000000001,100000000
D,000001,2016,1,0000000002,200000000
D,000001,2016,1,0000000003,300000000
T,000001,3,600000000
H,Sales_plan_detail header No.2
D,00002,2016,1,0000000004,400000000
D,00002,2016,1,0000000005,500000000
D,00002,2016,1,0000000006,600000000
T,00002,3,1500000000
H,Sales_plan_detail header No.3
D,00003,2016,1,0000000007,700000000
D,00003,2016,1,0000000008,800000000
D,00003,2016,1,0000000009,900000000
T,00003,3,2400000000
E,3,9,4500000000

下記に変換対象クラスのBean定義例を示す。

変換対象クラス
/**
 * Model of record indicator of sales plan detail.
 */
public class SalesPlanDetailMultiLayoutRecord {

    protected String record;

    // omitted getter/setter
}

/**
 * Model of sales plan detail header.
 */
public class SalesPlanDetailHeader extends SalesPlanDetailMultiLayoutRecord {

    private String description;

    // omitted getter/setter
}

/**
 * Model of Sales plan Detail.
 */
public class SalesPlanDetailData extends SalesPlanDetailMultiLayoutRecord {

    private String branchId;
    private int year;
    private int month;
    private String customerId;
    private BigDecimal amount;

    // omitted getter/setter
}

/**
 * Model of Sales plan Detail.
 */
public class SalesPlanDetailTrailer extends SalesPlanDetailMultiLayoutRecord {

    private String branchId;
    private int number;
    private BigDecimal total;

    // omitted getter/setter
}

/**
 * Model of Sales plan Detail.
 */
public class SalesPlanDetailEnd extends SalesPlanDetailMultiLayoutRecord {
    // omitted getter/setter

    private int headNum;
    private int trailerNum;
    private BigDecimal total;

    // omitted getter/setter
}

上記のファイルを読む込むための設定は以下のとおり。

Bean定義例
<!-- (1) -->
<bean id="headerDelimitedLineTokenizer"
      class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
      p:names="record,description"/>

<bean id="dataDelimitedLineTokenizer"
      class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
      p:names="record,branchId,year,month,customerId,amount"/>

<bean id="trailerDelimitedLineTokenizer"
      class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
      p:names="record,branchId,number,total"/>

<bean id="endDelimitedLineTokenizer"
      class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"
      p:names="record,headNum,trailerNum,total"/>

<!-- (2) -->
<bean id="headerBeanWrapperFieldSetMapper"
      class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
      p:targetType="org.terasoluna.batch.functionaltest.ch05.fileaccess.model.plan.SalesPlanDetailHeader"/>

<bean id="dataBeanWrapperFieldSetMapper"
      class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
      p:targetType="org.terasoluna.batch.functionaltest.ch05.fileaccess.model.plan.SalesPlanDetailData"/>

<bean id="trailerBeanWrapperFieldSetMapper"
      class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
      p:targetType="org.terasoluna.batch.functionaltest.ch05.fileaccess.model.plan.SalesPlanDetailTrailer"/>

<bean id="endBeanWrapperFieldSetMapper"
      class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"
      p:targetType="org.terasoluna.batch.functionaltest.ch05.fileaccess.model.plan.SalesPlanDetailEnd"/>

<bean id="reader"
    class="org.springframework.batch.item.file.FlatFileItemReader" scope="step"
    p:resource="file:#{jobParameters[inputFile]}">
    <property name="lineMapper">  <!-- (3) -->
        <bean class="org.springframework.batch.item.file.mapping.PatternMatchingCompositeLineMapper">
            <property name="tokenizers">  <!-- (4) -->
                <map>
                    <entry key="H*" value-ref="headerDelimitedLineTokenizer"/>
                    <entry key="D*" value-ref="dataDelimitedLineTokenizer"/>
                    <entry key="T*" value-ref="trailerDelimitedLineTokenizer"/>
                    <entry key="E*" value-ref="endDelimitedLineTokenizer"/>
                </map>
            </property>
            <property name="fieldSetMappers">  <!-- (5) -->
                <map>
                    <entry key="H*" value-ref="headerBeanWrapperFieldSetMapper"/>
                    <entry key="D*" value-ref="dataBeanWrapperFieldSetMapper"/>
                    <entry key="T*" value-ref="trailerBeanWrapperFieldSetMapper"/>
                    <entry key="E*" value-ref="endBeanWrapperFieldSetMapper"/>
                </map>
            </property>
        </bean>
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

各レコードに対応するLineTokenizer

各レコード区分に対応するLineTokenizerを定義する。

(2)

各レコードに対応するFieldSetMapper

各レコード区分に対応するFieldSetMapperを定義する。

(3)

lineMapper

org.springframework.batch.item.file.mapping.PatternMatchingCompositeLineMapperを設定する。

なし

(3)

tokenizers

map形式で各レコード区分に対応するLineTokenizerを設定する。
keyにレコードを判別する正規表現を設定し、value-refに使用するLineTokenizerを設定する。

なし

(4)

tokenizers

map形式で各レコード区分に対応するFieldSetMapperを設定する。
keyにレコードを判別する正規表現を設定し、value-refに使用するFieldSetMapperを設定する。

なし

出力

マルチフォーマットファイルを扱う場合の定義方法を説明する。

マルチフォーマットファイル読み込みではレコード区分によって使用するLineTokenizerおよびFieldSetMapperを判別するPatternMatchingCompositeLineMapperを使用することで実現可能である。
しかし、書き込み時に同様の機能をもつコンポーネントは提供されていない。

そのため、ItemProcessor内で変換対象クラスをレコード(文字列)に変換する処理までを行い、ItemWriterでは受け取った文字列をそのまま書き込みを行うことでマルチフォーマットファイルの書き込みを実現する。

マルチフォーマットファイルの書き込みは以下の要領で実装する。

  • ItemProcessorにて変換対象クラスをレコード(文字列)に変換してItemWriterに渡す

    • 例では、各レコード区分ごとのLineAggregatorおよびFieldExtractorを定義し、ItemProcessorでインジェクトして使用する

  • ItemWriterでは受け取った文字列をそのままファイルへ書き込みを行う

    • ItemWriterのプロパティlineAggregatorPassThroughLineAggregatorを設定する

    • PassThroughLineAggregatorは受け取ったitemのitem.toString()した結果を返すLineAggregatorである

以下に下記の出力ファイルを読み込むための設定例を示す。実装例を示す。

出力ファイル例
H,Sales_plan_detail header No.1
D,000001,2016,1,0000000001,100000000
D,000001,2016,1,0000000002,200000000
D,000001,2016,1,0000000003,300000000
T,000001,3,600000000
H,Sales_plan_detail header No.2
D,00002,2016,1,0000000004,400000000
D,00002,2016,1,0000000005,500000000
D,00002,2016,1,0000000006,600000000
T,00002,3,1500000000
H,Sales_plan_detail header No.3
D,00003,2016,1,0000000007,700000000
D,00003,2016,1,0000000008,800000000
D,00003,2016,1,0000000009,900000000
T,00003,3,2400000000
E,3,9,4500000000

変換対象クラスの定義およびItemProcessor定義例、注意点はマルチフォーマットの入力と同様である。

上記のファイルを出力するための設定は以下のとおり。 ItemProcessorの定義例をBean定義例の後に示す。

Bean定義例
<!-- (1) -->
<bean id="headerDelimitedLineAggregator"
      class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
    <property name="fieldExtractor">
        <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor"
              p:names="record,description"/>
    </property>
</bean>

<bean id="dataDelimitedLineAggregator"
      class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
    <property name="fieldExtractor">
        <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor"
              p:names="record,branchId,year,month,customerId,amount"/>
    </property>
</bean>

<bean id="trailerDelimitedLineAggregator"
      class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
    <property name="fieldExtractor">
        <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor"
              p:names="record,branchId,number,total"/>
    </property>
</bean>

<bean id="endDelimitedLineAggregator"
      class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
    <property name="fieldExtractor">
        <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor"
              p:names="record,headNum,trailerNum,total"/>
    </property>
</bean>


<bean id="writer" class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step"
      p:resource="file:#{jobParameters[outputFile]}"/>
    <property name="lineAggregator">  <!-- (2) -->
        <bean class="org.springframework.batch.item.file.transform.PassThroughLineAggregator"/>
    </property>
</bean>
設定内容の項目一覧
項番 プロパティ名 設定内容 必須 デフォルト値

(1)

各レコード区分に対応するLineAggregatorおよびFieldExtractor

LineAggregatorおよびFieldExtractorを定義する。
ItemProcessorLineAggregatorをインジェクトして使用する。

(2)

lineAggregator

org.springframework.batch.item.file.transform.PassThroughLineAggregatorを設定する。

なし

ItemProcessorの実装例を以下に示す。
例で実装しているのは、受け取ったitemを文字列に変換してItemWriterに渡す処理のみである。

ItemProcessorの定義例
public class MultiLayoutItemProcessor implements
        ItemProcessor<SalesPlanDetailMultiLayoutRecord, String> {

    // (1)
    @Inject
    @Named("headerDelimitedLineAggregator")
    DelimitedLineAggregator<SalesPlanDetailMultiLayoutRecord> headerDelimitedLineAggregator;

    @Inject
    @Named("dataDelimitedLineAggregator")
    DelimitedLineAggregator<SalesPlanDetailMultiLayoutRecord> dataDelimitedLineAggregator;

    @Inject
    @Named("trailerDelimitedLineAggregator")
    DelimitedLineAggregator<SalesPlanDetailMultiLayoutRecord> trailerDelimitedLineAggregator;

    @Inject
    @Named("endDelimitedLineAggregator")
    DelimitedLineAggregator<SalesPlanDetailMultiLayoutRecord> endDelimitedLineAggregator;

    @Override
    // (2)
    public String process(SalesPlanDetailMultiLayoutRecord item) throws Exception {
        String record = item.getRecord();  // (3)

        switch (record) {  // (4)
        case "H":
            return headerDelimitedLineAggregator.aggregate(item);  // (5)
        case "D":
            return dataDelimitedLineAggregator.aggregate(item);  // (5)
        case "T":
            return trailerDelimitedLineAggregator.aggregate(item);  // (5)
        case "E":
            return endDelimitedLineAggregator.aggregate(item);  // (5)
        default:
            throw new IncorrectRecordClassificationException(
                    "Record classification is incorrect.[value:" + record + "]");
        }
    }
}
設定内容の項目一覧
項番 説明

(1)

各レコード区分に対応するLineAggregatorをインジェクトする。

(2)

ItemProcessorの引数に継承関係をもたせた変換対象クラスのスーパークラスを設定する。

(3)

itemからレコード区分を取得する。

(4)

レコード区分を判定し、各レコード区分ごとの処理を行う。

(5)

各レコード区分に対応するLineAggregatorを使用し変換対象クラスをレコード(文字列)に変換してItemWriterに渡す。