SpringBatch从入门到精通-6 读和写处理【掘金日新计划】

732 阅读15分钟

持续创作,加速成长,6月更文活动来啦!| 掘金·日新计划

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情

SpringBatch从入门到精通-1【掘金日新计划】

SpringBatch从入门到精通-2-StepScope作用域和用法【掘金日新计划】

SpringBatch从入门到精通-3-并行处理【掘金日新计划】

SpringBatch从入门到精通-3.2-并行处理-远程分区【掘金日新计划】

SpringBatch从入门到精通-3.3-并行处理-远程分区(消息聚合)【掘金日新计划】

SpringBatch从入门到精通-4 监控和指标【掘金日新计划】

SpringBatch从入门到精通-4.2 监控和指标-原理【掘金日新计划】

SpringBatch从入门到精通-5 数据源配置相关【掘金日新计划】

SpringBatch从入门到精通-5.2 数据源配置相关-原理【掘金日新计划】

ItemReaders 和 ItemWriters

所有批处理都可以用最简单的形式描述为读取大量数据,执行某种类型的计算或转换,然后写出结果。Spring Batch 提供了三个关键接口来帮助执行批量读写: ItemReaderItemProcessorItemWriter.

Item Readers

项目阅读器描述
AbstractItemCountingItemStreamItemReader抽象基类,通过计算从ItemReader.
AggregateItemReader一个ItemReader提供一个列表作为它的项目,存储来自注入的对象,ItemReader直到它们准备好作为一个集合打包出来。此类必须用作ItemReader可以识别记录边界的自定义的包装器。AggregateItem自定义阅读器应通过返回响应true其查询方法的isHeader()和来标记记录的开始和结束isFooter()。请注意,此阅读器不是 Spring Batch 提供的阅读器库的一部分,而是在spring-batch-samples.
AmqpItemReader给定一个 Spring AmqpTemplate,它提供同步接收方法。该receiveAndConvert()方法允许您接收 POJO 对象。
KafkaItemReaderItemReader从 Apache Kafka 主题读取消息的。它可以配置为从同一主题的多个分区读取消息。此阅读器将消息偏移量存储在执行上下文中以支持重新启动功能。
FlatFileItemReader从平面文件中读取。包括ItemStreamSkippable功能。
HibernateCursorItemReader基于 HQL 查询从游标读取。
HibernatePagingItemReader从分页 HQL 查询中读取
ItemReaderAdapter使任何类适应 ItemReader接口。
JdbcCursorItemReader通过 JDBC 从数据库游标读取。
JdbcPagingItemReader给定一条 SQL 语句,对行进行分页,这样就可以读取大型数据集而不会耗尽内存。
JmsItemReader给定一个 SpringJmsOperations对象和一个要向其发送错误的 JMS 目标或目标名称,提供通过注入JmsOperations#receive() 方法接收的项目。
JpaPagingItemReader给定一条 JPQL 语句,对行进行分页,这样就可以读取大型数据集而不会耗尽内存。
ListItemReader提供列表中的项目,一次一个。
MongoItemReader给定一个MongoOperations对象和一个基于 JSON 的 MongoDB 查询,提供从该MongoOperations#find()方法接收的项目。
Neo4jItemReader给定一个Neo4jOperations对象和 Cyhper 查询的组件,项目作为 Neo4jOperations.query 方法的结果返回。
RepositoryItemReader给定一个 Spring DataPagingAndSortingRepository对象、aSort和要执行的方法的名称,返回 Spring Data 存储库实现提供的项。
StoredProcedureItemReader从执行数据库存储过程产生的数据库游标中读取。
StaxEventItemReader通过 StAX 读取。
JsonItemReader从 Json 文档中读取项目

image-20220614224746414

image-20220614224806031

ItemReader

虽然是一个简单的概念,但它ItemReader是从许多不同类型的输入中提供数据的方法。最普遍的例子包括:

  • Flat File:Flat File项目阅读器从Flat File读取数据行,该文件通常描述具有由文件中的固定位置定义或由某些特殊字符(例如逗号)分隔的数据字段的记录。

    Flat File是一种包含没有相对关系结构的记录的文件。这个类型通常用来描述文字处理、其他结构字符或标记被移除了的文本。在使用上,有一些模糊点,如像换行标记是否可以包含于“Flat File(flat file)”中。在任何事件中,许多用户把保存成“纯文本(text only)”类型的Microsoft Word文档叫做“Flat File(flat file)”。最终文件包含记录(一定长度的文本的行数)但没有信息,例如,用多长的行来定义标题或者一个程序用多大的长度来用一个内容表对该文档进行格式化。

  • XML:ItemReaders独立于用于解析、映射和验证对象的技术的 XML 处理 XML。输入数据允许针对 XSD 模式验证 XML 文件。

  • 数据库:访问数据库资源以返回可以映射到对象进行处理的结果集。默认的 SQLItemReader实现调用 aRowMapper 来返回对象,如果需要重新启动,则跟踪当前行,存储基本统计信息,并提供一些稍后解释的事务增强。

