FlatFileItemWriter

フラットファイルへの書き込みには、ファイルからの読み取りで克服しなければならない課題と課題があります。ステップは、トランザクション形式で区切られた形式または固定長形式のいずれかを書き込むことができる必要があります。

LineAggregator

LineTokenizer インターフェースがアイテムを取得して String に変換するために必要であるように、ファイル書き込みでは、ファイルに書き込むために複数のフィールドを単一のストリングに集約する方法が必要です。Spring Batch では、これは LineAggregator であり、次のインターフェース定義に示されています。

public interface LineAggregator<T> {

    public String aggregate(T item);

}

LineAggregator は LineTokenizer の論理的な反対です。LineTokenizer は String を受け取って FieldSet を返しますが、LineAggregator は item を受け取って String を返します。

PassThroughLineAggregator

LineAggregator インターフェースの最も基本的な実装は PassThroughLineAggregator です。これは、次のコードに示すように、オブジェクトがすでにストリングであるか、そのストリング表現が書き込みに受け入れられることを前提としています。

public class PassThroughLineAggregator<T> implements LineAggregator<T> {

    public String aggregate(T item) {
        return item.toString();
    }
}

上記の実装は、文字列の作成を直接制御する必要があるが、トランザクションや再起動のサポートなどの FlatFileItemWriter の利点が必要な場合に役立ちます。

簡易ファイル作成の例

LineAggregator インターフェースとその最も基本的な実装である PassThroughLineAggregator が定義されたため、基本的な記述の流れを説明できます。

  1. 書き込まれるオブジェクトは、String を取得するために LineAggregator に渡されます。

  2. 返された String は、構成されたファイルに書き込まれます。

FlatFileItemWriter からの次の抜粋は、これをコードで表現しています。

public void write(T item) throws Exception {
    write(lineAggregator.aggregate(item) + LINE_SEPARATOR);
}
  • Java

  • XML

Java では、構成の簡単な例は次のようになります。

Java 構成
@Bean
public FlatFileItemWriter itemWriter() {
	return  new FlatFileItemWriterBuilder<Foo>()
           			.name("itemWriter")
           			.resource(new FileSystemResource("target/test-outputs/output.txt"))
           			.lineAggregator(new PassThroughLineAggregator<>())
           			.build();
}

XML では、構成の簡単な例は次のようになります。

XML 構成
<bean id="itemWriter" class="org.spr...FlatFileItemWriter">
    <property name="resource" value="file:target/test-outputs/output.txt" />
    <property name="lineAggregator">
        <bean class="org.spr...PassThroughLineAggregator"/>
    </property>
</bean>

FieldExtractor

前述の例は、ファイルへの書き込みの最も基本的な使用に役立つ場合があります。ただし、FlatFileItemWriter のほとんどのユーザーは、書き出す必要があるドメインオブジェクトを持っているため、行に変換する必要があります。ファイルの読み取りでは、以下が必要でした。

  1. ファイルから 1 行読み取ります。

  2. FieldSet を取得するために、その行を LineTokenizer#tokenize() メソッドに渡します。

  3. トークン化から返された FieldSet を FieldSetMapper に渡し、ItemReader#read() メソッドから結果を返します。

ファイルの書き込みには、似ているが逆の手順があります。

  1. 書き込むアイテムをライターに渡します。

  2. アイテムのフィールドを配列に変換します。

  3. 結果の配列を 1 行に集約します。

フレームワークがオブジェクトのどのフィールドを書き出す必要があるかを知る方法がないため、次のインターフェース定義に示すように、アイテムを配列に変換するタスクを達成するために FieldExtractor を書く必要があります。

public interface FieldExtractor<T> {

    Object[] extract(T item);

}

FieldExtractor インターフェースの実装では、提供されたオブジェクトのフィールドから配列を作成する必要があります。この配列は、エレメント間の区切り文字を使用して、または固定幅の行の一部として書き出すことができます。

PassThroughFieldExtractor

配列、CollectionFieldSet などのコレクションを書き出す必要がある多くの場合があります。これらのコレクション型の 1 つから配列を「抽出」するのは非常に簡単です。そのためには、コレクションを配列に変換します。このシナリオでは PassThroughFieldExtractor を使用する必要があります。渡されたオブジェクトがコレクションの型ではない場合、PassThroughFieldExtractor は抽出されるアイテムのみを含む配列を返すことに注意してください。

