XML アイテムリーダーとライター

Spring Batch は、XML レコードの読み取りとそれらの Java オブジェクトへのマッピング、および Java オブジェクトを XML レコードとして書き込むためのトランザクションインフラストラクチャを提供します。

ストリーミング XML の制約

他の標準 XML 解析 API はバッチ処理要件に適合しないため、StAX API は I/O に使用されます(DOM は入力全体を一度にメモリにロードし、SAX はユーザーがコールバックのみを提供できるようにすることで解析プロセスを制御します)。

Spring Batch で XML の入出力がどのように機能するかを考慮する必要があります。まず、ファイルの読み取りや書き込みとは異なるが、Spring Batch XML 処理全体で共通する概念がいくつかあります。XML 処理では、トークン化する必要があるレコードの行(FieldSet インスタンス)の代わりに、次の図に示すように、XML リソースは個々のレコードに対応する「フラグメント」のコレクションであると想定されます。

XML Input
図 1: XML 入力

上記のシナリオでは、"trade" タグは "rootelement" として定義されています。'<trade>' と '</trade>' の間のすべてが 1 つの ' フラグメント ' と見なされます。Spring Batch は、オブジェクト / XML マッピング(OXM)を使用してフラグメントをオブジェクトにバインドします。ただし、Spring Batch は特定の XML バインディングテクノロジーに関連付けられていません。典型的な使用箇所は、最も人気のある OXM テクノロジーに均一な抽象化を提供する Spring OXM に委譲することです。Spring OXM への依存はオプションであり、必要に応じて Spring Batch 固有のインターフェースを実装することを選択できます。OXM がサポートするテクノロジーとの関連を次のイメージに示します。

OXM Binding
図 2: OXM バインディング

OXM の概要と、XML フラグメントを使用してレコードを表現する方法を使用して、リーダーとライターをさらに詳しく調べることができます。

StaxEventItemReader

StaxEventItemReader 構成は、XML 入力ストリームからのレコードを処理するための一般的なセットアップを提供します。まず、StaxEventItemReader が処理できる次の XML レコードのセットを検討します。

<?xml version="1.0" encoding="UTF-8"?>
<records>
    <trade xmlns="https://springframework.org/batch/sample/io/oxm/domain">
        <isin>XYZ0001</isin>
        <quantity>5</quantity>
        <price>11.39</price>
        <customer>Customer1</customer>
    </trade>
    <trade xmlns="https://springframework.org/batch/sample/io/oxm/domain">
        <isin>XYZ0002</isin>
        <quantity>2</quantity>
        <price>72.99</price>
        <customer>Customer2c</customer>
    </trade>
    <trade xmlns="https://springframework.org/batch/sample/io/oxm/domain">
        <isin>XYZ0003</isin>
        <quantity>9</quantity>
        <price>99.99</price>
        <customer>Customer3</customer>
    </trade>
</records>

XML レコードを処理できるようにするには、次のものが必要です。

  • ルート要素名: マップされるオブジェクトを構成するフラグメントのルート要素の名前。構成例は、トレードの価値でこれを示しています。

  • リソース: 読み取るファイルを表す Spring リソース。

  • Unmarshaller: XML フラグメントをオブジェクトにマッピングするために Spring OXM によって提供されるアンマーシャリング機能。

  • Java

  • XML

次の例は、trade という名前のルート要素、data/iosample/input/input.xml のリソース、Java で tradeMarshaller と呼ばれるアンマーシャラーと連携する StaxEventItemReader を定義する方法を示しています。

Java 構成
@Bean
public StaxEventItemReader itemReader() {
	return new StaxEventItemReaderBuilder<Trade>()
			.name("itemReader")
			.resource(new FileSystemResource("org/springframework/batch/item/xml/domain/trades.xml"))
			.addFragmentRootElements("trade")
			.unmarshaller(tradeMarshaller())
			.build();

}