ItemReader是通用输入操作的基本接口,如下接口定义所示:

public interface ItemReader<T> {
​
    T read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException;
​
}

read方法定义了ItemReader. 调用它会返回一个项目,或者null如果没有更多项目。一个项目可能代表文件中的一行、数据库中的一行或 XML 文件中的一个元素。通常期望这些映射到可用的域对象(例如TradeFoo或其他),但合同中没有要求这样做。

预计ItemReader接口的实现只是向前的。但是,如果底层资源是事务性的(例如 JMS 队列),则调用 read可能会在回滚场景中的后续调用中返回相同的逻辑项。还值得注意的是,缺少要由 an 处理的项目ItemReader不会导致引发异常。例如,ItemReader配置有返回 0 结果的查询的数据库null在第一次调用read.

ItemWriter

ItemWriter描述
AbstractItemStreamItemWriterItemStream结合和 ItemWriter接口的抽象基类 。
AmqpItemWriter给定一个 Spring AmqpTemplate,它提供了一个同步send方法。该convertAndSend(Object) 方法允许您发送 POJO 对象。
CompositeItemWriter将一个项目传递给write每个注入对象List的方法。ItemWriter
FlatFileItemWriter写入平面文件。包括ItemStream和可跳过的功能。
GemfireItemWriter使用一个GemfireOperations对象,项目可以根据删除标志的配置从 Gemfire 实例中写入或删除。
HibernateItemWriter这个项目编写器是 Hibernate-session 感知的,并处理一些与事务相关的工作,非“休眠感知”项目编写器不需要知道,然后委托给另一个项目编写器进行实际编写。
ItemWriterAdapter使任何类适应 ItemWriter接口。
JdbcBatchItemWriter使用 中的批处理功能 PreparedStatement(如果可用),并且可以采取基本步骤在 flush.
JmsItemWriter使用对象,通过该方法JmsOperations将项目写入默认队列。JmsOperations#convertAndSend()
JpaItemWriter这个项目编写器是 JPA EntityManager-aware 并处理一些非“JPA-aware” ItemWriter不需要知道的与事务相关的工作,然后委托给另一个编写器来进行实际的编写。
KafkaItemWriter使用对象,通过使用 a映射项目中的键KafkaTemplate的方法将项目写入默认主题 。还可以配置删除标志以将删除事件发送到主题。KafkaTemplate#sendDefault(Object, Object)``Converter
MimeMessageItemWriter使用 Spring 的JavaMailSender,类型的项目MimeMessage 作为邮件消息发送。
MongoItemWriter给定一个MongoOperations对象,通过该MongoOperations.save(Object)方法写入项目。实际写入会延迟到事务提交前的最后可能时刻。
Neo4jItemWriter给定一个Neo4jOperations对象,项目通过方法持久化 或通过配置 save(Object)删除delete(Object)``ItemWriter’s
PropertyExtractingDelegatingItemWriter扩展动态AbstractMethodInvokingDelegator 创建参数。SpringBeanWrapper根据注入的字段名称数组,通过从要处理的项目中的字段中检索值来创建参数(通过 a )。
RepositoryItemWriter给定一个 Spring DataCrudRepository实现,通过配置中指定的方法保存项目。
StaxEventItemWriter使用Marshaller实现将每个项目转换为 XML,然后使用 StAX 将其写入 XML 文件。
JsonFileItemWriter使用JsonObjectMarshaller实现将每个项目转换为 Json,然后将其写入 Json 文件。

ItemWriter在功能上与 ItemReader相似,但具有逆运算。资源仍然需要定位、打开和关闭,但它们的不同之处在于 ItemWriter写出而不是读入。在数据库或队列的情况下,这些操作可能是插入、更新或发送。输出的序列化格式特定于每个批处理作业。

与 一样ItemReaderItemWriter是一个相当通用的接口,如下面的接口定义所示:

public interface ItemWriter<T> {
​
    void write(List<? extends T> items) throws Exception;
​
}

readon一样ItemReaderwrite提供了基本的合约ItemWriter。只要它处于打开状态,它就会尝试写出传入的项目列表。因为通常期望项目被“批处理”成一个块然后输出,所以接口接受项目列表,而不是一个项目本身。写出列表后,可以在从 write 方法返回之前执行任何可能需要的刷新。例如,如果写入 Hibernate DAO,则可以多次调用 write,每个项目调用一次。然后,作者可以flush在返回之前调用休眠会话。

