此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Batch 文档 5.1.2spring-doc.cn

此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Batch 文档 5.1.2spring-doc.cn

平面文件是最多包含二维(表格)数据的任何类型的文件。 名为 的类有助于在 Spring Batch 框架中读取平面文件,该类提供了读取和解析平面的基本功能 文件。的两个最重要的必需依赖项是 和 。该界面将在下一篇文章中详细介绍 部分。resource 属性表示 Spring Core 。文档 解释如何创建这种类型的 bean 可以在 Spring 中找到 框架,第 5 章。资源。因此,本指南不详细介绍 创建对象 除了显示以下简单示例之外:FlatFileItemReaderFlatFileItemReaderResourceLineMapperLineMapperResourceResourcespring-doc.cn

Resource resource = new FileSystemResource("resources/trades.csv");

在复杂的批处理环境中,目录结构通常由 Enterprise Application Integration (EAI) 管理 基础结构,其中为外部接口建立了放置区以移动文件 从 FTP 位置到批处理位置,反之亦然。文件移动实用程序 超出了 Spring Batch 架构的范围,但对于 Batch 来说并不罕见 Job Streams 将文件移动实用程序作为 Job Stream 中的步骤包含在内。批次 架构只需要知道如何找到要处理的文件。Spring Batch 从此起点开始将数据馈送到管道的过程。但是, Spring 集成提供了许多 这些类型的服务。spring-doc.cn

中的其他属性允许您进一步指定数据的 解释,如下表所述:FlatFileItemReaderspring-doc.cn

表 1. 性能FlatFileItemReader
财产 类型 描述

评论spring-doc.cn

字符串spring-doc.cn

指定指示注释行的行前缀。spring-doc.cn

编码spring-doc.cn

字符串spring-doc.cn

指定要使用的文本编码。默认值为 .UTF-8spring-doc.cn

线映射器spring-doc.cn

LineMapperspring-doc.cn

将 a 转换为表示项的 an。StringObjectspring-doc.cn

linesToSkip (行到 Skip)spring-doc.cn

intspring-doc.cn

文件顶部要忽略的行数。spring-doc.cn

recordSeparatorPolicy 的spring-doc.cn

RecordSeparatorPolicy 的spring-doc.cn

用于确定行尾的位置 并执行诸如 continue 之类的操作,以 if 结尾的行位于带引号的字符串内。spring-doc.cn

资源spring-doc.cn

Resourcespring-doc.cn

要从中读取的资源。spring-doc.cn

skippedLines回调spring-doc.cn

LineCallbackHandler 线回调处理程序spring-doc.cn

将 文件中要跳过的行。如果设置为 2,则此接口为 被叫了两次。linesToSkipspring-doc.cn

严格spring-doc.cn

布尔spring-doc.cn

在严格模式下,读取器会在 if 输入资源不存在。否则,它会记录问题并继续。ExecutionContextspring-doc.cn

表 1. 性能FlatFileItemReader
财产 类型 描述

评论spring-doc.cn

字符串spring-doc.cn

指定指示注释行的行前缀。spring-doc.cn

编码spring-doc.cn

字符串spring-doc.cn

指定要使用的文本编码。默认值为 .UTF-8spring-doc.cn

线映射器spring-doc.cn

LineMapperspring-doc.cn

将 a 转换为表示项的 an。StringObjectspring-doc.cn

linesToSkip (行到 Skip)spring-doc.cn

intspring-doc.cn

文件顶部要忽略的行数。spring-doc.cn

recordSeparatorPolicy 的spring-doc.cn

RecordSeparatorPolicy 的spring-doc.cn

用于确定行尾的位置 并执行诸如 continue 之类的操作,以 if 结尾的行位于带引号的字符串内。spring-doc.cn

资源spring-doc.cn

Resourcespring-doc.cn

要从中读取的资源。spring-doc.cn

skippedLines回调spring-doc.cn

LineCallbackHandler 线回调处理程序spring-doc.cn

将 文件中要跳过的行。如果设置为 2,则此接口为 被叫了两次。linesToSkipspring-doc.cn