BeanWrapperFieldExtractor

ファイル読み取りのセクションで説明した BeanWrapperFieldSetMapper の場合と同様、変換を自分で記述するよりも、ドメインオブジェクトをオブジェクト配列に変換する方法を構成する方が望ましい場合がよくあります。次の例に示すように、BeanWrapperFieldExtractor はこの機能を提供します。

BeanWrapperFieldExtractor<Name> extractor = new BeanWrapperFieldExtractor<>();
extractor.setNames(new String[] { "first", "last", "born" });

String first = "Alan";
String last = "Turing";
int born = 1912;

Name n = new Name(first, last, born);
Object[] values = extractor.extract(n);

assertEquals(first, values[0]);
assertEquals(last, values[1]);
assertEquals(born, values[2]);

このエクストラクターの実装には、必須のプロパティが 1 つだけあります。それは、マップするフィールドの名前です。BeanWrapperFieldSetMapper が FieldSet のフィールドを提供されたオブジェクトの setter にマップするためにフィールド名を必要とするように、BeanWrapperFieldExtractor はオブジェクト配列を作成するために getter にマップするための名前を必要とします。名前の順序によって配列内のフィールドの順序が決まることに注意してください。

区切りファイル書き込みの例

最も基本的なフラットファイル形式は、すべてのフィールドが区切り文字で区切られている形式です。これは、DelimitedLineAggregator を使用して実現できます。次の例では、顧客アカウントのクレジットを表す単純なドメインオブジェクトを書き出します。

public class CustomerCredit {

    private int id;
    private String name;
    private BigDecimal credit;

    //getters and setters removed for clarity
}

ドメインオブジェクトが使用されているため、使用する区切り文字とともに、FieldExtractor インターフェースの実装を提供する必要があります。

  • Java

  • XML

次の例は、Java で区切り文字を使用して FieldExtractor を使用する方法を示しています。

Java 構成
@Bean
public FlatFileItemWriter<CustomerCredit> itemWriter(Resource outputResource) throws Exception {
	BeanWrapperFieldExtractor<CustomerCredit> fieldExtractor = new BeanWrapperFieldExtractor<>();
	fieldExtractor.setNames(new String[] {"name", "credit"});
	fieldExtractor.afterPropertiesSet();

	DelimitedLineAggregator<CustomerCredit> lineAggregator = new DelimitedLineAggregator<>();
	lineAggregator.setDelimiter(",");
	lineAggregator.setFieldExtractor(fieldExtractor);

	return new FlatFileItemWriterBuilder<CustomerCredit>()
				.name("customerCreditWriter")
				.resource(outputResource)
				.lineAggregator(lineAggregator)
				.build();
}

次の例は、XML で区切り文字を使用して FieldExtractor を使用する方法を示しています。

XML 構成
<bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
    <property name="resource" ref="outputResource" />
    <property name="lineAggregator">
        <bean class="org.spr...DelimitedLineAggregator">
            <property name="delimiter" value=","/>
            <property name="fieldExtractor">
                <bean class="org.spr...BeanWrapperFieldExtractor">
                    <property name="names" value="name,credit"/>
                </bean>
            </property>
        </bean>
    </property>
</bean>

前の例では、この章で前述した BeanWrapperFieldExtractor を使用して、CustomerCredit 内の名前フィールドとクレジットフィールドをオブジェクト配列に変換し、各フィールドの間にコンマを付けて書き出します。

  • Java

  • XML

次の例に示すように、FlatFileItemWriterBuilder.DelimitedBuilder を使用して BeanWrapperFieldExtractor および DelimitedLineAggregator を自動的に作成することもできます。

Java 構成
@Bean
public FlatFileItemWriter<CustomerCredit> itemWriter(Resource outputResource) throws Exception {
	return new FlatFileItemWriterBuilder<CustomerCredit>()
				.name("customerCreditWriter")
				.resource(outputResource)
				.delimited()
				.delimiter("|")
				.names(new String[] {"name", "credit"})
				.build();
}