次の例は、trade という名前のルート要素、data/iosample/input/input.xml のリソース、XML で tradeMarshaller と呼ばれるアンマーシャラーと連携する StaxEventItemReader を定義する方法を示しています。

XML 構成
<bean id="itemReader" class="org.springframework.batch.item.xml.StaxEventItemReader">
    <property name="fragmentRootElementName" value="trade" />
    <property name="resource" value="org/springframework/batch/item/xml/domain/trades.xml" />
    <property name="unmarshaller" ref="tradeMarshaller" />
</bean>

この例では、XStreamMarshaller を使用することを選択したことに注意してください。これは、マップとして渡されたエイリアスを受け入れ、最初のキーと値はフラグメントの名前(つまり、ルート要素)とバインドするオブジェクト型です。次に、FieldSet と同様に、オブジェクト型内のフィールドにマップされる他の要素の名前は、マップ内のキーと値のペアとして記述されます。構成ファイルでは、Spring 構成ユーティリティを使用して必要なエイリアスを記述することができます。

  • Java

  • XML

次の例は、Java でエイリアスを記述する方法を示しています。

Java 構成
@Bean
public XStreamMarshaller tradeMarshaller() {
	Map<String, Class> aliases = new HashMap<>();
	aliases.put("trade", Trade.class);
	aliases.put("price", BigDecimal.class);
	aliases.put("isin", String.class);
	aliases.put("customer", String.class);
	aliases.put("quantity", Long.class);

	XStreamMarshaller marshaller = new XStreamMarshaller();

	marshaller.setAliases(aliases);

	return marshaller;
}

次の例は、エイリアスを XML で記述する方法を示しています。

XML 構成
<bean id="tradeMarshaller"
      class="org.springframework.oxm.xstream.XStreamMarshaller">
    <property name="aliases">
        <util:map id="aliases">
            <entry key="trade"
                   value="org.springframework.batch.samples.domain.trade.Trade" />
            <entry key="price" value="java.math.BigDecimal" />
            <entry key="isin" value="java.lang.String" />
            <entry key="customer" value="java.lang.String" />
            <entry key="quantity" value="java.lang.Long" />
        </util:map>
    </property>
</bean>

入力時に、リーダーは新しいフラグメントが開始されようとしていることを認識するまで XML リソースを読み取ります。デフォルトでは、リーダーは要素名を照合して、新しいフラグメントが開始されようとしていることを認識します。リーダーは、フラグメントからスタンドアロン XML ドキュメントを作成し、そのドキュメントをデシリアライザー(通常は Spring OXM Unmarshaller のラッパー)に渡して、XML を Java オブジェクトにマッピングします。

要約すると、この手順は、Spring 構成によって提供されるインジェクションを使用する次の Java コードに類似しています。

StaxEventItemReader<Trade> xmlStaxEventItemReader = new StaxEventItemReader<>();
Resource resource = new ByteArrayResource(xmlResource.getBytes());

Map aliases = new HashMap();
aliases.put("trade","org.springframework.batch.samples.domain.trade.Trade");
aliases.put("price","java.math.BigDecimal");
aliases.put("customer","java.lang.String");
aliases.put("isin","java.lang.String");
aliases.put("quantity","java.lang.Long");
XStreamMarshaller unmarshaller = new XStreamMarshaller();
unmarshaller.setAliases(aliases);
xmlStaxEventItemReader.setUnmarshaller(unmarshaller);
xmlStaxEventItemReader.setResource(resource);
xmlStaxEventItemReader.setFragmentRootElementName("trade");
xmlStaxEventItemReader.open(new ExecutionContext());

boolean hasNext = true;

Trade trade = null;

while (hasNext) {
    trade = xmlStaxEventItemReader.read();
    if (trade == null) {
        hasNext = false;
    }
    else {
        System.out.println(trade);
    }
}

StaxEventItemWriter