严格spring-doc.cn

布尔spring-doc.cn

在严格模式下,读取器会在 if 输入资源不存在。否则,它会记录问题并继续。ExecutionContextspring-doc.cn

LineMapper

与 一样,它采用低级结构,例如 and 返回 平面文件处理需要相同的构造来转换一行 转换为 ,如以下接口定义所示:RowMapperResultSetObjectStringObjectspring-doc.cn

public interface LineMapper<T> {

    T mapLine(String line, int lineNumber) throws Exception;

}

基本协定是,给定当前行及其行号 associated,则 Mapper 应返回一个结果域对象。这类似于 ,因为每行都与其行号相关联,就像 a 中的每一行都与其行号相关联一样。这允许将行号绑定到 生成的 domain 对象,用于身份比较或提供更多信息的日志记录。然而 与 不同,它给出了一条原始行,如上所述,只有 让你成功一半。该行必须被标记成一个 ,然后可以是 映射到对象,如本文档后面所述。RowMapperResultSetRowMapperLineMapperFieldSetspring-doc.cn

LineTokenizer

将一行 input 转换为 a 的抽象是必要的,因为 可以是需要转换为 .在 Spring Batch 中,这个接口是:FieldSetFieldSetLineTokenizerspring-doc.cn

public interface LineTokenizer {

    FieldSet tokenize(String line);

}

a 的契约是这样的,给定一行输入(理论上可以包含多行),表示该行的 a 是 返回。然后,可以将此 API 传递给 .Spring Batch 包含 以下实现:LineTokenizerStringFieldSetFieldSetFieldSetMapperLineTokenizerspring-doc.cn

  • DelimitedLineTokenizer:用于记录中的字段由 定界符。最常见的分隔符是逗号,但通常使用竖线或分号 也。spring-doc.cn

  • FixedLengthTokenizer:用于记录中的字段均为“固定 宽度”。必须为每种记录类型定义每个字段的宽度。spring-doc.cn

  • PatternMatchingCompositeLineTokenizer:确定 应该通过检查模式来在特定行上使用分词器。LineTokenizerspring-doc.cn

FieldSetMapper

该接口定义了一个方法 ,该方法采用一个对象并将其内容映射到一个对象。此对象可以是自定义 DTO、 domain 对象或数组,具体取决于作业的需要。的 与 结合使用 以翻译资源中的一行数据 转换为所需类型的对象,如以下接口定义所示:FieldSetMappermapFieldSetFieldSetFieldSetMapperLineTokenizerspring-doc.cn

public interface FieldSetMapper<T> {

    T mapFieldSet(FieldSet fieldSet) throws BindException;

}

使用的模式与 使用的模式相同。RowMapperJdbcTemplatespring-doc.cn

DefaultLineMapper

现在,用于读取平面文件的基本接口已经定义,它变为 明确需要三个基本步骤:spring-doc.cn

  1. 从文件中读取一行。spring-doc.cn

  2. 将该行传递到方法中以检索 .StringLineTokenizer#tokenize()FieldSetspring-doc.cn

  3. 将 returned from tokenizing 传递给 a ,返回 方法的结果。FieldSetFieldSetMapperItemReader#read()spring-doc.cn

上面描述的两个接口代表两个独立的任务:将 line 转换为 a 并将 a 映射到域对象。因为 a 的输入与 (a line) 的输入匹配,并且 a 的输出与 ,所以默认实现 同时使用 a 和 a。这 如下面的类定义所示,表示大多数用户需要的行为:FieldSetFieldSetLineTokenizerLineMapperFieldSetMapperLineMapperLineTokenizerFieldSetMapperDefaultLineMapperspring-doc.cn

public class DefaultLineMapper<T> implements LineMapper<>, InitializingBean {

    private LineTokenizer tokenizer;

    private FieldSetMapper<T> fieldSetMapper;

    public T mapLine(String line, int lineNumber) throws Exception {
        return fieldSetMapper.mapFieldSet(tokenizer.tokenize(line));
    }

