XML 项目读取器和写入器

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

流式 XML 的约束

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

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

XML Input
图 1. XML 输入

在上面的场景中,'trade' 标签被定义为 '根元素'。'<trade>' 和 '</trade>' 之间的所有内容都被视为一个 '片段'。Spring Batch 使用对象/XML 映射 (OXM) 将片段绑定到对象。但是,Spring Batch 不绑定到任何特定的 XML 绑定技术。典型用法是委托给 Spring OXM,它为最流行的 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 记录,需要以下内容

  • 根元素名称:构成要映射的对象的片段的根元素的名称。示例配置使用 trade 的值来演示这一点。

  • 资源:表示要读取的文件的 Spring 资源。

  • Unmarshaller:由 Spring OXM 提供的用于将 XML 片段映射到对象的解组工具。

  • Java

  • XML

以下示例展示了如何定义一个 StaxEventItemReader,它使用名为 trade 的根元素、名为 data/iosample/input/input.xml 的资源和名为 tradeMarshaller 的解组器在 Java 中工作

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

}

以下示例展示了如何定义一个 StaxEventItemReader,它使用名为 trade 的根元素、名为 data/iosample/input/input.xml 的资源和名为 tradeMarshaller 的解组器在 XML 中工作

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

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

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 序列化器),该序列化器使用自定义事件写入器将数据写入 Resource,该写入器会过滤掉 OXM 工具为每个片段生成的 StartDocumentEndDocument 事件。

  • 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>

前面的配置设置了三个必需的属性,并设置了可选的 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);