FlatFileItemReader
平面文件是指任何最多包含二维(表格)数据的文件类型。
在 Spring Batch 框架中读取平面文件由名为FlatFileItemReader该功能提供了读取和解析平板的基本功能
文件。两个最重要的依赖关系FlatFileItemReader是资源和线图仪.这线图仪界面将在下一个区域进一步探讨
部分。资源属性代表一个Spring核心资源.文档
关于如何制作这种Beans的介绍可以在《春季》一集中找到
框架,第五章。资源。因此,本指南不涉及
创建资源对象之外展示了以下简单示例:
Resource resource = new FileSystemResource("resources/trades.csv");
在复杂的批处理环境中,目录结构通常由企业应用集成(EAI)管理 基础设施,即为外部接口设置投放区以传输文件 从FTP地点到批处理地点,反之亦然。文件移动工具 超出了春批架构的范围,但批处理中并不罕见 作业流中包含文件移动工具作为作业流中的步骤。批次 架构只需要知道如何定位待处理的文件。春季批次 开始将数据从该起点输入管道的过程。然而,Spring 集成提供了许多 这些类型的服务。
其他性质FlatFileItemReader让你进一步说明你的数据是怎样的
如下表所述的解释:
| 属性 | 类型 | 描述 |
|---|---|---|
评论 |
弦[] |
指定表示注释行的前缀。 |
编码 |
字符串 |
指定使用哪种文本编码。默认值为 |
线图仪 |
|
转换为 |
linesToSkip |
智力 |
文件顶部可以忽略的行数。 |
记录分离器策略 |
记录分隔器策略 |
用于确定线尾位置 并且如果在引号字符串内,可以继续在结尾的行上继续。 |
资源 |
|
阅读资源。 |
跳过了回拨 |
线路回调处理程序 |
传递原始行内容的接口
文件中要跳过的行。如果 |
严格 |
布尔 |
在严格模式下,读卡器会抛出一个异常 |
线图仪
如同行图仪,该结构采用低层构造,如结果集以及返回
一对象,平面文件处理需要相同的构造来转换字符串线
变成了对象如以下接口定义所示:
public interface LineMapper<T> {
T mapLine(String line, int lineNumber) throws Exception;
}
基本契约是,给定当前行及其所对应的行号
关联后,映射器应返回一个结果域对象。这类似于行图仪即每行都与其行号相关联,就像 a 中的每一行一样结果集与其行号绑定。这使得行号可以与
结果域对象用于身份比较或更有用的日志记录。然而
与行图仪这线图仪给出一条原始直线,如上所述,仅
能帮你完成一半。该行必须被标记为野外集,此时可以是
映射到一个对象,如本文档后面所述。
LineTokenizer
将输入线转换为野外集是必要的,因为
可以有多种格式的平面文件数据需要转换为野外集.在
Spring Batch,这个接口是LineTokenizer:
public interface LineTokenizer {
FieldSet tokenize(String line);
}
一LineTokenizer满足于,给定一行输入(理论上字符串可以包含多行),a野外集表示该直线为
返回。这野外集然后可以传递给FieldSetMapper.春季批次包含
以下内容LineTokenizer实现:
-
DelimitedLineTokenizer: 用于记录中字段之间被 定界符。最常见的分隔符是逗号,但也常用管道或分号 也。 -
固定长度标记器: 用于记录中字段各自为“固定”的文件 宽度“。每个字段的宽度必须为每种记录类型定义。 -
模式匹配复合线分词器: 确定LineTokenizer在以下列表中 分词器应通过与某个模式进行对照来对应特定行。
FieldSetMapper
这FieldSetMapper接口定义了单一方法,mapFieldSet,该过程具有野外集对象并将其内容映射到一个对象。该对象可以是自定义DTO,也是一个
域对象,或数组,具体取决于作业需求。这FieldSetMapper是
与LineTokenizer将数据行从资源中转换出来
进入目标类型的对象,如以下接口定义所示:
public interface FieldSetMapper<T> {
T mapFieldSet(FieldSet fieldSet) throws BindException;
}
所用图案与行图仪使用Jdbc模板.
默认线图器
现在定义了平面文件读取的基本接口,它变为 明确需要三个基本步骤:
-
读文件里的一句话。
-
传给
字符串进入LineTokenizer#tokenize()获取野外集. -
传给
野外集从分词化到AFieldSetMapper,返回 结果由ItemReader#read()方法。
上述两种接口代表两个独立任务:将一条线转换为野外集以及映射一个野外集到域对象。因为LineTokenizer与线图仪(一行),以及FieldSetMapper与 的输出相匹配线图仪,一个默认实现
同时使用LineTokenizer以及一个FieldSetMapper提供。这默认线图器,
以下类定义所示,代表大多数用户所需的行为:
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;
}
}
上述功能是在默认实现中提供,而非被开发 进入阅读器本身(如同之前版本框架所做的那样),以便用户能够使用 在控制解析过程时,尤其是在访问原始文件时,更灵活 需要这条线。
简单分隔文件读取示例
以下示例展示了如何用实际的域场景读取一个平面文件。 这个批次作业会从以下文件中读取球员:
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"
该文件的内容映射到以下内容选手领域对象:
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...
}
映射一个野外集变成了选手对象,aFieldSetMapper这回馈了玩家的需求
待定义,如下例所示:
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;
}
}
然后可以通过正确构造FlatFileItemReader以及呼唤读如下例所示:
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();
每次呼叫读退还一个新的选手文件中每行的对象。当文件结尾为
达到零被归还。
按名称映射字段
还有一项额外的功能是两者都允许的DelimitedLineTokenizer和固定长度标记器且 在功能上类似于
JDBC结果集.场的名称可以注入到任一中LineTokenizer提升映射函数可读性的实现。
首先,将平面文件中所有字段的列名注入到分词器中,
如下例所示:
tokenizer.setNames(new String[] {"ID", "lastName", "firstName", "position", "birthYear", "debutYear"});
一个FieldSetMapper你可以根据以下方式使用这些信息:
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;
}
}
将字段集自动映射到域对象
对许多人来说,必须写出特定的内容FieldSetMapper和写作一样繁琐
一个特定的行图仪对于Jdbc模板.Spring Batch通过提供
一个FieldSetMapper它通过匹配字段名与设定器,自动映射字段
在对象上使用JavaBean规范。
-
Java
-
XML
再次以足球为例,BeanWrapperFieldSetMapper配置看起来像
以下是爪哇语的摘录:
@Bean
public FieldSetMapper fieldSetMapper() {
BeanWrapperFieldSetMapper fieldSetMapper = new BeanWrapperFieldSetMapper();
fieldSetMapper.setPrototypeBeanName("player");
return fieldSetMapper;
}
@Bean
@Scope("prototype")
public Player player() {
return new Player();
}
再次以足球为例,BeanWrapperFieldSetMapper配置看起来像
以下为XML格式的片段:
<bean id="fieldSetMapper"
class="org.springframework.batch.infrastructure.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="prototypeBeanName" value="player" />
</bean>
<bean id="player"
class="org.springframework.batch.samples.domain.Player"
scope="prototype" />
对于每个野外集,映射器在新的 上寻找对应的定位器
实例选手对象(因此,需要原型作用波)在
就像 Spring 容器寻找与属性名称匹配的设置器一样。每种情况都可用
在野外集映射,结果选手返回对象,且
需要代码。
固定长度文件格式
到目前为止,只有分隔文件被详细讨论过。然而,它们代表 只有一半的文件显示图片。许多使用平面文件的组织使用固定文件 长度格式。以下是一个固定长度文件示例:
UK21341EAH4121131.11customer1 UK21341EAH4221232.11customer2 UK21341EAH4321333.11customer3 UK21341EAH4421434.11customer4 UK21341EAH4521535.11customer5
虽然看起来像是一个大场,但实际上代表了四个不同的场:
-
ISIN:订单物品的唯一标识符——12个字符。
-
数量:订购物品的数量——3个字符。
-
价格:物品价格——5个字符长度。
-
顾客:订购该商品的顾客身份证——9个字符。
在配置固定长度线标记器必须提供每个长度
以范围的形式出现。
-
Java
-
XML
以下示例展示了如何定义固定长度线标记器在
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;
}
以下示例展示了如何定义固定长度线标记器在
XML:
<bean id="fixedLengthLineTokenizer"
class="org.springframework.batch.infrastructure.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>
因为固定长度线标记器使用方式相同LineTokenizer作为接口
如前所述,返回结果相同野外集仿佛用了分隔符。这
允许在处理其输出时采用相同的方法,例如使用BeanWrapperFieldSetMapper.
|
支持前述范围语法需要专业的属性编辑器, |
因为固定长度线标记器使用方式相同LineTokenizer作为接口
如上所述,返回结果相同野外集仿佛用了分隔符。这
允许在处理其输出时使用相同的方法,例如使用BeanWrapperFieldSetMapper.
单一文件中的多种记录类型
到目前为止所有文件读取示例都假设了 为了简化:文件中的所有记录格式相同。不过,这可能 但情况并非总是如此。一个文件中可能有不同的记录是非常常见的 需要以不同方式标记并映射到不同对象的格式。这 以下摘录为一份文件,说明了这一点:
USER;Smith;Peter;;T;20014539;F LINEA;1044391041ABC037.49G201XX1383.12H LINEB;2134776319DEF422.99M005LI
在这个文件中,我们有三种类型的记录:“USER”、“LINEA”和“LINEB”。一个“用户”行
对应于用户对象。“LINEA”和“LINEB”都对应于线对象
不过“LINEA”比“LINEB”提供更多信息。
这物品阅读器分别读取每行,但必须指定不同的行LineTokenizer和FieldSetMapper使得物品写手接收
正确的物品。这模式匹配复合线映射器通过允许映射,这变得简单
模式 到线分词器模式为FieldSetMappers需要配置。
-
Java
-
XML
@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;
}
以下示例展示了如何定义固定长度线标记器在
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” 分别是独立的LineTokenizer实例,但它们都使用
一样FieldSetMapper.
这模式匹配复合线映射器使用图案拼凑员#比赛方法
为了为每条线选出正确的代表。这模式匹配器允许
两个具有特殊含义的万用字符:问号(“?”)恰好匹配一个
字符,而星号(“*”)则匹配零个或多个字符。注意,在
在配置之前,所有模式以星号结尾,使其有效
行前缀。这模式匹配器总是匹配最具体的模式
无论配置顺序如何,都可能。所以如果“LINE*”和“LINEA*” 是
两者均列为图案,“LINEA”对应“LINEA*”,而“LINEB”对应
图案“LINE*”。此外,单一星号(“*”)可作为默认,通过匹配
任何没有与其他图案匹配的直线。
-
Java
-
XML
以下示例展示了如何匹配Java中其他任何模式都无法匹配的行:
...
tokenizers.put("*", defaultLineTokenizer());
...
以下示例展示了如何匹配XML中没有其他模式匹配的行:
<entry key="*" value-ref="defaultLineTokenizer" />
还有一个模式匹配复合线分词器这可以用于分词化
独自。
平面文件通常包含跨越多行的记录。自
处理这种情况时,需要更复杂的策略。演示一下
常见的模式可见于multiLineRecords样本。
平面文件中的异常处理
在许多情况下,分牌化某行可能导致异常出现。多
平面文件不完美,且包含格式错误的记录。许多用户选择
在记录问题、原始行和该行时跳过这些错误行
数。这些日志可以之后手动检查,也可以用其他批处理作业进行检查。为此
因此,Spring Batch 提供了处理解析异常的异常层级结构:FlatFileParseException和FlatFileFormatException.FlatFileParseException是
被FlatFileItemReader当尝试读取 a 时遇到任何错误
文件。FlatFileFormatException是被 的实现抛掷LineTokenizer接口和表示在分词化过程中遇到的更具体错误。
IncorrectTokenCountException
双DelimitedLineTokenizer和固定长度线标记器具备 具体
可用于创建野外集.然而,如果列数
名称与分词行时发现的列数不匹配,野外集无法被创造,且IncorrectTokenCountException是抛掷的,包含
遇到的标记数量及预期数量,如下例所示:
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列名称,但只找到3个Tokens
文件,一个IncorrectTokenCountException被扔了。
IncorrectLineLengthException
格式为固定长度的文件在解析时有额外要求 因为与分隔格式不同,每个列必须严格遵守其预定义 宽度。如果总行长不等于该列的最宽值,则 投掷异常,如下示例所示:
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的直线
被传入,导致IncorrectLineLengthException被扔出去。抛出一个
这里的例外是,除了映射第一列,还允许处理
如果失败,行会更早且包含更多信息,而
试图在A的第2栏读取FieldSetMapper.然而,也存在一些情形
直线长度并不总是恒定的。因此,行长的验证可以
通过“严格”性质关闭,如下示例所示:
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));
前述示例与前例几乎相同,唯一区别是tokenizer.setStrict(false)被叫到了。该设置告诉分词器不要强制执行
分词时的行长。一个野外集现在正确生成了 和
返回。然而,它只包含剩余值的空标记。