    public void setLineTokenizer(LineTokenizer tokenizer) {
        this.tokenizer = tokenizer;
    }

    public void setFieldSetMapper(FieldSetMapper<T> fieldSetMapper) {
        this.fieldSetMapper = fieldSetMapper;
    }
}

上述功能在默认实现中提供,而不是构建 导入到 Reader 本身中(就像在框架的早期版本中所做的那样),以允许用户 在控制解析过程方面具有更大的灵活性,尤其是在访问 RAW 时 线路。spring-doc.cn

简单分隔文件读取示例

以下示例说明了如何使用实际域方案读取平面文件。 此特定批处理作业从以下文件中读取 football players:spring-doc.cn

ID,lastName,firstName,position,birthYear,debutYear
"AbduKa00,Abdul-Jabbar,Karim,rb,1974,1996",
"AbduRa00,Abdullah,Rabih,rb,1975,1999",
"AberWa00,Abercrombie,Walter,rb,1959,1982",
"AbraDa00,Abramowicz,Danny,wr,1945,1967",
"AdamBo00,Adams,Bob,te,1946,1969",
"AdamCh00,Adams,Charlie,wr,1979,2003"

此文件的内容将映射到以下域对象:Playerspring-doc.cn

public class Player implements Serializable {

    private String ID;
    private String lastName;
    private String firstName;
    private String position;
    private int birthYear;
    private int debutYear;

    public String toString() {
        return "PLAYER:ID=" + ID + ",Last Name=" + lastName +
            ",First Name=" + firstName + ",Position=" + position +
            ",Birth Year=" + birthYear + ",DebutYear=" +
            debutYear;
    }

    // setters and getters...
}

要将 a 映射到对象中,返回 players 的 a 需要 ,如以下示例所示:FieldSetPlayerFieldSetMapperspring-doc.cn

protected static class PlayerFieldSetMapper implements FieldSetMapper<Player> {
    public Player mapFieldSet(FieldSet fieldSet) {
        Player player = new Player();

        player.setID(fieldSet.readString(0));
        player.setLastName(fieldSet.readString(1));
        player.setFirstName(fieldSet.readString(2));
        player.setPosition(fieldSet.readString(3));
        player.setBirthYear(fieldSet.readInt(4));
        player.setDebutYear(fieldSet.readInt(5));

        return player;
    }
}

然后,可以通过正确构造 a 并调用 来读取该文件,如以下示例所示:FlatFileItemReaderreadspring-doc.cn

FlatFileItemReader<Player> itemReader = new FlatFileItemReader<>();
itemReader.setResource(new FileSystemResource("resources/players.csv"));
DefaultLineMapper<Player> lineMapper = new DefaultLineMapper<>();
//DelimitedLineTokenizer defaults to comma as its delimiter
lineMapper.setLineTokenizer(new DelimitedLineTokenizer());
lineMapper.setFieldSetMapper(new PlayerFieldSetMapper());
itemReader.setLineMapper(lineMapper);
itemReader.open(new ExecutionContext());
Player player = itemReader.read();

每次调用 to 都会从文件的每一行返回一个新对象。当文件末尾为 reached,则返回。readPlayernullspring-doc.cn

按名称映射字段

还有一项额外的功能是 and 都允许的,它在功能上类似于 JDBC 的 .字段的名称可以注入到这些实现中的任何一个中,以提高 mapping 函数的可读性。 首先,将平面文件中所有字段的列名注入到分词器中。 如以下示例所示:DelimitedLineTokenizerFixedLengthTokenizerResultSetLineTokenizerspring-doc.cn

tokenizer.setNames(new String[] {"ID", "lastName", "firstName", "position", "birthYear", "debutYear"});

A 可以按如下方式使用此信息:FieldSetMapperspring-doc.cn

