FlatFileItemWriter
写入平面文件具有与从文件中读取时必须克服的相同问题。必须能够以事务方式写入定界或固定长度格式。
Writing out to flat files has the same problems and issues that reading in from a file must overcome. A step must be able to write either delimited or fixed length formats in a transactional manner.
LineAggregator
正如 LineTokenizer
接口对于获取一项并将其转换为 String
是必需的,文件写入必须能够将多个字段聚合到一个字符串中以写入文件。在 Spring Batch 中,这是 LineAggregator
,如下面的接口定义所示:
Just as the LineTokenizer
interface is necessary to take an item and turn it into a
String
, file writing must have a way to aggregate multiple fields into a single string
for writing to a file. In Spring Batch, this is the LineAggregator
, shown in the
following interface definition:
public interface LineAggregator<T> {
public String aggregate(T item);
}
LineAggregator
是 LineTokenizer
的逻辑反义词。LineTokenizer
获取 String
并返回 FieldSet
,而 LineAggregator
获取 item
并返回 String
。
The LineAggregator
is the logical opposite of LineTokenizer
. LineTokenizer
takes a
String
and returns a FieldSet
, whereas LineAggregator
takes an item
and returns a
String
.
PassThroughLineAggregator
LineAggregator
接口的最基本实现是 PassThroughLineAggregator
,该实现假设该对象已经是字符串或其字符串表示形式可以接受写入,如下面的代码所示:
The most basic implementation of the LineAggregator
interface is the
PassThroughLineAggregator
, which assumes that the object is already a string or that
its string representation is acceptable for writing, as shown in the following code:
public class PassThroughLineAggregator<T> implements LineAggregator<T> {
public String aggregate(T item) {
return item.toString();
}
}
如果需要直接控制创建字符串,但需要 FlatFileItemWriter
的优点(如事务和重启支持),那么前面的实现很有用。
The preceding implementation is useful if direct control of creating the string is
required but the advantages of a FlatFileItemWriter
, such as transaction and restart
support, are necessary.
Simplified File Writing Example
现在已定义 LineAggregator
接口及其最基本的实现 PassThroughLineAggregator
,因此可以解释写入的基本流程:
Now that the LineAggregator
interface and its most basic implementation,
PassThroughLineAggregator
, have been defined, the basic flow of writing can be
explained:
-
The object to be written is passed to the
LineAggregator
in order to obtain aString
. -
The returned
String
is written to the configured file.
以下来自 FlatFileItemWriter
的摘录以代码表示:
The following excerpt from the FlatFileItemWriter
expresses this in code:
public void write(T item) throws Exception {
write(lineAggregator.aggregate(item) + LINE_SEPARATOR);
}
- Java
-
在 Java 中,简单的配置示例如下所示:
In Java, a simple example of configuration might look like the following:
@Bean
public FlatFileItemWriter itemWriter() {
return new FlatFileItemWriterBuilder<Foo>()
.name("itemWriter")
.resource(new FileSystemResource("target/test-outputs/output.txt"))
.lineAggregator(new PassThroughLineAggregator<>())
.build();
}
- XML
-
在 XML 中,简单的配置示例如下所示:
In XML, a simple example of configuration might look like the following:
<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
的大多数用户都有需要写入后再转换成行的领域对象。在文件读取中,需要以下内容:
The preceding example may be useful for the most basic uses of a writing to a file.
However, most users of the FlatFileItemWriter
have a domain object that needs to be
written out and, thus, must be converted into a line. In file reading, the following was
required:
-
Read one line from the file.
-
Pass the line into the
LineTokenizer#tokenize()
method, in order to retrieve aFieldSet
. -
Pass the
FieldSet
returned from tokenizing to aFieldSetMapper
, returning the result from theItemReader#read()
method.
文件写入有类似但相反的步骤:
File writing has similar but inverse steps:
-
Pass the item to be written to the writer.
-
Convert the fields on the item into an array.
-
Aggregate the resulting array into a line.
因为该框架无法知道需要从该对象中写入哪些字段,所以必须编写 FieldExtractor
来完成将项转换为数组的任务,如下面的接口定义所示:
Because there is no way for the framework to know which fields from the object need to
be written out, a FieldExtractor
must be written to accomplish the task of turning the
item into an array, as shown in the following interface definition:
public interface FieldExtractor<T> {
Object[] extract(T item);
}
FieldExtractor
接口的实现应从给定对象的字段创建一个数组,然后可以用元素之间的定界符或作为固定宽度的行的一部分将其写出。
Implementations of the FieldExtractor
interface should create an array from the fields
of the provided object, which can then be written out with a delimiter between the
elements or as part of a fixed-width line.
PassThroughFieldExtractor
在很多情况下,需要写出集合,如数组、Collection
或 FieldSet
。从这些集合类型中“抽取”数组非常简单。为此,将该集合转换为数组。因此,在此场景中应使用 PassThroughFieldExtractor
。应注意,如果传递的对象不是集合类型,则 PassThroughFieldExtractor
返回仅包含要提取项的数组。
There are many cases where a collection, such as an array, Collection
, or FieldSet
,
needs to be written out. "Extracting" an array from one of these collection types is very
straightforward. To do so, convert the collection to an array. Therefore, the
PassThroughFieldExtractor
should be used in this scenario. It should be noted that, if
the object passed in is not a type of collection, then the PassThroughFieldExtractor
returns an array containing solely the item to be extracted.
BeanWrapperFieldExtractor
与文件读取部分中描述的 BeanWrapperFieldSetMapper
一样,通常最好配置如何将域对象转换为对象数组,而不是自己编写转换。BeanWrapperFieldExtractor
提供此功能,如下面的示例所示:
As with the BeanWrapperFieldSetMapper
described in the file reading section, it is
often preferable to configure how to convert a domain object to an object array, rather
than writing the conversion yourself. The BeanWrapperFieldExtractor
provides this
functionality, as shown in the following example:
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]);
此提取器实现仅有一个必需的属性:要映射的字段的名称。正如 BeanWrapperFieldSetMapper
需要字段名称才能将 FieldSet
上的字段映射到给定对象上的 setter,BeanWrapperFieldExtractor
需要名称才能映射到 getter 以创建对象数组。值得注意的是,名称的顺序决定了数组中字段的顺序。
This extractor implementation has only one required property: the names of the fields to
map. Just as the BeanWrapperFieldSetMapper
needs field names to map fields on the
FieldSet
to setters on the provided object, the BeanWrapperFieldExtractor
needs names
to map to getters for creating an object array. It is worth noting that the order of the
names determines the order of the fields within the array.
Delimited File Writing Example
最基本的平面文件格式是其中所有字段都由定界符分隔的格式。这可以用 DelimitedLineAggregator
实现。以下示例写出了一个简单的表示为客户帐户贷项的域对象:
The most basic flat file format is one in which all fields are separated by a delimiter.
This can be accomplished using a DelimitedLineAggregator
. The following example writes
out a simple domain object that represents a credit to a customer account:
public class CustomerCredit {
private int id;
private String name;
private BigDecimal credit;
//getters and setters removed for clarity
}
因为正在使用域对象,所以必须提供 FieldExtractor
接口的实现,以及要使用的定界符。
Because a domain object is being used, an implementation of the FieldExtractor
interface must be provided, along with the delimiter to use.
- Java
-
以下示例演示了如何在 Java 中使用带定界符的
FieldExtractor
:
The following example shows how to use the FieldExtractor
with a delimiter in Java:
@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
-
以下示例演示了如何在 XML 中使用带定界符的
FieldExtractor
:
The following example shows how to use the FieldExtractor
with a delimiter in XML:
<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
中的名称和信用字段转换为对象数组,然后在每个字段之间用逗号将该数组写出。
In the previous example, the BeanWrapperFieldExtractor
described earlier in this
chapter is used to turn the name and credit fields within CustomerCredit
into an object
array, which is then written out with commas between each field.
- Java
-
你也可以使用
FlatFileItemWriterBuilder.DelimitedBuilder
自动创建BeanWrapperFieldExtractor
和DelimitedLineAggregator
,如下面的示例所示:
It is also possible to use the FlatFileItemWriterBuilder.DelimitedBuilder
to
automatically create the BeanWrapperFieldExtractor
and DelimitedLineAggregator
as shown in the following example:
@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();
}
- XML
-
在 XML 中没有使用
FlatFileItemWriterBuilder
的等效示例。
There is no XML equivalent of using FlatFileItemWriterBuilder
.
Fixed Width File Writing Example
定界并不是平面文件格式的唯一类型。许多人更喜欢对每列使用设定宽度来分隔字段,这通常称为“固定宽度”。Spring Batch 在文件写入中使用 FormatterLineAggregator
支持这样做。
Delimited is not the only type of flat file format. Many prefer to use a set width for
each column to delineate between fields, which is usually referred to as 'fixed width'.
Spring Batch supports this in file writing with the FormatterLineAggregator
.
- Java
-
使用上面描述的相同
CustomerCredit
域对象,可以在 Java 中对其配置如下:
Using the same CustomerCredit
domain object described above, it can be configured as
follows in 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();
}
- XML
-
使用上面描述的相同
CustomerCredit
域对象,可以在 XML 中对其配置如下:
Using the same CustomerCredit
domain object described above, it can be configured as
follows in 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>
前面的示例大部分应看起来很熟悉。但是,“格式”属性的值是新的。
Most of the preceding example should look familiar. However, the value of the format property is new.
- Java
-
以下示例演示了 Java 中的格式属性:
The following example shows the format property in Java:
...
FormatterLineAggregator<CustomerCredit> lineAggregator = new FormatterLineAggregator<>();
lineAggregator.setFormat("%-9s%-2.0f");
...
- XML
-
以下示例演示了 XML 中的格式属性:
The following example shows the format property in XML:
<property name="format" value="%-9s%-2.0f" />
底层实现使用添加为 Java 5 一部分的相同 `Formatter`构建。Java `Formatter`基于 C 编程语言的 `printf`功能。在 Formatter的 Javadoc 中可以找到有关如何配置格式化程序的大多数详细信息。
The underlying implementation is built using the same
Formatter
added as part of Java 5. The Java
Formatter
is based on the
printf
functionality of the C programming
language. Most details on how to configure a formatter can be found in
the Javadoc of Formatter.
- Java
-
也可以使用
FlatFileItemWriterBuilder.FormattedBuilder
自动创建BeanWrapperFieldExtractor
和FormatterLineAggregator
,如下例所示:
It is also possible to use the FlatFileItemWriterBuilder.FormattedBuilder
to
automatically create the BeanWrapperFieldExtractor
and FormatterLineAggregator
as shown in following example:
@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();
}
- XML
Handling File Creation
FlatFileItemReader
与文件资源之间的关系非常简单。当读取器初始化时,它将打开文件(如果存在),并且如果文件不存在,它将引发异常。文件写入并不那么简单。乍一看,似乎 FlatFileItemWriter
应该存在类似的简单的契约:如果文件已存在,则引发异常;如果文件不存在,则创建文件并开始写入。但是,重新启动 Job
可能会导致问题。在正常重新启动场景中,契约相反:如果文件存在,则从最后已知的好位置开始向其中写入;如果文件不存在,则引发异常。但是,如果此作业的文件名始终相同,将会发生什么?在这种情况下,如果这不是重新启动,您将希望删除该文件(如果存在)。由于这种可能性,FlatFileItemWriter
包含属性 shouldDeleteIfExists
。将此属性设置为 true 将导致在打开写入器时,删除具有相同名称的现有文件。
FlatFileItemReader
has a very simple relationship with file resources. When the reader
is initialized, it opens the file (if it exists), and throws an exception if it does not.
File writing isn’t quite so simple. At first glance, it seems like a similar
straightforward contract should exist for FlatFileItemWriter
: If the file already
exists, throw an exception, and, if it does not, create it and start writing. However,
potentially restarting a Job
can cause issues. In normal restart scenarios, the
contract is reversed: If the file exists, start writing to it from the last known good
position, and, if it does not, throw an exception. However, what happens if the file name
for this job is always the same? In this case, you would want to delete the file if it
exists, unless it’s a restart. Because of this possibility, the FlatFileItemWriter
contains the property, shouldDeleteIfExists
. Setting this property to true causes an
existing file with the same name to be deleted when the writer is opened.