FlatFileItemWriter
写入平面文件与从文件读入具有相同的问题和问题必须克服。一个步骤必须能够以事务方式编写分隔或固定长度格式。
LineAggregator
就像LineTokenizer
接口是将项目转换为String
,文件写入必须有一种方法将多个字段聚合到一个字符串中用于写入文件。在 Spring Batch 中,这是LineAggregator
,显示在以下接口定义中:
public interface LineAggregator<T> {
public String aggregate(T item);
}
这LineAggregator
是逻辑上的相反LineTokenizer
.LineTokenizer
需要一个String
并返回一个FieldSet
而LineAggregator
采用item
并返回一个String
.
PassThroughLineAggregator
最基本的实现LineAggregator
接口是PassThroughLineAggregator
,这假设对象已经是一个字符串,或者它的字符串表示形式可以接受写入,如以下代码所示:
public class PassThroughLineAggregator<T> implements LineAggregator<T> {
public String aggregate(T item) {
return item.toString();
}
}
如果直接控制创建字符串,则前面的实现很有用但FlatFileItemWriter
,例如事务和重启支持,都是必要的。
简化的文件写入示例
现在LineAggregator
接口及其最基本的实现,PassThroughLineAggregator
,已经定义了,写作的基本流程可以 解释:
-
要写入的对象被传递给
LineAggregator
为了获得String
. -
返回的
String
写入配置的文件。
以下摘录自FlatFileItemWriter
在代码中表达了这一点:
public void write(T item) throws Exception {
write(lineAggregator.aggregate(item) + LINE_SEPARATOR);
}
-
Java
-
XML
在 Java 中,一个简单的配置示例可能如下所示:
@Bean
public FlatFileItemWriter itemWriter() {
return new FlatFileItemWriterBuilder<Foo>()
.name("itemWriter")
.resource(new FileSystemResource("target/test-outputs/output.txt"))
.lineAggregator(new PassThroughLineAggregator<>())
.build();
}
在 XML 中,一个简单的配置示例可能如下所示:
<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
有一个需要写出来的域对象,因此必须转换为一行。在文件读取中,以下是 必填:
-
从文件中读取一行。
-
将行传递到
LineTokenizer#tokenize()
方法,以便检索FieldSet
. -
将
FieldSet
从标记化返回到FieldSetMapper
,返回结果ItemReader#read()
方法。
文件写入有类似但相反的步骤:
-
将要写入的项目传递给编写器。
-
将项目上的字段转换为数组。
-
将生成的数组聚合成一行。
因为框架无法知道对象中的哪些字段需要被写出来,所以一个FieldExtractor
必须编写才能完成将item 转换为数组的任务,如以下接口定义所示:
public interface FieldExtractor<T> {
Object[] extract(T item);
}
的实现FieldExtractor
接口应从字段创建一个数组提供的对象,然后可以在元素之间使用分隔符将其写出来元素或作为固定宽度线的一部分。
PassThroughFieldExtractor
在许多情况下,集合(例如数组)Collection
或FieldSet
, 需要写出来。从这些集合类型之一中“提取”一个数组是非常 简单。 为此,请将集合转换为数组。因此,PassThroughFieldExtractor
应该在这种情况下使用。应该注意的是,如果传入的对象不是一种集合类型,那么PassThroughFieldExtractor
返回一个仅包含要提取的项的数组。
BeanWrapperFieldExtractor
与BeanWrapperFieldSetMapper
在文件读取部分中描述的是
通常最好配置如何将域对象转换为对象数组,而不是
而不是自己编写转换。这BeanWrapperFieldExtractor
提供
功能,如以下示例所示:
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。值得注意的是,的顺序
名称确定数组中字段的顺序。
分隔文件写入示例
最基本的平面文件格式是所有字段都用分隔符分隔的格式。
这可以使用DelimitedLineAggregator
.以下示例将
输出一个简单的域对象,该对象表示客户帐户的信用额度:
public class CustomerCredit {
private int id;
private String name;
private BigDecimal credit;
//getters and setters removed for clarity
}
由于正在使用域对象,因此FieldExtractor
必须提供接口以及要使用的分隔符。
-
Java
-
XML
以下示例演示如何使用FieldExtractor
在 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();
}
以下示例演示如何使用FieldExtractor
在 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
变成一个对象
数组,然后在每个字段之间用逗号写出。
-
Java
-
XML
也可以使用FlatFileItemWriterBuilder.DelimitedBuilder
自
自动创建BeanWrapperFieldExtractor
和DelimitedLineAggregator
如以下示例所示:
@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 使用FlatFileItemWriterBuilder
.
固定宽度文件写入示例
分隔并不是唯一类型的平面文件格式。许多人更喜欢使用设定的宽度
每列在字段之间划分,这通常称为“固定宽度”。
Spring Batch 在文件写入中支持这一点,使用FormatterLineAggregator
.
-
Java
-
XML
使用相同的CustomerCredit
domain 对象,可以将其配置为
在 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();
}
使用相同的CustomerCredit
domain 对象,可以将其配置为
在 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>
前面的大多数示例应该看起来很熟悉。但是,格式的值 属性是新的。
-
Java
-
XML
以下示例显示了 Java 中的 format 属性:
...
FormatterLineAggregator<CustomerCredit> lineAggregator = new FormatterLineAggregator<>();
lineAggregator.setFormat("%-9s%-2.0f");
...
以下示例显示了 XML 中的 format 属性:
<property name="format" value="%-9s%-2.0f" />
底层实现是使用相同的Formatter
作为 Java 5 的一部分添加。爪哇Formatter
基于printf
C 编程的功能
语言。有关如何配置格式化程序的大多数详细信息,请访问
格式化程序的 Javadoc。
-
Java
-
XML
也可以使用FlatFileItemWriterBuilder.FormattedBuilder
自
自动创建BeanWrapperFieldExtractor
和FormatterLineAggregator
如以下示例所示:
@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();
}
处理文件创建
FlatFileItemReader
与文件资源的关系非常简单。当读者
初始化时,它会打开文件(如果存在),如果不存在,则抛出异常。
文件编写并不是那么简单。乍一看,这似乎是一个类似的
简单的合约应该存在于FlatFileItemWriter
:如果文件已经
存在,抛出异常,如果没有,则创建它并开始写入。然而
可能会重新启动Job
可能会导致问题。在正常重启场景中,
合约被反转:如果文件存在,则从最后一个已知的 Good 开始写入它
position,如果没有,则抛出异常。但是,如果文件名
因为这份工作总是一样的?在这种情况下,如果文件
存在,除非是重启。由于这种可能性,FlatFileItemWriter
包含属性shouldDeleteIfExists
.将此属性设置为 true 会导致
打开写入器时要删除的具有相同名称的现有文件。