public class PlayerMapper implements FieldSetMapper<Player> {
    public Player mapFieldSet(FieldSet fs) {

       if (fs == null) {
           return null;
       }

       Player player = new Player();
       player.setID(fs.readString("ID"));
       player.setLastName(fs.readString("lastName"));
       player.setFirstName(fs.readString("firstName"));
       player.setPosition(fs.readString("position"));
       player.setDebutYear(fs.readInt("debutYear"));
       player.setBirthYear(fs.readInt("birthYear"));

       return player;
   }
}

将 FieldSet 自动映射到 Domain Objects

对于许多人来说,必须编写特定内容与编写一样麻烦 特定于 .Spring Batch 通过提供 a 通过将字段名称与 setter 匹配来自动映射字段 在对象上使用 JavaBean 规范。FieldSetMapperRowMapperJdbcTemplateFieldSetMapperspring-doc.cn

再次使用 football 示例,配置如下所示 以下 Java 代码段:BeanWrapperFieldSetMapperspring-doc.cn

Java 配置
@Bean
public FieldSetMapper fieldSetMapper() {
	BeanWrapperFieldSetMapper fieldSetMapper = new BeanWrapperFieldSetMapper();

	fieldSetMapper.setPrototypeBeanName("player");

	return fieldSetMapper;
}

@Bean
@Scope("prototype")
public Player player() {
	return new Player();
}

再次使用 football 示例,配置如下所示 以下代码段采用 XML 格式:BeanWrapperFieldSetMapperspring-doc.cn

XML 配置
<bean id="fieldSetMapper"
      class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
    <property name="prototypeBeanName" value="player" />
</bean>

<bean id="player"
      class="org.springframework.batch.samples.domain.Player"
      scope="prototype" />

对于 中的每个条目,映射器会在新的 对象的实例(因此,需要原型范围)位于 Spring 容器查找与属性名称匹配的 setter 的方式相同。每个都可用 字段,并返回结果对象,没有 需要代码。FieldSetPlayerFieldSetPlayerspring-doc.cn

固定长度文件格式

到目前为止,仅对分隔文件进行了详细讨论。然而,它们代表 只有一半的文件读取图片。许多使用平面文件的组织都使用固定的 length 格式。固定长度文件示例如下:spring-doc.cn

UK21341EAH4121131.11customer1
UK21341EAH4221232.11customer2
UK21341EAH4321333.11customer3
UK21341EAH4421434.11customer4
UK21341EAH4521535.11customer5

虽然这看起来像一个大字段,但它实际上代表了 4 个不同的字段:spring-doc.cn

  1. ISIN: 所订购商品的唯一标识符 - 长度为 12 个字符。spring-doc.cn

  2. 数量:所订购商品的数量 - 3 个字符长。spring-doc.cn

  3. 价格:商品的价格 - 5 个字符长。spring-doc.cn

  4. 客户:订购商品的客户的 ID - 长度为 9 个字符。spring-doc.cn

配置 时,必须提供这些长度中的每一个 以范围的形式。FixedLengthLineTokenizerspring-doc.cn

以下示例演示如何定义 in Java:FixedLengthLineTokenizerspring-doc.cn

Java 配置
@Bean
public FixedLengthTokenizer fixedLengthTokenizer() {
	FixedLengthTokenizer tokenizer = new FixedLengthTokenizer();

	tokenizer.setNames("ISIN", "Quantity", "Price", "Customer");
	tokenizer.setColumns(new Range(1, 12),
						new Range(13, 15),
						new Range(16, 20),
						new Range(21, 29));

	return tokenizer;
}

以下示例演示如何定义 in XML:FixedLengthLineTokenizerspring-doc.cn

XML 配置
<bean id="fixedLengthLineTokenizer"
      class="org.springframework.batch.item.file.transform.FixedLengthTokenizer">
    <property name="names" value="ISIN,Quantity,Price,Customer" />
    <property name="columns" value="1-12, 13-15, 16-20, 21-29" />
</bean>

由于 使用 与 前面讨论过,它返回与使用 Delimiter 相同的结果。这 允许使用相同的方法处理其输出,例如使用 .FixedLengthLineTokenizerLineTokenizerFieldSetBeanWrapperFieldSetMapperspring-doc.cn

