XML Item Readers and Writers

Spring Batch 提供了事务基础设施,用于读取 XML 记录并将其映射到 Java 对象,以及写入 Java 对象作为 XML 记录。

Spring Batch provides transactional infrastructure for both reading XML records and mapping them to Java objects as well as writing Java objects as XML records.

Constraints on streaming XML

StAX API 用于 I/O,因为其他标准 XML 解析 API 不符合批处理要求(DOM 一次性将整个输入加载到内存中,而 SAX 通过允许用户仅提供回调函数来控制解析进程)。

The StAX API is used for I/O, as other standard XML parsing APIs do not fit batch processing requirements (DOM loads the whole input into memory at once and SAX controls the parsing process by allowing the user to provide only callbacks).

我们需要考虑在 Spring Batch 中 XML 输入和输出是如何工作的。首先,有一些概念与文件读取和写入不同,但它们在 Spring Batch XML 处理中很常见。在 XML 处理中,假设 XML 资源是分别对应各个记录的“片段”的集合,而不是需要标记化的记录行(FieldSet 实例),如下图所示:

We need to consider how XML input and output works in Spring Batch. First, there are a few concepts that vary from file reading and writing but are common across Spring Batch XML processing. With XML processing, instead of lines of records (FieldSet instances) that need to be tokenized, it is assumed an XML resource is a collection of 'fragments' corresponding to individual records, as shown in the following image: .XML Input image::xmlinput.png[]

在此场景中,“trade”标记被定义为“根元素”。“<trade>”和 “</trade>”之间的所有内容都被视为一个“片段”。Spring Batch 使用对象/XML 映射 (OXM) 将片段绑定到对象。然而,Spring Batch 并不依赖于任何特定的 XML 绑定技术。典型的使用是委托给 Spring OXM,它为最流行的 OXM 技术提供统一的抽象。依赖于 Spring OXM 是可选的,您可以选择实现 Spring Batch 特定的接口(如果需要的话)。与 OXM 支持的技术之间的关系在以下图片中展示:

The 'trade' tag is defined as the 'root element' in the scenario above. Everything between '<trade>' and '</trade>' is considered one 'fragment'. Spring Batch uses Object/XML Mapping (OXM) to bind fragments to objects. However, Spring Batch is not tied to any particular XML binding technology. Typical use is to delegate to Spring OXM, which provides uniform abstraction for the most popular OXM technologies. The dependency on Spring OXM is optional and you can choose to implement Spring Batch specific interfaces if desired. The relationship to the technologies that OXM supports is shown in the following image: .OXM Binding image::oxm-fragments.png[]

介绍了 OXM 以及如何使用 XML 片段表示记录之后,我们现在可以更仔细地检查读取器和写入器。

With an introduction to OXM and how one can use XML fragments to represent records, we can now more closely examine readers and writers.

StaxEventItemReader

StaxEventItemReader 配置为从 XML 输入流中处理记录提供了一个典型设置。首先,考虑 StaxEventItemReader 可以处理的以下一组 XML 记录:

The StaxEventItemReader configuration provides a typical setup for the processing of records from an XML input stream. First, consider the following set of XML records that the StaxEventItemReader can process:

<?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 记录,需要满足以下条件:

To be able to process the XML records, the following is needed:

  • Root Element Name: The name of the root element of the fragment that constitutes the object to be mapped. The example configuration demonstrates this with the value of trade.

  • Resource: A Spring Resource that represents the file to read.

  • Unmarshaller: An unmarshalling facility provided by Spring OXM for mapping the XML fragment to an object.

Java

以下示例展示了如何使用名为 trade 的根元素、资源 data/iosample/input/input.xml 和 unmarshaller 在 Java 中定义 StaxEventItemReader

The following example shows how to define a StaxEventItemReader that works with a root element named trade, a resource of data/iosample/input/input.xml, and an unmarshaller called tradeMarshaller in Java:

Java Configuration
@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();

}
XML

以下示例展示了如何使用名为 trade 的根元素、资源 data/iosample/input/input.xml 和 unmarshaller 在 XML 中定义 StaxEventItemReader

The following example shows how to define a StaxEventItemReader that works with a root element named trade, a resource of data/iosample/input/input.xml, and an unmarshaller called tradeMarshaller in XML:

XML Configuration
<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 配置实用程序来描述必需的别名。

Note that, in this example, we have chosen to use an XStreamMarshaller, which accepts an alias passed in as a map with the first key and value being the name of the fragment (that is, a root element) and the object type to bind. Then, similar to a FieldSet, the names of the other elements that map to fields within the object type are described as key/value pairs in the map. In the configuration file, we can use a Spring configuration utility to describe the required alias.

Java

以下示例展示了如何在 Java 中描述别名:

The following example shows how to describe the alias in Java:

Java Configuration
@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 中描述别名:

The following example shows how to describe the alias in XML:

XML Configuration
<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 对象。

On input, the reader reads the XML resource until it recognizes that a new fragment is about to start. By default, the reader matches the element name to recognize that a new fragment is about to start. The reader creates a standalone XML document from the fragment and passes the document to a deserializer (typically a wrapper around a Spring OXM Unmarshaller) to map the XML to a Java object.

总之,此过程类似于以下 Java 代码,它使用了 Spring 配置提供的注入:

In summary, this procedure is analogous to the following Java code, which uses the injection provided by the Spring configuration:

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、一个 marshaller 和一个 rootTagName。一个 Java 对象被传递给一个 marshaller(通常是一个标准 Spring OXM Marshaller),它使用一个自定义事件写入器来将对象写入 Resource,该写入器会过滤由 OXM 工具为每个片段生成的 StartDocumentEndDocument 事件。

Output works symmetrically to input. The StaxEventItemWriter needs a Resource, a marshaller, and a rootTagName. A Java object is passed to a marshaller (typically a standard Spring OXM Marshaller) which writes to a Resource by using a custom event writer that filters the StartDocument and EndDocument events produced for each fragment by the OXM tools.

Java

以下 Java 示例使用了 MarshallingEventWriterSerializer

The following Java example uses the MarshallingEventWriterSerializer:

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

}
XML

以下 XML 示例使用了 MarshallingEventWriterSerializer

The following XML example uses the MarshallingEventWriterSerializer:

XML Configuration
<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>

前面的配置设置了三个必需属性,并设置了本章之前提到的可选属性 overwriteOutput=true 以指定是否可以覆盖现有文件。

The preceding configuration sets up the three required properties and sets the optional overwriteOutput=true attrbute, mentioned earlier in this chapter for specifying whether an existing file can be overwritten.

Java

以下 Java 示例使用与本章前面显示的读取示例中使用的相同编组器:

The following Java example uses the same marshaller as the one used in the reading example shown earlier in the chapter:

Java Configuration
@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 示例使用与本章前面显示的读取示例中使用的相同编组器:

The following XML example uses the same marshaller as the one used in the reading example shown earlier in the chapter:

XML Configuration
<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 示例代码总结说明所有要点,展示必需属性的编程设置:

To summarize with a Java example, the following code illustrates all of the points discussed, demonstrating the programmatic setup of the required properties:

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);