出力は入力に対して対称的に機能します。StaxEventItemWriter には、Resource、マーシャラー、rootTagName が必要です。Java オブジェクトはマーシャラー(通常は標準の Spring OXM マーシャラー)に渡されます。マーシャラーは、OXM ツールによってフラグメントごとに生成された StartDocument および EndDocument イベントをフィルター処理するカスタムイベントライターを使用して Resource に書き込みます。

  • Java

  • XML

次の Java の例では、MarshallingEventWriterSerializer を使用しています。

Java 構成
@Bean
public StaxEventItemWriter itemWriter(Resource outputResource) {
	return new StaxEventItemWriterBuilder<Trade>()
			.name("tradesWriter")
			.marshaller(tradeMarshaller())
			.resource(outputResource)
			.rootTagName("trade")
			.overwriteOutput(true)
			.build();

}

次の XML の例では、MarshallingEventWriterSerializer を使用しています。

XML 構成
<bean id="itemWriter" class="org.springframework.batch.item.xml.StaxEventItemWriter">
    <property name="resource" ref="outputResource" />
    <property name="marshaller" ref="tradeMarshaller" />
    <property name="rootTagName" value="trade" />
    <property name="overwriteOutput" value="true" />
</bean>

上記の構成では、3 つの必須プロパティを設定し、既存のファイルを上書きできるかどうかを指定するためにこの章で前述したオプションの overwriteOutput=true 属性を設定します。

  • Java

  • XML

次の Java の例では、この章で前述した読み取りの例で使用したものと同じマーシャラーを使用しています。

Java 構成
@Bean
public XStreamMarshaller customerCreditMarshaller() {
	XStreamMarshaller marshaller = new XStreamMarshaller();

	Map<String, Class> aliases = new HashMap<>();
	aliases.put("trade", Trade.class);
	aliases.put("price", BigDecimal.class);
	aliases.put("isin", String.class);
	aliases.put("customer", String.class);
	aliases.put("quantity", Long.class);

	marshaller.setAliases(aliases);

	return marshaller;
}

次の XML の例では、この章で前述した読み取りの例で使用したものと同じマーシャラーを使用しています。

XML 構成
<bean id="customerCreditMarshaller"
      class="org.springframework.oxm.xstream.XStreamMarshaller">
    <property name="aliases">
        <util:map id="aliases">
            <entry key="customer"
                   value="org.springframework.batch.samples.domain.trade.Trade" />
            <entry key="price" value="java.math.BigDecimal" />
            <entry key="isin" value="java.lang.String" />
            <entry key="customer" value="java.lang.String" />
            <entry key="quantity" value="java.lang.Long" />
        </util:map>
    </property>
</bean>

Java の例をまとめると、次のコードは、説明したすべてのポイントを示しており、必要なプロパティのプログラムによる設定を示しています。

FileSystemResource resource = new FileSystemResource("data/outputFile.xml")

Map aliases = new HashMap();
aliases.put("trade","org.springframework.batch.samples.domain.trade.Trade");
aliases.put("price","java.math.BigDecimal");
aliases.put("customer","java.lang.String");
aliases.put("isin","java.lang.String");
aliases.put("quantity","java.lang.Long");
Marshaller marshaller = new XStreamMarshaller();
marshaller.setAliases(aliases);

StaxEventItemWriter staxItemWriter =
	new StaxEventItemWriterBuilder<Trade>()
				.name("tradesWriter")
				.marshaller(marshaller)
				.resource(resource)
				.rootTagName("trade")
				.overwriteOutput(true)
				.build();

staxItemWriter.afterPropertiesSet();

ExecutionContext executionContext = new ExecutionContext();
staxItemWriter.open(executionContext);
Trade trade = new Trade();
trade.setPrice(11.39);
trade.setIsin("XYZ0001");
trade.setQuantity(5L);
trade.setCustomer("Customer1");
staxItemWriter.write(trade);