支持上述范围语法需要在 .但是,这个 bean 在使用批处理命名空间的位置自动声明。RangeArrayPropertyEditorApplicationContextApplicationContextspring-doc.cn

由于 使用 与 上面讨论过,它返回的值与使用 Delimiter 时相同。这 允许使用相同的方法处理其输出,例如使用 .FixedLengthLineTokenizerLineTokenizerFieldSetBeanWrapperFieldSetMapperspring-doc.cn

支持上述范围语法需要在 .但是,这个 bean 在使用批处理命名空间的位置自动声明。RangeArrayPropertyEditorApplicationContextApplicationContextspring-doc.cn

单个文件中的多个记录类型

到目前为止,所有的文件读取示例都对 为简单起见:文件中的所有记录都具有相同的格式。但是,这可能会 并非总是如此。文件可能包含具有不同 格式,这些格式需要以不同的方式进行标记化并映射到不同的对象。这 以下文件摘录说明了这一点:spring-doc.cn

USER;Smith;Peter;;T;20014539;F
LINEA;1044391041ABC037.49G201XX1383.12H
LINEB;2134776319DEF422.99M005LI

在这个文件中,我们有三种类型的记录,“USER”、“LINEA”和“LINEB”。“USER” 行 对应于一个对象。“LINEA” 和 “LINEB” 都对应于对象 尽管 “LINEA” 比 “LINEB” 有更多的信息。UserLinespring-doc.cn

该命令单独读取每一行,但我们必须指定不同的 and 对象,以便接收 正确的项目。通过允许地图 的模式和模式要配置。ItemReaderLineTokenizerFieldSetMapperItemWriterPatternMatchingCompositeLineMapperLineTokenizersFieldSetMappersspring-doc.cn

Java 配置
@Bean
public PatternMatchingCompositeLineMapper orderFileLineMapper() {
	PatternMatchingCompositeLineMapper lineMapper =
		new PatternMatchingCompositeLineMapper();

	Map<String, LineTokenizer> tokenizers = new HashMap<>(3);
	tokenizers.put("USER*", userTokenizer());
	tokenizers.put("LINEA*", lineATokenizer());
	tokenizers.put("LINEB*", lineBTokenizer());

	lineMapper.setTokenizers(tokenizers);

	Map<String, FieldSetMapper> mappers = new HashMap<>(2);
	mappers.put("USER*", userFieldSetMapper());
	mappers.put("LINE*", lineFieldSetMapper());

	lineMapper.setFieldSetMappers(mappers);

	return lineMapper;
}

以下示例演示如何定义 in XML:FixedLengthLineTokenizerspring-doc.cn

XML 配置
<bean id="orderFileLineMapper"
      class="org.spr...PatternMatchingCompositeLineMapper">
    <property name="tokenizers">
        <map>
            <entry key="USER*" value-ref="userTokenizer" />
            <entry key="LINEA*" value-ref="lineATokenizer" />
            <entry key="LINEB*" value-ref="lineBTokenizer" />
        </map>
    </property>
    <property name="fieldSetMappers">
        <map>
            <entry key="USER*" value-ref="userFieldSetMapper" />
            <entry key="LINE*" value-ref="lineFieldSetMapper" />
        </map>
    </property>
</bean>

在此示例中,“LINEA” 和 “LINEB” 具有单独的实例,但它们都使用 一样。LineTokenizerFieldSetMapperspring-doc.cn

该方法使用 以便为每行选择正确的代表。允许 两个具有特殊含义的通配符:问号 (“?”) 只匹配一个 字符,而星号 (“*”) 匹配零个或多个字符。请注意,在 前面的配置,所有模式都以星号结尾,从而有效地使它们 前缀。始终匹配最具体的模式 可能,无论配置中的顺序如何。因此,如果 “LINE*” 和 “LINEA*” 是 两者都列为模式,“LINEA” 将匹配模式 “LINEA*”,而 “LINEB” 将匹配 模式 “LINE*”。此外,单个星号 (“*”) 可以通过匹配 任何其他模式不匹配的任何行。PatternMatchingCompositeLineMapperPatternMatcher#matchPatternMatcherPatternMatcherspring-doc.cn

