跳到主要内容

XML 项读取器和写入器

QWen Plus 中英对照 XML Item Readers and Writers

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 输入

图1. XML 输入

trade 标签在上述场景中被定义为 “根元素”。\<trade>\</trade> 之间的一切内容被视为一个 “片段”(fragment)。Spring Batch 使用对象/XML 映射(OXM)将片段绑定到对象。然而,Spring Batch 并不依赖于任何特定的 XML 绑定技术。通常情况下,可以委托给 Spring OXM,它为最受欢迎的 OXM 技术提供了统一的抽象。对 Spring OXM 的依赖是可选的,如果需要,可以选择实现 Spring Batch 特定的接口。OXM 所支持的技术之间的关系在下图中显示:

OXM Binding

图2. OXM绑定

通过介绍OXM以及如何使用XML片段来表示记录,我们现在可以更详细地考察读取器和写入器。

StaxEventItemReader

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

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

为了能够处理 XML 记录,需要以下内容:

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

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

  • Unmarshaller:由 Spring OXM 提供的用于将 XML 片段映射到对象的反序列化工具。

下面的示例展示了如何定义一个 StaxEventItemReader,它与名为 trade 的根元素、资源为 data/iosample/input/input.xml 和名为 tradeMarshaller 的解码器配合使用(在 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();

}
java

请注意,在这个例子中,我们选择使用 XStreamMarshaller,它接受一个以映射形式传递的别名,其中第一个键和值分别是片段(即根元素)的名称和要绑定的对象类型。然后,类似于 FieldSet,映射到对象类型内部字段的其他元素的名称被描述为映射中的键/值对。在配置文件中,我们可以使用 Spring 配置工具来描述所需的别名。

下面的示例展示了如何在 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;
}
java

在输入时,读取器会读取 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);
}
}
java

StaxEventItemWriter

输出以对称于输入的方式工作。StaxEventItemWriter 需要一个 Resource、一个序列化器(marshaller)和一个 rootTagName。一个 Java 对象会被传递给序列化器(通常是标准的 Spring OXM Marshaller),它通过使用自定义事件写入器将数据写入到 Resource,该自定义事件写入器会过滤 OXM 工具为每个片段生成的 StartDocumentEndDocument 事件。

下面的 Java 示例使用了 MarshallingEventWriterSerializer

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

}
java

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

下面的 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;
}
java

为了用一个 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);
java