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
が定義されたため、基本的な記述の流れを説明できます。
書き込まれるオブジェクトは、
String
を取得するためにLineAggregator
に渡されます。返された
String
は、構成されたファイルに書き込まれます。
FlatFileItemWriter
からの次の抜粋は、これをコードで表現しています。
public void write(T item) throws Exception {
write(lineAggregator.aggregate(item) + LINE_SEPARATOR);
}
Java
XML
Java では、構成の簡単な例は次のようになります。
@Bean
public FlatFileItemWriter itemWriter() {
return new FlatFileItemWriterBuilder<Foo>()
.name("itemWriter")
.resource(new FileSystemResource("target/test-outputs/output.txt"))
.lineAggregator(new PassThroughLineAggregator<>())
.build();
}
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 行読み取ります。
FieldSet
を取得するために、その行をLineTokenizer#tokenize()
メソッドに渡します。トークン化から返された
FieldSet
をFieldSetMapper
に渡し、ItemReader#read()
メソッドから結果を返します。
ファイルの書き込みには、似ているが逆の手順があります。
書き込むアイテムをライターに渡します。
アイテムのフィールドを配列に変換します。
結果の配列を 1 行に集約します。
フレームワークがオブジェクトのどのフィールドを書き出す必要があるかを知る方法がないため、次のインターフェース定義に示すように、アイテムを配列に変換するタスクを達成するために FieldExtractor
を書く必要があります。
public interface FieldExtractor<T> {
Object[] extract(T item);
}
FieldExtractor
インターフェースの実装では、提供されたオブジェクトのフィールドから配列を作成する必要があります。この配列は、エレメント間の区切り文字を使用して、または固定幅の行の一部として書き出すことができます。
PassThroughFieldExtractor
配列、Collection
、FieldSet
などのコレクションを書き出す必要がある多くの場合があります。これらのコレクション型の 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
を使用する方法を示しています。
@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
を使用する方法を示しています。
<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
を自動的に作成することもできます。
@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 で次のように構成できます。
@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 で次のように構成できます。
<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
を自動的に作成することもできます。
@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 に設定すると、ライターが開かれたときに同じ名前の既存のファイルが削除されます。