FlatFileItemWriter

寫入平面檔案與從檔案讀取具有相同的問題。一個步驟必須能夠以事務方式寫入分隔符或固定長度格式。

LineAggregator

正如 LineTokenizer 介面對於將項轉換為 String 是必需的,檔案寫入也必須有一種方法將多個欄位聚合為單個字串以寫入檔案。在 Spring Batch 中,這就是 LineAggregator,其介面定義如下

public interface LineAggregator<T> {

    public String aggregate(T item);

}

LineAggregatorLineTokenizer 邏輯相反。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,可以解釋寫入的基本流程

  1. 要寫入的物件被傳遞給 LineAggregator 以獲取 String

  2. 返回的 String 被寫入到配置的檔案中。

FlatFileItemWriter 的以下摘錄用程式碼表達了這一點

public void write(T item) throws Exception {
    write(lineAggregator.aggregate(item) + LINE_SEPARATOR);
}
  • Java

  • XML

在 Java 中,一個簡單的配置示例可能如下所示

Java 配置
@Bean
public FlatFileItemWriter itemWriter() {
	return  new FlatFileItemWriterBuilder<Foo>()
           			.name("itemWriter")
           			.resource(new FileSystemResource("target/test-outputs/output.txt"))
           			.lineAggregator(new PassThroughLineAggregator<>())
           			.build();
}

在 XML 中,一個簡單的配置示例可能如下所示

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 的大多數使用者都有一個需要寫入的域物件,因此必須將其轉換為一行。在檔案讀取中,需要以下內容

  1. 從檔案中讀取一行。

  2. 將該行傳遞給 LineTokenizer#tokenize() 方法,以檢索 FieldSet

  3. 將標記化返回的 FieldSet 傳遞給 FieldSetMapper,返回 ItemReader#read() 方法的結果。

檔案寫入有類似但相反的步驟

  1. 將要寫入的項傳遞給寫入器。

  2. 將專案中的欄位轉換為陣列。

  3. 將生成的陣列聚合為一行。

由於框架無法知道物件中的哪些欄位需要寫入,因此必須編寫 FieldExtractor 來完成將項轉換為陣列的任務,如以下介面定義所示

public interface FieldExtractor<T> {

    Object[] extract(T item);

}

FieldExtractor 介面的實現應該從提供的物件的欄位建立陣列,然後可以使用元素之間的分隔符或作為固定寬度行的一部分寫入。

PassThroughFieldExtractor

在許多情況下,需要寫入集合,例如陣列、CollectionFieldSet。“提取”其中一種集合型別的陣列非常簡單。為此,將集合轉換為陣列。因此,在這種情況下應使用 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 中的欄位對映到所提供物件上的設定器一樣,BeanWrapperFieldExtractor 需要名稱來對映到獲取器以建立物件陣列。值得注意的是,名稱的順序決定了陣列中欄位的順序。

分隔檔案寫入示例

最基本的平面檔案格式是所有欄位都由分隔符分隔的格式。這可以透過使用 DelimitedLineAggregator 來實現。以下示例寫入一個表示客戶賬戶信用的簡單域物件

public class CustomerCredit {

    private int id;
    private String name;
    private BigDecimal credit;

    //getters and setters removed for clarity
}

由於正在使用域物件,因此必須提供 FieldExtractor 介面的實現,以及要使用的分隔符。

  • Java

  • XML

以下示例顯示瞭如何在 Java 中使用帶有分隔符的 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();
}

以下示例顯示瞭如何在 XML 中使用帶有分隔符的 FieldExtractor

XML 配置
<bean id="itemWriter" class="org.springframework.batch.infrastructure.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 自動建立 BeanWrapperFieldExtractorDelimitedLineAggregator,如以下示例所示

Java 配置
@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();
}

使用 FlatFileItemWriterBuilder 沒有 XML 等效項。

固定寬度檔案寫入示例

分隔符不是平面檔案格式的唯一型別。許多人喜歡為每列使用固定寬度來區分欄位,這通常稱為“固定寬度”。Spring Batch 透過 FormatterLineAggregator 支援檔案寫入中的這種方式。

  • Java

  • XML

使用上面描述的相同 CustomerCredit 域物件,它可以在 Java 中按如下方式配置

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 域物件,它可以在 XML 中按如下方式配置

XML 配置
<bean id="itemWriter" class="org.springframework.batch.infrastructure.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 中的格式屬性

...
FormatterLineAggregator<CustomerCredit> lineAggregator = new FormatterLineAggregator<>();
lineAggregator.setFormat("%-9s%-2.0f");
...

以下示例顯示了 XML 中的格式屬性

<property name="format" value="%-9s%-2.0f" />

底層實現是使用 Java 5 中新增的相同 Formatter 構建的。Java Formatter 基於 C 程式語言的 printf 功能。有關如何配置格式化程式的大多數詳細資訊可以在 Formatter 的 Javadoc 中找到。

  • Java

  • XML

也可以使用 FlatFileItemWriterBuilder.FormattedBuilder 自動建立 BeanWrapperFieldExtractorFormatterLineAggregator,如以下示例所示

Java 配置
@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 可能會導致問題。在正常的重新啟動場景中,契約是反向的:如果檔案存在,則從上次已知的好位置開始寫入;如果不存在,則丟擲異常。但是,如果此作業的檔名始終相同,會發生什麼?在這種情況下,除非是重新啟動,否則您將希望刪除檔案(如果存在)。由於這種可能性,FlatFileItemWriter 包含屬性 shouldDeleteIfExists。將此屬性設定為 true 會導致在開啟寫入器時刪除具有相同名稱的現有檔案。

© . This site is unofficial and not affiliated with VMware.