FlatFileItemWriterBuilder の使用に相当する XML はありません。

固定幅ファイルの書き込みの例

フラットファイル形式の型は、区切り文字だけではありません。多くの人は、各列に設定された幅を使用してフィールド間を線引きすることを好みます。これは通常、「固定幅」と呼ばれます。Spring Batch は、FormatterLineAggregator を使用したファイル書き込みでこれをサポートします。

  • Java

  • XML

上記と同じ CustomerCredit ドメインオブジェクトを使用して、Java で次のように構成できます。

Java 構成
@Bean
public FlatFileItemWriter<CustomerCredit> itemWriter(Resource outputResource) throws Exception {
	BeanWrapperFieldExtractor<CustomerCredit> fieldExtractor = new BeanWrapperFieldExtractor<>();
	fieldExtractor.setNames(new String[] {"name", "credit"});
	fieldExtractor.afterPropertiesSet();

	FormatterLineAggregator<CustomerCredit> lineAggregator = new FormatterLineAggregator<>();
	lineAggregator.setFormat("%-9s%-2.0f");
	lineAggregator.setFieldExtractor(fieldExtractor);

	return new FlatFileItemWriterBuilder<CustomerCredit>()
				.name("customerCreditWriter")
				.resource(outputResource)
				.lineAggregator(lineAggregator)
				.build();
}

上記と同じ CustomerCredit ドメインオブジェクトを使用して、XML で次のように構成できます。

XML 構成
<bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
    <property name="resource" ref="outputResource" />
    <property name="lineAggregator">
        <bean class="org.spr...FormatterLineAggregator">
            <property name="fieldExtractor">
                <bean class="org.spr...BeanWrapperFieldExtractor">
                    <property name="names" value="name,credit" />
                </bean>
            </property>
            <property name="format" value="%-9s%-2.0f" />
        </bean>
    </property>
</bean>

前の例のほとんどは見覚えがあるはずです。ただし、format プロパティの値は新しいものです。

  • Java

  • XML

次の例は、Java の format プロパティを示しています。

...
FormatterLineAggregator<CustomerCredit> lineAggregator = new FormatterLineAggregator<>();
lineAggregator.setFormat("%-9s%-2.0f");
...

次の例は、XML の format プロパティを示しています。

<property name="format" value="%-9s%-2.0f" />

基礎となる実装は、Java 5 の一部として追加された同じ Formatter を使用して構築されます。Java Formatter は、C プログラミング言語の printf 機能に基づいています。フォーマッタの設定方法の詳細は、フォーマッター (標準 Javadoc) の Javadoc にあります。

  • Java

  • XML

次の例に示すように、FlatFileItemWriterBuilder.FormattedBuilder を使用して BeanWrapperFieldExtractor および FormatterLineAggregator を自動的に作成することもできます。

Java 構成
@Bean
public FlatFileItemWriter<CustomerCredit> itemWriter(Resource outputResource) throws Exception {
	return new FlatFileItemWriterBuilder<CustomerCredit>()
				.name("customerCreditWriter")
				.resource(outputResource)
				.formatted()
				.format("%-9s%-2.0f")
				.names(new String[] {"name", "credit"})
				.build();
}

ファイル作成の処理

FlatFileItemReader は、ファイルリソースと非常に単純な関連にあります。リーダーが初期化されると、ファイルが存在する場合はファイルを開き、存在しない場合は例外をスローします。ファイルの書き込みはそれほど単純ではありません。一見すると、FlatFileItemWriter にも同様の簡単な契約が存在するように見えます。ファイルがすでに存在する場合は例外をスローし、存在しない場合は作成して書き込みを開始します。ただし、Job を再起動すると、問題が発生する機能があります。通常の再起動シナリオでは、契約は逆になります。ファイルが存在する場合は、最後の既知の正常な位置から書き込みを開始し、存在しない場合は例外をスローします。ただし、このジョブのファイル名が常に同じ場合はどうなるでしょうか? この場合、再起動しない限り、ファイルが存在する場合は削除することをお勧めします。この可能性のため、FlatFileItemWriter にはプロパティ shouldDeleteIfExists が含まれています。このプロパティを true に設定すると、ライターが開かれたときに同じ名前の既存のファイルが削除されます。