ItemStream

两者都很好ItemReadersItemWriters服务于它们各自的目的,但是它们之间有一个共同的问题,即需要另一个接口。通常,作为批处理作业范围的一部分,需要打开、关闭读取器和写入器,并且需要一种保持状态的机制。该ItemStream接口用于此目的,如以下示例所示:

public interface ItemStream {
​
    void open(ExecutionContext executionContext) throws ItemStreamException;
​
    void update(ExecutionContext executionContext) throws ItemStreamException;
​
    void close() throws ItemStreamException;
}

在描述每种方法之前,我们应该提到ExecutionContext. ItemReader同样实现的客户端 ItemStream应该在对 的open任何调用之前调用 read,以便打开任何资源(例如文件)或获取连接。类似的限制适用于ItemWriter实现ItemStream. 如第 2 章所述,如果在 中找到预期数据ExecutionContext,则它可能用于在其初始状态以外的位置启动ItemReader或。ItemWriter相反, close调用以确保在打开期间分配的任何资源都被安全释放。 update调用主要是为了确保当前持有的任何状态都加载到提供的ExecutionContext. 该方法在提交前调用,以确保当前状态在提交前被持久化在数据库中。

委托模式和注册步骤

请注意,这CompositeItemWriter是委托模式的一个示例,在 Spring Batch 中很常见。委托本身可能实现回调接口,例如StepListener. 如果它们这样做并且如果它们与 Spring Batch Core 一起作为 aStep的一部分使用Job,那么它们几乎肯定需要手动注册到Step. 直接连接到接口的读取器、写入器或处理器在Step实现ItemStreamStepListener接口时会自动注册。但是,由于代理不知道Step,因此需要将它们作为侦听器或流(或两者都注入,如果合适的话)。

Java 配置

@Bean
public Job ioSampleJob() {
    return this.jobBuilderFactory.get("ioSampleJob")
                .start(step1())
                .build();
}
​
@Bean
public Step step1() {
    return this.stepBuilderFactory.get("step1")
                .<String, String>chunk(2)
                .reader(fooReader())
                .processor(fooProcessor())
                .writer(compositeItemWriter())
                .stream(barWriter())
                .build();
}
​
@Bean
public CustomCompositeItemWriter compositeItemWriter() {
​
    CustomCompositeItemWriter writer = new CustomCompositeItemWriter();
​
    writer.setDelegate(barWriter());
​
    return writer;
}
​
@Bean
public BarWriter barWriter() {
    return new BarWriter();
}

Flat File

交换批量数据的最常见机制之一一直是Flat File。与 XML 不同,XML 具有定义其结构 (XSD) 的公认标准,任何阅读Flat File的人都必须提前了解文件的结构。一般来说,所有Flat File都分为两种类型:定界文件和定长文件。分隔文件是其中字段由分隔符(例如逗号)分隔的文件。固定长度文件具有设定长度的字段。

FieldSet

在 Spring Batch 中处理Flat File时,无论是用于输入还是输出,最重要的类之一是FieldSet. 许多体系结构和库包含帮助您从文件中读取的抽象,但它们通常返回一个String或一组String对象。这真的只能让你走到一半。AFieldSet是 Spring Batch 的抽象,用于启用来自文件资源的字段绑定。它允许开发人员以与处理数据库输入相同的方式处理文件输入。AFieldSet在概念上类似于 JDBC ResultSet。AFieldSet只需要一个参数:aString令牌数组。(可选)您还可以配置字段的名称,以便可以通过索引或名称访问字段ResultSet,如以下示例所示:

String[] tokens = new String[]{"foo", "1", "true"};
FieldSet fs = new DefaultFieldSet(tokens);
String name = fs.readString(0);
int value = fs.readInt(1);
boolean booleanValue = fs.readBoolean(2);

界面上还有更多的选项FieldSet,如Date、long 、 BigDecimal等。最大的优点FieldSet是它提供了对Flat File输入的一致解析。在处理由格式异常引起的错误或进行简单的数据转换时,它可以保持一致,而不是每个批处理作业以潜在的意外方式进行不同的解析。

FlatFileItemReader

Flat File是最多包含二维(表格)数据的任何类型的文件。在 Spring Batch 框架中读取Flat File是由名为 的类促进的 FlatFileItemReader,它提供了读取和解析Flat File的基本功能。两个最重要的必需依赖项FlatFileItemReaderResourceLineMapper。该LineMapper接口将在下一节中进行更多探讨。resource 属性表示一个 Spring Core Resource

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