下面的示例展示了如何匹配 Java 中任何其他模式都不匹配的行:spring-doc.cn

Java 配置
...
tokenizers.put("*", defaultLineTokenizer());
...

下面的示例演示如何匹配 XML 中任何其他模式都不匹配的行:spring-doc.cn

XML 配置
<entry key="*" value-ref="defaultLineTokenizer" />

还有一个可用于标记化 独自。PatternMatchingCompositeLineTokenizerspring-doc.cn

平面文件包含每个记录跨越多行的记录也很常见。自 处理这种情况,则需要更复杂的策略。演示 常见模式可以在样本中找到。multiLineRecordsspring-doc.cn

平面文件中的异常处理

在许多情况下,对行进行标记可能会导致引发异常。多 平面文件不完美,并且包含格式不正确的记录。许多用户选择 在记录问题、原始行和行时跳过这些错误的行 数。这些日志稍后可以手动检查,也可以由另一个批处理作业检查。对于这个 原因,Spring Batch 提供了用于处理解析异常的异常层次结构:和 。 是 在尝试读取 文件。 由接口的实现引发,并指示在分词时遇到更具体的错误。FlatFileParseExceptionFlatFileFormatExceptionFlatFileParseExceptionFlatFileItemReaderFlatFileFormatExceptionLineTokenizerspring-doc.cn

IncorrectTokenCountException

Both 和 都能够指定 可用于创建 .但是,如果列数 names 与标记行时找到的列数不匹配,则无法创建 ,并且会抛出一个,其中包含 遇到的令牌数和预期数量,如以下示例所示:DelimitedLineTokenizerFixedLengthLineTokenizerFieldSetFieldSetIncorrectTokenCountExceptionspring-doc.cn

tokenizer.setNames(new String[] {"A", "B", "C", "D"});

try {
    tokenizer.tokenize("a,b,c");
}
catch (IncorrectTokenCountException e) {
    assertEquals(4, e.getExpectedCount());
    assertEquals(3, e.getActualCount());
}

因为分词器配置了 4 个列名,但在 文件,一个被抛出。IncorrectTokenCountExceptionspring-doc.cn

IncorrectLineLengthException

以固定长度格式格式化的文件时,解析时有其他要求 因为,与分隔格式不同,每列都必须严格遵守其预定义的 宽度。如果总行长不等于此列的最大宽度值,则 Exception 的 Exception 引发,如以下示例所示:spring-doc.cn

tokenizer.setColumns(new Range[] { new Range(1, 5),
                                   new Range(6, 10),
                                   new Range(11, 15) });
try {
    tokenizer.tokenize("12345");
    fail("Expected IncorrectLineLengthException");
}
catch (IncorrectLineLengthException ex) {
    assertEquals(15, ex.getExpectedLength());
    assertEquals(5, ex.getActualLength());
}

上述分词器的配置范围为:1-5、6-10 和 11-15。因此 线路的总长度为 15。但是,在前面的示例中,长度为 5 的行 被传入,导致 an 被抛出。抛出一个 exception 而不是仅映射第一列,从而允许处理 行更早失败,并且包含的信息比失败时包含的信息多,而 尝试读取 .但是,在某些情况下, 线路的长度并不总是恒定的。因此,验证行长度可以 通过 'strict' 属性关闭,如以下示例所示:IncorrectLineLengthExceptionFieldSetMapperspring-doc.cn

tokenizer.setColumns(new Range[] { new Range(1, 5), new Range(6, 10) });
tokenizer.setStrict(false);
FieldSet tokens = tokenizer.tokenize("12345");
assertEquals("12345", tokens.readString(0));
assertEquals("", tokens.readString(1));

前面的示例与前面的示例几乎相同,只是调用了该示例。此设置告诉分词器不强制执行 line length。A 现已正确创建,并且 返回。但是,它仅包含其余值的空令牌。tokenizer.setStrict(false)FieldSetspring-doc.cn