在复杂的批处理环境中,目录结构通常由企业应用程序集成 (EAI) 基础设施管理,其中为外部接口建立放置区,用于将文件从 FTP 位置移动到批处理位置,反之亦然。文件移动实用程序超出了 Spring Batch 架构的范围,但是批处理作业流将文件移动实用程序作为作业流中的步骤包括在内并不罕见。批处理架构只需要知道如何定位要处理的文件。Spring Batch 从这个起点开始将数据输入管道的过程。但是, Spring Integration提供了许多这些类型的服务。

中的其他属性可FlatFileItemReader让您进一步指定数据的解释方式,如下表所述:

PropertyTypeDescription
commentsString[]指定指示注释行的行前缀。
encodingString指定要使用的文本编码。默认值为 的值Charset.defaultCharset()
lineMapperLineMapperString转换为Object项目
linesToSkipint文件顶部要忽略的行数。
recordSeparatorPolicyRecordSeparatorPolicy用于确定行尾在哪里,并在带引号的字符串内执行诸如继续行尾之类的操作。
resourceResource要从中读取的资源。
skippedLinesCallbackLineCallbackHandler传递文件中要跳过的行的原始行内容的接口。如果linesToSkip设置为 2,则该接口被调用两次。
strictbooleanExecutionContext在严格模式下,如果输入资源不存在,阅读器会抛出异常。否则,它会记录问题并继续。
``
LineMapper

与 一样RowMapper,它采用低级构造,例如ResultSet并返回 an Object,Flat File处理需要相同的构造来将Stringline 转换为 an Object,如以下接口定义所示:

public interface LineMapper<T> {
​
    T mapLine(String line, int lineNumber) throws Exception;
​
}

基本约定是,给定当前行和与其关联的行号,映射器应返回结果域对象。这类似于 RowMapper,因为每一行都与其行号相关联,就像 a 中的每一行都 ResultSet与其行号相关联。这允许将行号绑定到生成的域对象,以进行身份比较或获取更多信息的日志记录。但是,与 不同RowMapper的是,LineMapper它给出了一条原始线,如上所述,它只会让你走到一半。该行必须被标记为 a FieldSet,然后可以映射到一个对象,如本文档后面所述。

LineTokenizer

将输入行转换为 a 的抽象FieldSet是必要的,因为可能有许多格式的Flat File数据需要转换为FieldSet. 在 Spring Batch 中,这个接口是LineTokenizer

public interface LineTokenizer {
​
    FieldSet tokenize(String line);
​
}

a 的契约LineTokenizer是这样的,给定一行输入(理论上 String可以包含多行),FieldSet返回代表该行的 a。然后FieldSet可以将其传递给FieldSetMapper. Spring Batch 包含以下LineTokenizer实现:

  • DelimitedLineTokenizer:用于记录中的字段由分隔符分隔的文件。最常见的分隔符是逗号,但也经常使用管道或分号。
  • FixedLengthTokenizer:用于记录中的每个字段都是“固定宽度”的文件。必须为每种记录类型定义每个字段的宽度。
  • PatternMatchingCompositeLineTokenizerLineTokenizer通过检查模式来确定应在特定行上使用标记器列表中的哪一个。
FieldSetMapper

FieldSetMapper接口定义了一个方法,mapFieldSet该方法接受一个 FieldSet对象并将其内容映射到一个对象。此对象可能是自定义 DTO、域对象或数组,具体取决于作业的需要。与FieldSetMapper结合使用,LineTokenizer将资源中的一行数据转换为所需类型的对象,如下面的接口定义所示:

public interface FieldSetMapper<T> {
​
    T mapFieldSet(FieldSet fieldSet) throws BindException;
​
}

RowMapper使用的模式与使用的相同JdbcTemplate

DefaultLineMapper

现在已经定义了读取Flat File的基本接口,很明显需要三个基本步骤:

  1. 从文件中读取一行。
  2. String将该行传递给LineTokenizer#tokenize()方法以检索 FieldSet.
  3. 将标记化返回的值传递FieldSet给 a FieldSetMapper,从方法返回结果ItemReader#read()

上面描述的两个接口代表两个独立的任务:将线转换为 a FieldSet并将 a 映射FieldSet到域对象。因为 a LineTokenizer的输入与LineMapper(a 行)的输入相匹配,并且 a 的输出 FieldSetMapper与 的输出相匹配,所以提供了同时使用 a和 aLineMapper的默认实现。下面的类定义中显示的 代表大多数用户需要的行为:LineTokenizer``FieldSetMapper``DefaultLineMapper

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;
    }
}

上述功能是在默认实现中提供的,而不是内置在阅读器本身中(就像在以前版本的框架中所做的那样),以允许用户在控制解析过程方面具有更大的灵活性,尤其是在需要访问原始行的情况下。

代码位置: github.com/jackssybin/…