SpringBatch-精要-全-
SpringBatch 精要(全)
原文:
zh.annas-archive.org/md5/0fe2dd5fae2f276a4e88e006109c4f2b译者:飞龙
前言
欢迎来到 Spring Batch 的世界!我们很高兴您选择了我们的书籍,本书全面致力于 Spring Batch 3.0.2 版本的精华。在我们开始这本书之前,我想概述一下这本书的组织结构和如何最大限度地利用它。一旦您完成阅读这本书,您应该熟悉关键的批处理概念,并了解如何解决您将使用 Spring Batch 解决的大多数现实世界问题。本书深入探讨了 Spring Batch 框架的基本概念和应用,这将使您能够处理书中未涵盖的任何意外用例。
Spring Batch 是一个开源、轻量级且全面的解决方案,旨在使开发健壮的批处理应用程序成为可能,这对于企业运营至关重要。
本教程从对批处理应用程序和 Spring Batch 提供的深入了解开始。了解架构和关键组件后,您将学习如何开发和执行批处理应用程序。然后,您可以了解基本配置和执行技术,读取和写入的关键技术实现,以及不同形式数据的处理功能。接下来,您将转向关键功能,如事务管理、作业流、作业监控以及执行作业步骤之间的数据共享。最后,您将了解 Spring Batch 如何与各种企业技术集成,并使用缩放和分区技术促进优化和性能改进。
本书使用一个基于 Spring Batch 的简单应用程序来说明如何解决现实世界的问题。该应用程序旨在非常简单直接,并且故意包含很少的功能——这个应用程序的目标是鼓励您专注于 Spring Batch 概念,而不是陷入应用程序开发的复杂性。如果您花时间审查示例应用程序源代码,您将更容易跟随本书。有关入门的一些提示可以在附录中找到。
本书涵盖内容
第一章,Spring Batch 基础,向您介绍批处理应用程序以及 Spring Batch 和其提供的详细信息。它讨论了 Spring Batch 架构,并展示了如何设计和执行一个 Spring Batch 作业。
第二章,开始使用 Spring Batch 作业,涵盖了 Spring Batch XML 功能,如何配置作业、事务和存储库,使用 EL 和监听器进行工作,以及从命令行和 Web 应用程序调度器执行作业。
第三章,与数据一起工作,展示了基本的数据处理机制,包括从不同来源(如平面文件、XML 和数据库)读取数据,处理数据,并将数据写入不同的目的地。它还解释了在数据处理阶段转换和验证数据。
第四章,处理作业事务,涵盖了事务和 Spring Batch 事务管理,并解释了如何自定义事务以及事务模式。
第五章,步骤执行,解释了控制作业流程的技术,以及在执行步骤间共享数据和外部化过程重用的方法。它还演示了在不同状态下终止批量作业及其重要性。
第六章,集成 Spring Batch,涵盖了基于 Spring Batch 的企业应用程序的集成技术,以及一些作业启动和流程技术。
第七章,检查 Spring Batch 作业,展示了 Spring Batch 作业的监控技术,执行作业数据的访问方法,以及使用监听器和 Web 监控技术的监控和报告。
第八章,使用 Spring Batch 进行扩展,讨论了 Spring Batch 对基于配置的调整技术(如并行处理、远程分块和分区)的支持所带来的性能和扩展问题。
第九章,测试 Spring Batch,介绍了使用开源框架和 Spring 对单元测试、集成测试和功能测试的支持来测试 Spring Batch 应用程序的详细测试技术。
附录涵盖了 Java、Eclipse IDE、具有依赖关系的项目和行政项目的设置参考。
你需要这本书什么
以下列表提供了运行本书附带示例应用程序所需的软件。某些章节有额外的要求,这些要求在章节本身中进行了概述**:
-
Java 开发工具包 1.6+可以从 Oracle 的网站下载,网址为
www.oracle.com/technetwork/java/javase/downloads/index.html -
Web 开发者使用的 Eclipse Java EE IDE 可以从 Eclipse 的网站下载,网址为
www.eclipse.org/downloads/packages/eclipse-ide-java-ee-developers/keplersr2
这本书面向谁
本书面向具有一定企业应用开发经验的 Java 开发者,他们想了解使用 Spring Batch 进行批处理应用开发,以在基于 Java 的平台构建简单而强大的批处理应用。
习惯用法
在本书中,你会找到多种文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码词汇将如下所示:“ChunkProvider 是返回ItemReader中块的面接口。”
代码块将以如下方式设置:
public interface ChunkProvider<T> {
void postProcess(StepContribution contribution, Chunk<T> chunk);
Chunk<T> provide(StepContribution contribution) throws Exception;
}
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
<step id="initialStep">
<partition step="stepPartition" handler="handler"/>
</step>
<bean class="org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler">
<property name="taskExecutor" ref="taskExecutor"/>
<property name="step" ref="stepPartition"/>
<property name="gridSize" value="10"/>
</bean>
新术语和重要词汇将以粗体显示。你在屏幕上看到的,例如在菜单或对话框中的单词,在文本中会这样显示:“如说明中提到的,Maven 软件需要集成到 Eclipse IDE 中,详情请见www.eclipse.org/m2e/”。
注意
警告或重要提示将以这样的框显示。
小贴士
技巧和窍门将如下所示。
读者反馈
我们欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者反馈对我们开发您真正能从中获得最大价值的标题非常重要。
要向我们发送一般反馈,只需发送一封电子邮件到 <feedback@packtpub.com>,并在邮件主题中提及书名。
如果你在某个领域有专业知识,并且你对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南,网址为 www.packtpub.com/authors。
客户支持
现在,您已经是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大价值。
下载示例代码
您可以从您在www.packtpub.com的账户下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
错误清单
尽管我们已经尽最大努力确保内容的准确性,错误仍然可能发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以避免其他读者感到沮丧,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/support,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站,或添加到该标题的勘误部分现有的勘误列表中。
侵权
互联网上版权材料的侵权是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过 <copyright@packtpub.com> 与我们联系,并提供涉嫌侵权材料的链接。
我们感谢您在保护我们的作者以及为我们提供有价值内容的能力方面提供的帮助。
问题
如果您在本书的任何方面遇到问题,可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决。
第一章。Spring Batch 基础知识
组织需要在日常运营中通过一系列交易处理大量数据。这些业务操作应该自动化,以便在没有人为干预的情况下高效处理信息。批处理可以通过程序执行此类一系列操作,以预定义的数据组作为输入,处理数据,并生成一组输出数据组或更新数据库。
在本章中,我们将涵盖以下主题:
-
批处理应用程序简介
-
Spring Batch 及其提供的产品
-
Spring Batch 基础设施
-
作业设计和执行
批处理应用程序简介
组织需要完成各种业务操作,包括大量数据处理。以下是一些此类操作的示例:
-
在大型企业中生成工资单和税务计算
-
银行生成的信用卡账单
-
零售商在其目录中更新的新鲜库存
所有这些操作都是通过预定义的配置和计划集来执行的,以便在特定的卸载系统时间运行。批处理应用程序应该能够在没有人为干预的情况下处理大量数据。以下图表示了一个典型的批处理应用程序:

标准批处理应用程序应具备以下功能:
-
可扩展性:它应该能够处理数十亿条记录,并且在不会崩溃应用程序的情况下保持可靠性
-
健壮性:它应该足够智能,能够识别无效数据并跟踪此类错误,以便使用修正后的数据重新运行
-
动态性:它应该能够与不同的系统交互,使用提供的凭据访问数据并处理操作
-
并发性:它必须能够与共享资源并行处理多个作业
-
系统性:它应该按照依赖步骤的顺序处理由工作流程驱动的批处理
-
高性能:它必须在指定的批处理窗口内完成处理
Spring Batch 及其提供的产品
Spring Batch 是一个轻量级、全面的批处理框架,旨在使开发健壮的批处理应用程序成为可能,这对于 SpringSource 和埃森哲合作开发的企业系统日常运营至关重要。
Spring Batch 采用基于 POJO 的开发模式,以便开发人员能够轻松实现批处理并在需要时与其他企业系统集成。
纯旧 Java 对象(POJO)代表一个普通的 Java 对象,可以用来存储数据项并轻松地在服务之间交换信息。
虽然 Spring Batch 提供了许多从 Spring 框架中采用的可重用函数,并针对批处理应用程序进行了定制,以执行常见的批处理操作(如大量数据的分割处理、日志记录、事务管理、作业过程跳过-重启和有效的资源管理),但它不是一个调度框架。Spring Batch 可以与调度程序(如 Quartz/Control-M)一起工作,但不能替代调度程序。
在上一节中,我们讨论了从标准批处理应用程序中期望的功能。Spring Batch 被设计用来实现这些期望的功能,同时具备高能力,以与其他框架中开发的不同的应用程序集成。让我们观察 Spring Batch 提供的一些重要功能:
-
支持多种文件格式,包括固定长度、分隔符文件、XML 以及使用 JDBC 的常见数据库访问,以及其他突出框架。
-
失败后的自动重试
-
作业控制语言以监控和执行常见操作,如作业启动、停止、暂停和取消
-
在批处理执行期间以及批处理处理完成后跟踪状态和统计信息
-
支持多种启动批处理作业的方式,包括脚本、HTTP 和消息
-
支持运行并发作业
-
支持日志记录、资源管理、跳过和重新启动处理等服务
Spring Batch 基础设施
Spring Batch 采用分层架构设计,包括三个主要组件,即应用程序、核心和基础设施,如下图所示:

应用程序层包含开发者编写的代码,用于使用 Spring Batch 运行批处理作业。
批处理核心层包含 JobLauncher、Job 和 Step 等核心运行时类,这些类对于启动和控制批处理作业是必要的。此层与应用程序层和批处理基础设施层交互以运行批处理作业。
批处理基础设施层包含常见的读取器、写入器和服务。应用程序和批处理核心都是建立在基础设施之上的。它们引用基础设施以获取运行批处理作业所需的信息。
Spring Batch 作业执行涉及多个组件。下一节将讨论这些组件及其关系。
Spring Batch 组件
下图表示 Spring Batch 作业组件及其之间的关系:

JobLauncher 是负责启动作业的接口。当作业首次启动时,JobLauncher 会验证在 JobRepository 中该作业是否已经执行以及执行作业前 Job 参数的有效性。
作业是实际要执行的批处理过程。可以在 XML 或 Java 程序中配置 Job 参数。
JobInstance是每个周期作业的逻辑实例。如果一个JobInstance执行失败,相同的JobInstance可以再次执行。因此,每个JobInstance可以有多个作业执行。
JobExecution是单个作业运行的表示。JobExecution包含正在执行的作业的运行信息,例如status、startTime、endTime、failureExceptions等等。
JobParameters是用于批处理作业的参数集。
Step是批处理作业的顺序阶段。Step包含批处理作业的定义和控制信息。以下图表示批处理作业中的多个步骤。每个Step由三个活动组成,即数据读取、处理和写入,分别由ItemReader、ItemProcessor和ItemWriter处理。每条记录被读取、处理(可选)并写入系统。
StepExecution是单个Step运行的表示。StepExecution包含步骤的运行信息,例如status、startTime、endTime、readCount、writeCount、commitCount等等。

JobRepository为JobLauncher、Job和Step实现提供创建、检索、更新和删除(CRUD)操作。
ItemReader是Step检索操作的抽象表示。ItemReader一次读取一个项目。
ItemProcessor是ItemReader读取的项目业务处理的抽象表示。ItemProcessor仅处理有效的项目,如果项目无效则返回null。
ItemWriter是Step输出操作的抽象表示。ItemWriter一次写入一个批次或一批项目。
在下一节中,我们将使用对这些组件的理解,并使用基本的 Spring Batch 作业组件开发一个简单的批处理应用程序。还包括该应用程序的代码片段。
作业设计和执行
Spring Batch 可以通过多种方式配置到项目中,包括包含下载的 ZIP 发行版和从 Git 检出,或者使用 Maven 进行配置。在我们的例子中,我们将使用 Maven 配置。您应该在系统上直接安装 Maven 或使用基于 IDE 的插件(我们在这个例子中使用 Eclipse)。请参阅www.eclipse.org/m2e/以在 Eclipse IDE 中集成 Maven。Eclipse 的最新版本都预装了此插件;在安装之前请验证这一点。
Spring Batch 作业可以通过多种方式启动,包括以下几种:
-
从命令行启动作业
-
使用作业调度器启动作业
-
从 Java 程序启动作业
-
从 Web 应用程序启动作业
对于这个示例程序,我们是从一个简单的 Java 程序中启动批处理作业。
以下是通过 Spring Batch 运行第一个批处理作业的步骤,包括代码片段:
-
创建一个 Maven 启用的 Java 项目(让我们称它为
SpringBatch)。Maven 是用于有效管理项目的软件。pom.xml文件是 Maven 的配置文件,用于包含任何 API 依赖项。有专门的 Maven 原型可以创建示例项目。Maven 的位置是mvnrepository.com/artifact/org.springframework.batch/spring-batch-archetypes。 -
在项目的
root目录下配置pom.xml,使其包含所需的 Maven 依赖项,如下所示:-
带批处理的 Spring 框架
-
log4j用于日志记录 -
使用 JUnit 测试应用程序
-
Commons Lang 辅助工具用于
java.langAPI -
HyperSQL 数据库(HSQLDB)能够使用 HSQLDB 运行,HSQLDB 是一个用 Java 编写的数据库管理系统
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>batch</groupId> <artifactId>SpringBatch</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <spring.framework.version>3.2.1.RELEASE </spring.framework.version> <spring.batch.version>3.0.2.RELEASE </spring.batch.version> </properties> <dependencies> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>org.springframework.batch</groupId> <artifactId>spring-batch-core</artifactId> <version>${spring.batch.version}</version> </dependency> <dependency> <groupId>org.springframework.batch</groupId> <artifactId>spring-batch-infrastructure</artifactId> <version>${spring.batch.version}</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.framework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.framework.version}</version> </dependency> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>1.8.0.7</version> </dependency> </dependencies> </project>
-
-
在
src\main\resources目录下创建log4j.xml文件,以以下内容进行日志记录,这将生成格式化的控制台输出:<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration > <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender"> <param name="Target" value="System.out"/> <param name="Threshold" value="INFO" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d %-5p %c - %m%n"/> </layout> </appender> <logger name="org.springframework" additivity="false"> <level value="INFO"/> <appender-ref ref="CONSOLE"/> </logger> <root> <level value="DEBUG"/> <appender-ref ref="CONSOLE"/> </root> </log4j:configuration> -
在
src\main\resources\batch目录下包含配置文件(context.xml),内容如下。上下文配置包括jobRepository、jobLauncher和transactionManager配置。在此配置中,我们将批处理配置为默认模式。<?xml version="1.0" encoding="UTF-8"?> <beans:beans xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-3.0.xsd"> <beans:bean id="jobRepository" class="org.springframework.batch.core.repository. support.MapJobRepositoryFactoryBean"> <beans:property name="transactionManager" ref="transactionManager"/> </beans:bean> <beans:bean id="jobLauncher" class="org.springframework.batch.core.launch.support. SimpleJobLauncher"> <beans:property name="jobRepository" ref="jobRepository" /> </beans:bean> <beans:bean id="transactionManager" class="org.springframework.batch.support.transaction. ResourcelessTransactionManager"/> </beans:beans> -
在
src\main\resources\batch目录下包含作业配置(firstBatch.xml),内容如下。批处理作业配置包括使用 Java 程序配置批处理作业的步骤和任务。<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns ="http://www.springframework.org/schema/batch" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-3.0.xsd"> <beans:import resource="context.xml" /> <beans:bean id="firstBatch" class=" batch.FirstBatch"/> <step id="firstBatchStepOne"> <tasklet ref="firstBatch"/> </step> <job id="firstBatchJob"> <step id="stepOne" parent="firstBatchStepOne"/> </job> </beans:beans> -
在
src\main\java\batch目录下编写第一个作业的任务(步骤中的处理策略)FirstBatch.java,内容如下。此任务程序在firstBatch.xml配置文件中作为Job下任务引用的参考。package batch; import org.apache.log4j.Logger; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.Tasklet; import org.springframework.batch.repeat.RepeatStatus; public class FirstBatch implements Tasklet { static Logger logger = Logger.getLogger("FirstBatch"); public RepeatStatus execute(StepContribution arg0, ChunkContext arg1) throws Exception { logger.info("** First Batch Job is Executing! **"); return RepeatStatus.FINISHED; } } -
在
src\main\java\batch目录下编写执行批处理作业的 Java 程序(ExecuteBatchJob.java),内容如下。通过此程序,我们访问作业配置文件,并从配置文件中识别JobLauncher和Job豆。通过传递作业和jobParameters,从JobLauncher的run方法中调用JobExecution。如前所述,我们可以从以下任一选项运行批处理作业,包括命令行、作业调度器、Web 应用程序或简单的 Java 程序。在这里,我们使用一个简单的 Java 程序来运行我们的第一个作业。
package batch; import org.apache.log4j.Logger; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class ExecuteBatchJob { static Logger logger = Logger.getLogger("ExecuteBatchJob"); public static void main(String[] args) { String[] springConfig = {"batch/firstBatch.xml"}; ApplicationContext context = new ClassPathXmlApplicationContext(springConfig); JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher"); Job job = (Job) context.getBean("firstBatchJob"); try { JobExecution execution = jobLauncher.run(job, new JobParameters()); logger.info("Exit Status : " + execution.getStatus()); } catch (Exception e) { e.printStackTrace(); } finally { if (context != null) { context = null; } } logger.info("Done"); } } -
以下是
SpringBatch项目包含上述资源后生成的文件夹结构![作业设计和执行]()
通过构建路径属性将
src/main/java和src/main/resources添加到项目源中,如下截图所示:![作业设计和执行]()
-
使用 Maven 安装构建项目并运行
ExecuteBatchJobJava 程序,以在控制台上打印批处理作业执行状态:2014-06-01 17:02:29,548 INFO org.springframework.batch.core.launch.support.SimpleJobLauncher - Job: [FlowJob: [name=firstBatchJob]] launched with the following parameters: [{}] 2014-06-01 17:02:29,594 INFO org.springframework.batch.core.job.SimpleStepHandler - Executing step: [stepOne] 2014-06-01 17:02:29,599 INFO ** First Batch Job is Executing! ** 2014-06-01 17:02:29,633 INFO org.springframework.batch.core.launch.support.SimpleJobLauncher - Job: [FlowJob: [name=firstBatchJob]] completed with the following parameters: [{}] and the following status: [COMPLETED] 2014-06-01 17:02:29,637 INFO Exit Status :COMPLETED 2014-06-01 17:02:29,639 INFO Done
按照之前提到的步骤,我们使用 Spring Batch 配置了我们的第一个批处理作业,并成功从 Java 程序中执行了它。
摘要
在本章中,我们学习了批处理应用、实时批处理应用以及标准批处理应用应具备的功能。我们还学习了 Spring Batch 应用以及 Spring Batch 技术提供的特点、高级 Spring Batch 架构以及 Spring Batch 作业执行中涉及到的组件,以及这些组件之间的关系。我们通过开发一个简单的批处理应用并成功运行程序来完成本章内容。
在下一章中,我们将学习如何使用 XML 和 EL 配置批处理作业,以及从命令行和应用程序执行批处理作业。我们还将讨论批处理作业的调度。
第二章 使用 Spring Batch 作业入门
在上一章中,我们学习了批量应用程序、Spring Batch 的提供和架构,以及如何构建 Spring Batch 应用程序以运行批量作业。理解框架及其组件的细节对于能够有效地为业务需求配置它们非常重要。基于 XML 和注解的配置使 Spring Batch 的编程更加高效和灵活。
一些应用程序期望配置能够灵活适应它们遵循的编程风格。不同的程序需要以不同的方式触发批量作业,包括命令行和调度程序,或者程序本身的一部分。如果需要,优雅地停止执行批量作业也很重要。
在本章中,我们将涵盖以下主题:
-
Spring Batch XML 功能
-
配置作业、事务和存储库
-
EL 和监听器
-
从命令行和 Web 应用程序执行作业
-
调度程序
Spring Batch XML 功能
Spring Batch XML 配置是 Spring Batch 编程最重要的方面。Spring Batch 有独特的 XML 术语和命名空间。理解这些术语并使用正确的实体集有助于构建高效的批量应用程序。
Spring Batch XML 命名空间
Spring Batch 有专门的 XML 命名空间支持,以提供舒适的配置。Spring XML 应用程序上下文文件需要以下声明来激活命名空间:
<beans xmlns:batch="http://www.springframework.org/schema/batch"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch-3.0.xsd">
<batch:job id="readDetailsJob">
...
</batch:job>
</beans>
命名空间配置提供了可以在上下文文件中配置详细信息的前缀。在先前的示例中,我们使用 batch 作为配置作业的前缀。前缀是仅针对本文件的标识符。可以为命名空间配置使用任何有效的名称作为前缀。如果配置了任何没有前缀的命名空间,则被视为默认命名空间,并且应该配置没有前缀的元素以使用默认前缀。在上一章中,我们配置了 batch 作为默认前缀,因此我们直接配置了作业和步骤。
Spring Batch XML 标签
Spring Batch XML 配置定义了批量作业的流程。以下是一些重要的 Spring Batch 标签及其描述:
-
job:这定义了一个由一系列步骤和步骤之间的转换组成的作业。该作业将在封装的 bean 工厂中作为类型为Job的组件公开,可以使用JobLauncher启动。 -
step:这定义了一个由步骤支持的作业处理阶段。 -
tasklet:这声明了任务策略(步骤中的处理策略)的实现。可以通过配置一个块或通过配置对Tasklet接口的引用来实现。 -
chunk:这声明拥有步骤将执行面向数据块的处理(一次读取数据并创建要写入的组),委托定义数据块的内容,并配置面向数据块的组件。 -
job-repository:使用关系数据存储配置JobRepository(负责持久化批处理元数据实体的存储库)。它由其他组件(如job和step实现)需要。 -
flow:这定义了一个由一系列步骤和步骤之间的转换组成的流程。
配置作业、事务和存储库
如前所述,我们可以通过 XML 配置本身方便地配置 Spring Batch 作业。作业是配置中的主要元素,以下图显示了配置中组件的层次结构:

每个工作可以包含多个步骤,每个步骤包含任务单元,每个任务单元包含数据块。每个组件都有定义为其子元素的独立元素。以下是一个此类批量作业的语法:
<beans xmlns:batch ... >
<batch:job id="jobId">
<batch:step id="stepId">
<batch:tasklet ref="beanReference">
<batch:chunk reader="dataReader"
processor="dataProcessor"
writer="dataWriter" commit-interval="500" />
</batch:tasklet>
</batch:step>
<batch:step>
...
</batch:step>
</batch:job>
</beans>
作业配置
作业是批处理应用程序配置中的根元素。作业定义了要执行的批处理作业,包括作业存储库的配置以及是否可重启等属性。以下为job元素的属性:
-
id:这是job元素的唯一标识符。 -
abstract:这用于配置作业是否为抽象的,也就是说,它本身不打算被实例化,而是作为具体子作业定义的父级。默认情况下为false。 -
increment:这是对JobParametersIncrementerbean 定义的引用。这将通过修改前一组参数以使其适用于新的运行作为next实例来提供一组新参数。 -
job-repository:这是要使用的JobRepository的 bean 名称。此属性不是必需的,默认为jobRepositorybean。 -
parent:这是要继承的父作业的名称。 -
restartable:这定义了在失败的情况下作业是否应该可重启。如果作业不应该重启,请将此设置为false。默认情况下为true。
可以将类型为DefaultJobParametersValidator的验证器配置为作业配置的一部分,以验证简单和可选参数。以下是一个此类配置的片段:
<beans xmlns:batch ... >
<batch:job id="jobId">
<batch:step id="stepId">
...
</batch:step>
<batch:validator ref="validatorId"/>
</batch:job>
<bean id="validatorId" class="beans.JobParametersValidator">
<property name="Keys">
<set>
<value>keyValues</value>
</set>
</property>
</bean>
</beans>
对于复杂的约束,也可以实现validator接口。
步骤配置
步骤是作业的第一个子元素。作业可以包含多个步骤。以下是在配置多个步骤的不同方法:
-
多线程步骤(单进程):Spring Batch 允许您将工作块并行执行作为单个进程的一部分。当有大量数据需要在线程中处理时,每个工作块处理一组记录。
![步骤配置]()
启动并行处理的最简单方法是向步骤配置中添加
taskExecutor作为任务 let 的属性。<step id="loading"> <tasklet task-executor="taskExecutor">...</tasklet> </step> -
并行步骤(单进程):这是在单个进程中处理多个步骤的机制。
![步骤配置]()
以下是为配置并行步骤的代码片段:
<job id="jobId"> <split id="splitId" task-executor="taskExecutor" next="step3"> <flow> <step id="step1" next="step2"/> <step id="step2"/> </flow> </split> <step id="step3"/> </job> <beans:bean id="taskExecutor" class="TaskExecutor"/> -
步骤的远程分块(多进程):这将在多个进程中分割步骤处理,通过中间件相互通信。Spring Batch 的一个步骤作为主步骤,相应中间件的监听器作为奴隶步骤。当主组件作为一个单独的进程运行时,奴隶是多个远程进程。
![步骤配置]()
-
步骤分区(单进程或多进程):分区是将一个步骤配置为具有子步骤的过程。超级步骤是主步骤,子步骤是奴隶步骤。奴隶步骤必须完成执行才能将主步骤视为完成。
![步骤配置]()
以下步骤元素的属性:
-
id:这是step元素的唯一标识符 -
next:这是一个指定当前步骤之后要执行的下一个步骤的快捷方式 -
parent:这是父步骤的名称,作业应从中继承 -
allow-start-if-complete:设置为true以允许在步骤已完成的情况下启动步骤
以下是一个示例步骤配置:
<step id="firstBatchStepOne">
<tasklet ref="firstBatch"/>
</step>
<job id="firstBatchJob">
<step id="stepOne" parent="firstBatchStepOne"/>
</job>
<bean id="firstBatch" class="FirstBatch"/>
Tasklet 配置
Tasklet 是步骤元素的子元素,可以用来指定作为步骤一部分的可重复和事务性的步骤过程。
以下tasklet元素的属性:
-
ref:这是对实现Tasklet接口的 bean 定义的引用。 -
allow-start-if-complete:设置为true以允许在步骤已完成的情况下启动步骤。 -
method:这是任务 let 执行的指定方法。 -
start-limit:这是步骤可能启动的最大次数。 -
task-executor:任务执行器负责执行任务。 -
throttle-limit:这是可以排队进行并发处理的任务的最大数量,以防止线程池过载。默认值为4。 -
transaction-manager:这是将要使用的交易管理器的 bean 名称。默认为transactionManager,如果未指定。
以下是一个带有任务 let 的示例作业配置:
<step id="firstBatchStepOne">
<tasklet ref="firstBatch" start-limit="6">
...
</tasklet>
</step>
<job id="firstBatchJob">
<step id="stepOne" parent="firstBatchStepOne"/>
</job>
<bean id="firstBatch" class="FirstBatch"/>
块配置
块是 tasklet 的子元素,可用于执行读写处理。与其它元素的配置相比,块配置涉及更多的数据 Bean。
以下为块元素的属性:
-
reader:这是用于过程并实现ItemReader接口的项目读取器的 Bean 名称。 -
processor:这是用于过程并实现ItemProcessor接口的项目处理器的 Bean 名称。 -
writer:这是用于过程并实现ItemWriter接口的项目写入器的 Bean 名称。 -
cache-capacity:这是重试策略中缓存的容量。 -
chunk-completion-policy:当此策略决定完成时,将提交事务。默认为SimpleCompletionPolicy,块大小等于commit-interval属性。 -
commit-interval:在调用commit进行事务提交之前将要处理的项目数量。设置此属性或chunk-completion-policy属性,但不能同时设置两者。 -
processor-transactional:这决定了处理器是否具有事务感知性。 -
reader-transactional-queue:这决定了读取器是否是事务队列。 -
retry-limit:这是处理项目重试的最大次数。 -
retry-policy:这是重试策略的 Bean 规范。如果指定,则忽略retry-limit和retryable异常。 -
skip-limit:这是允许跳过的最大项目数量。 -
skip-policy:这是跳过策略的 Bean 规范。如果指定,则忽略skip-limit和skippable异常。
以下是一个带有 tasklet 块的示例作业配置:
<step id="firstBatchStepOne">
<tasklet ref="firstBatch">
<chunk reader="itemReader" processor="itemProcessor" writer="itermWriter" commit-interval="150"/>
</tasklet>
</step>
<job id="firstBatchJob">
<step id="stepOne" parent="firstBatchStepOne"/>
</job>
<bean id="firstBatch" class="FirstBatch"/>
<bean id="itemReader" class="ItemReader"/>
<bean id="itemProcessor" class="ItemProcessor"/>
<bean id="itemWriter" class="ItemProcessor"/>
块配置可以添加异常跳过和重试元素作为其子组件。跳过和重试配置的skippable-exception-classes和retryable-exception-classes元素。Bean 配置也可以注解以简化 Spring Batch 配置。
事务配置
事务配置是 Spring Batch 的关键方面之一。Spring 事务管理器是事务的配置。Spring 为不同的规范提供不同的事务管理器;对于 JDBC 是DataSourceTransactionManager,对于 JPA 是JpaTransactionManager。
Spring Batch 允许我们将transaction-attributes元素配置为块子元素,以设置事务的隔离和传播级别。
可以选择不需要执行回滚操作异常。这些异常可以通过将include元素作为no-rollback-exception-classes元素的子元素进行配置,而no-rollback-exception-classes元素是 tasklet 的子元素。
以下是一个带有事务管理器的示例作业配置:
<step id="firstBatchStepOne">
<tasklet ref="firstBatch" transaction-manager="transactionManager">
...
</tasklet>
</step>
<job id="firstBatchJob">
<step id="stepOne" parent="firstBatchStepOne"/>
</job>
<bean id="firstBatch" class="FirstBatch"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="DataSource"/>
</bean>
任务存储库配置
作业存储库维护与作业执行相关的信息。它还维护批处理作业的状态。Spring Batch 提供了两种类型的作业存储库:内存存储库和持久存储库。
内存存储库允许作业针对相同的作业配置和参数多次运行。内存存储库是易变的,因此不允许在 JVM 实例之间重启。它也不能保证具有相同参数的两个作业实例将并发启动,因此不适合多线程作业或本地分区步骤。它可以使用MapJobRepositoryFactoryBean进行配置。
它需要事务管理器在存储库中进行回滚语义处理,并处理在事务数据库中定义的业务逻辑。
以下是一个示例内存存储库配置:
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager-ref"
ref="transactionManager"/>
</bean>
<bean id="transactionManager"
class="org.springframework.batch.support.transaction.ResourcelessTransactionManager"/>
<job id="deductionsJob" job-repository="jobRepository">
...
</job>
持久 存储库可以使用job-repository元素进行配置,以在数据库上执行持久数据库操作。数据源可以使用任何 API 进行配置,例如,我们在以下配置中使用了 Apache Commons BasicDataSource。
以下是一个示例持久存储库配置:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${batch.jdbc.driver}" />
<property name="url" value="${batch.jdbc.url}" />
<property name="username" value="${batch.jdbc.user}" />
<property name="password" value="${batch.jdbc.password}" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" lazy-init="true">
<property name="dataSource" ref="dataSource" />
</bean>
<job-repository id="jobRepository" data-source="dataSource"
transaction-manager="transactionManager"/>
EL 和监听器
从版本 3 开始,Spring Batch 提供了一项有趣的功能:表达式语言(EL)。Spring 表达式语言(SpEL)允许我们在运行时从执行上下文中捕获值,从而使 XML 配置动态化。SpEL 可以从属性和 bean 中解析表达式。这种运行时捕获行为允许作业访问延迟绑定的配置。
以下是一个示例 SpEL 配置:
<bean id="processBean" class="JobProcessBean" scope="step">
<property name="name" value="#{jobParameters[name]}"/>
</bean>
监听器
Spring Batch 可以通过监听器识别一组附加事件。监听器可以组合使用,以识别不同级别的事件。以下是由 Spring Batch 为批处理提供的各种监听器类型。
-
作业监听器:它们识别作业级别的事件
-
步骤监听器:它们识别步骤级别的事件
-
项目监听器:它们识别项目重复和重试事件
作业监听器
作业监听器识别作业级别发生的事件。可以通过以下方式配置作业监听器:
-
实现 JobExecutionListener: 以下是一个使用
JobExecutionListener实现的示例监听器配置:import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecutionListener; public class JobStatusListener implements JobExecutionListener { public void beforeJob(JobExecution jobExecution) { System.out.println("Job: " + jobExecution.getJobInstance().getJobName() + " is beginning"); } public void afterJob(JobExecution jobExecution) { System.out.println("Job: " + jobExecution.getJobInstance(). getJobName() + " has completed"); System.out.println("The Job Execution status is: " + jobExecution.getStatus()); } }以下是对先前定义的监听器的 XML 配置:
<job id="employeeDeductionsJob"> <listeners> <listener ref="jobStatusListener"/> </listeners> </job> -
使用注解: 以下是一个使用注解的示例监听器配置:
import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.annotation.AfterJob; import org.springframework.batch.core.annotation.BeforeJob; public class JobStatusListener { @BeforeJob public void beforeJob(JobExecution jobExecution) { System.out.println("Job: " + jobExecution.getJobInstance().getJobName() + " is beginning"); } @AfterJob public void afterJob(JobExecution jobExecution) { System.out.println("Job: " + jobExecution.getJobInstance().getJobName() + " has completed"); System.out.println("The Job Execution status is: " + jobExecution.getStatus()); } }
配置注解监听器的方式与JobExecutionListener配置相同。
步骤监听器
就像作业监听器捕获作业的执行状态一样,步骤也有特定的监听器来捕获不同的事件。实现这一组监听器的方式与作业监听器相同(通过实现相应的接口或使用注解),只是监听器元素必须配置为step元素的子元素。
以下是一个带有覆盖方法的步骤监听器列表:
-
StepExecutionListener:该监听器分别使用beforeStep和afterStep方法来识别步骤执行事件的前后情况。 -
ChunkListener:该监听器分别使用beforeChunk和afterChunk方法来识别块执行事件的前后情况。 -
ItemReadListener:该监听器分别使用beforeRead、afterRead和onReadError方法来识别读取前后项的情况,以及在发生异常时读取项事件。 -
ItemProcessListener:该监听器分别使用beforeProcess、afterProcess和onProcessError方法来识别ItemProcessor获取项目前后的状态,以及处理器抛出异常时的情况。 -
ItemWriteListener:该监听器分别使用beforeWrite、afterWrite和onWriteError方法来识别写入项的前后情况,以及在发生异常时写入项事件。 -
SkipListener:该监听器分别使用onSkipInRead、onSkipInProcess和onSkipInWrite方法来识别读取、处理或写入项目的跳过事件。
项监听器
项监听器识别重试和重复事件。这些监听器可以像作业或步骤监听器一样进行配置。
以下是一个带有覆盖方法的项监听器列表:
-
RepeatListener:该监听器分别使用before和after方法来识别每个重复事件的前后情况。它分别使用open和close方法来识别第一次和最后一次重复事件。它还使用onError方法来识别每次失败事件。 -
RetryListener:该监听器分别使用open和close方法来识别第一次和最后一次尝试事件,无论重试是成功还是失败。它还使用onError方法来识别每次失败事件。
从命令行和 Web 应用程序执行作业
在第一章中,我们学习了如何使用 Spring Batch 配置和运行一个简单的批处理作业应用程序,通过从 Java 程序中启动作业来实现。Spring Batch 的基于 Java 的 API 通过不同的方式调用批处理作业,使得作业启动变得非常方便。在本节中,让我们探讨以不同方式启动批处理作业的概念,以及优雅地停止批处理作业。
作业启动器
Spring Batch 通过JobLauncher使启动批处理工作变得更容易。JobLauncher代表一个简单的接口,用于使用给定的一组工作参数启动工作。JobLauncher的运行方法接受类型为 Spring bean 的Job和JobParameters作为参数,并调用批处理工作执行。
以下是我们之前章节中使用JobLauncher启动工作的代码片段:
String[] springConfig = {"batch/firstBatch.xml"};
context = new ClassPathXmlApplicationContext(springConfig);
JobLauncher jobLauncher = (JobLauncher) context.getBean("jobLauncher");
Job job = (Job) context.getBean("firstBatchJob");
JobExecution execution = jobLauncher.run(job, new JobParameters());
System.out.println("Exit Status : " + execution.getStatus());
我们可以使用JobParametersBuilder来构建不同类型的JobParameter。可以通过以下语法配置带有持久化工作存储库的JobLauncher:
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository"/>
</bean>

从JobLauncher的run方法启动一个工作将调用job的execute方法,并在执行后确认工作执行状态(FINISHED或FAILED),这是一个同步过程。
然而,在特定的业务场景中,我们希望JobLauncher调用并将流程转交给另一个控制器以使其异步,这样就可以触发多个流程。如果与JobLauncher一起配置,TaskExecutor有助于这种情况。

以下是将SimpleJobLauncher与taskExecutor配置以使流程异步的语法:
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository"/>
<property name="taskExecutor">
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
</property>
</bean>
从命令行启动工作
CommandLineJobRunner使得从命令行启动 Spring Batch 工作变得简单。以下是CommandLineJobRunner工作的步骤:
-
加载适当的
ApplicationContext -
将命令行参数解析为
JobParameters -
根据参数定位适当的工作
-
使用应用程序上下文中提供的
JobLauncher来启动工作
以下是用CommandLineJobRunner启动工作的命令:
> java -classpath "/lib/*" org.springframework.batch.core.launch.support.CommandLineJobRunner firstBatch.xml firstBatchJob schedule.date(date)=2007/05/05
工作执行退出代码表示运行后批处理工作的状态,其中0表示COMPLETED,1表示FAILED,2表示来自命令行工作运行器的错误,例如在提供的上下文中找不到工作。
在 Web 应用程序内部启动工作
到目前为止,我们已经学习了如何从 Java 程序和命令行启动批处理工作。在某些情况下,需要从 Web 应用程序内部启动工作。从应用程序内部生成报告并从具有基于线程的配置的应用程序中触发异步流程的应用程序是这类业务场景。
以下是用 Spring MVC 框架和 Spring 依赖项启动工作的程序:
@Controller
public class JobLauncherController {
@Autowired
JobLauncher jobLauncher;
@Autowired
Job job;
@RequestMapping("/jobLauncher.html")
public void handle() throws Exception {
jobLauncher.run(job, new JobParameters());
}
}
控制器通过在JobLauncherController中配置自动装配的JobController来启动工作。控制器可以通过配置了handle方法的RequestMapping从请求 URL 中调用。
优雅地停止批处理工作
在必要时,可以通过JobOperator接口从程序内部优雅地停止工作。JobOperator提供了工作的 CRUD 操作。
下面的语法是使用JobOperator停止作业的语法:
Set<Long> executions =jobOperator.getRunningExecutions("jobName");
If( executions.iterator().hasNext()) {
jobOperator.stop(executions.iterator().next());
}
JobOperator使用给定的jobName识别正在运行的作业,并通过从执行中获取作业id来调用stop方法。
JobOperator需要配置为程序可用。以下是对jobOperator的示例配置,包括资源、作业探索器(浏览正在运行或历史作业和步骤的入口点)、注册表和存储库属性。
<bean id="jobOperator" class="org.springframework.batch.core.launch.support. SimpleJobOperator">
<property name="jobExplorer">
<bean class=" org.springframework.batch.core.explore.support. JobExplorerFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
</property>
<property name="jobRepository" ref="jobRepository"/>
<property name="jobRegistry" ref="jobRegistry"/>
<property name="jobLauncher" ref="jobLauncher"/>
</bean>
作业配置支持在任务和以块为导向的步骤级别进行停止设置。
调度器
调度器是可以定期启动其他程序的程序。如前所述,Spring Batch 不是一个调度框架。Spring Batch 可以与调度器(如 Quartz/Control-M)协同工作,但不能替代调度器。
以下是一些流行的调度器:
-
Cron: 这是一个基于表达式的作业调度器,可在类 Unix 系统中启动其他程序
-
Control-M: 这是一个适用于分布式计算平台(包括 Unix、Windows、Linux 和 OpenVMS 环境)的批处理调度软件
-
Spring 调度器: 这个来自 Spring 的调度器支持 XML、基于注解或 cron 表达式来启动批处理作业
-
石英(Quartz): 石英是一个功能丰富的开源作业调度库,可以集成到几乎任何 Java 应用程序中
虽然CommandLineJobRunner可以用于 Cron 和 Control-M 启动批处理作业,但 Quartz 和 Spring 调度器可以从应用程序内部以编程方式启动批处理作业。可以根据作业执行的频率和调用方式选择这些选项之一。
摘要
在本章中,我们学习了 Spring Batch 作业及其组件的配置细节,以便能够有效地满足业务需求。我们学习了如何通过基于 XML 和注解的配置使批处理编程更加高效和灵活。我们还学习了不同的启动批处理作业的方式,例如从命令行、Java 程序以及 Web 应用程序中启动,以及如何在程序内部优雅地停止批处理作业。我们通过了解市场上可用的不同作业调度器和哪些启动解决方案可以与这些调度器结合使用来完成本章。
在下一章中,我们将详细了解使用 Spring Batch 读取、处理和写入不同形式的数据。
第三章。与数据一起工作
在上一章中,我们学习了关于批配置、组件和执行模式的知识,以适应不同的业务需求。处理数据,包括读取、处理和写入,是任何类型应用程序的一个基本部分,批处理应用程序也不例外。Spring Batch 提供了读取不同形式数据的能力,按照业务预期的方式处理数据,并将其写回到不同的系统中,这些系统可以轻松地与不同的框架集成。
在本章中,我们将介绍数据处理中涉及到的三个主要操作:
-
数据读取
-
数据处理
-
数据写入
![与数据一起工作]()
前面的图显示了批处理应用程序中处理数据的三个阶段。输入(源)可以是数据库、文件系统(平面文件或 XML)或来自 Web 服务的数据。应用程序需要从输入(源)读取数据,处理它,并将其写入输出(目标)系统。输出(目标)可以是数据库或文件系统(平面文件或 XML 文件)。在处理阶段,读取的数据格式可以验证并转换为所需的格式,然后再写入输出。现在让我们逐一检查这些阶段。
数据读取
Spring Batch 提供了从不同来源读取不同形式数据的配置,包括平面文件、XML 和关系数据库。它还支持为未提供规格的格式提供自定义读取器配置。
ItemReader
Spring Batch 提供了一个名为ItemReader的接口,用于从不同形式读取大量数据,包括以下内容:
-
平面文件:这些通常有两种类型:固定宽度和基于分隔符的字符文件
-
XML:这种格式用于不同形式的应用数据
-
数据库:这维护了一组类似或不同信息组的记录
以下是对ItemReader接口的定义:
public interface ItemReader<T> {
T read() throws Exception, UnexpectedInputException, ParseException;
}
让我们来探讨一下ItemReader如何帮助我们读取不同格式的数据。
从平面文件读取数据
平面文件配置有两种格式,即固定宽度和分隔符。固定宽度文件中的每个字段细节都配置了预定义的固定宽度,而分隔符文件中的字段使用特定的字符(通常为制表符)来分隔它们与其他字段。
固定宽度文件
固定宽度文件通常具有其字段的预定义规格,包括每个字段在文件中应占用的长度,以及从哪一位置到哪一位置在一行上。
以下是我们想要读取的固定宽度文件的这样一个规格:
Field Length Position
ID 2 characters 1 to 2
Last name 10 characters 3 to 12
First name 10 characters 13 to 22
Designation 10 characters 23 to 32
Department 15 characters 33 to 47
Year of joining 4 characters 48 to 51
使用前面的规格,让我们填充一个包含员工信息的样本文件,该文件是一个固定宽度文件,如下所示(employees.txt):
11Alden Richie associate sales 1996
12Casey Stanley manager sales 1999
13Rex An architect development 2001
14Royce Dan writer development 2006
15Merlin Sams accountant finance 1995
16Olin Covey manager finance 1989
如果要生成与该规格相对应的 Java 对象,我们可以创建以下表示的普通 Java 对象(POJO):
package batch;
import java.io.Serializable;
public class Employee implements Serializable {
int id;
String lastName;
String firstName;
String designation;
String department;
int yearOfJoining;
public int getID() {
return id;
}
public void setID(int id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getDesignation() {
return designation;
}
public void setDesignation(String designation) {
this.designation = designation;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public int getYearOfJoining() {
return yearOfJoining;
}
public void setYearOfJoining(int yearOfJoining) {
this.yearOfJoining = yearOfJoining;
}
public String toString() {
return "Employee: ID="+ id + ", Last Name="+ lastName +
", First Name="+ firstName + ", Designation="+ designation +
", Department="+ department + ",Year of joining="+
yearOfJoining;
}
}
FlatFileItemReader
FlatFileItemReader提供了一种读取不同类型的平面文件并通过以下方式解析它们的方法:
-
resource:这表示需要从中读取数据的文件。 -
lineMapper:这表示将ItemReader读取的字符串转换为 Java 对象的映射器。 -
linesToSkip:当文件在记录之前有标题内容时使用。这是我们想要忽略文件顶部的行数。
LineMapper
LineMapper接口允许我们在每次迭代中传递行号来读取文件中的每一行。它是 Spring Batch 的LineMapper标准实现。以下为LineMapper接口:
public interface LineMapper<T> {
T mapLine(String line, int lineNumber) throws Exception;
}
LineTokenizer接口将LineMapper读取的行转换为字段集(FieldSet)。DelimitedLineTokenizer、FixedLengthTokenizer和PatternMatchingCompositeLineTokenizer是 Spring Batch 对LineTokenizer的支持实现。以下为LineTokenizer接口:
public interface LineTokenizer {
FieldSet tokenize(String line);
}
FieldSetMapper接口允许我们将从读取的字符串映射到Employee对象中的每个字段。以下为FieldSetMapper接口:
public interface FieldSetMapper<T> {
T mapFieldSet(FieldSet fieldSet);
}
我们可以为我们的Employee数据实现FieldSetMapper,如下所示:
package batch;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
class EmployeeFieldSetMapper implements FieldSetMapper<Employee> {
public Employee mapFieldSet(FieldSet fieldSet) {
Employee emp = new Employee();
emp.setID(fieldSet.readInt("ID"));
emp.setLastName(fieldSet.readString("lastName"));
emp.setFirstName(fieldSet.readString("firstName"));
emp.setDesignation(fieldSet.readString("designation"));
emp.setDepartment(fieldSet.readString("department"));
emp.setYearOfJoining(fieldSet.readInt("yearOfJoining"));
return emp;
}
}
数据可以作为批处理作业的一部分从文件中读取,如下面的代码片段所示:
FlatFileItemReader<Employee> itemReader = new FlatFileItemReader<Employee>();
itemReader.setResource(new FileSystemResource("employees.txt"));
// FixedLengthTokenizer reads each field of length specified
DefaultLineMapper<Employee> lineMapper = new DefaultLineMapper<Employee>();
FixedLengthTokenizer lineTokenizer = new FixedLengthTokenizer();
lineMapper.setLineTokenizer(lineTokenizer);
lineMapper.setFieldSetMapper(new EmployeeFieldSetMapper());
itemReader.setLineMapper(lineMapper);
itemReader.open);
Employee ;
while (employee != null) {
employee = itemReader.read();
if (employee == null) {
return RepeatStatus.FINISHED;
}
System.out.println(employee.toString());
}
setResource()方法将平面文件资源发送到FlatFileItemReader。LineTokenizer接口可以与字段名称和范围一起使用,使用setNames()和setColumns()方法分别设置文件上的起始和结束位置作为数组。每次在itemReader上调用read()方法时,它都会读取一行并移动到下一行。
以下是从固定宽度平面文件读取数据并将其捕获到 Java 对象后的批处理程序输出:
** Executing the fixed width file read batch job! **
Employee: ID=11, Last Name=Alden, First Name=Richie, Designation=associate, Department=sales,Year of joining=1996
Employee: ID=12, Last Name=Casey, First Name=Stanley, Designation=manager, Department=sales,Year of joining=1999
Employee: ID=13, Last Name=Rex, First Name=An, Designation=architect, Department=development,Year of joining=2001
Employee: ID=14, Last Name=Royce, First Name=Dan, Designation=writer, Department=development,Year of joining=2006
Employee: ID=15, Last Name=Merlin, First Name=Sams, Designation=accountant, Department=finance,Year of joining=1995
Employee: ID=16, Last Name=Olin, First Name=Covey, Designation=manager, Department=finance,Year of joining=1989
Exit Status : COMPLETED
reader、linetokenizer和fieldsetmapper在批处理中作为 bean 使用,如下所示:
<beans:bean id="employeeFile"
class="org.springframework.core.io.FileSystemResource" >
<beans:constructor-arg value=""/>
</beans:bean>
<beans:bean id="employeeReader"
class="org.springframework.batch.item.file.FlatFileItemReader">
<beans:property name="resource" ref="employeeFile" />
<beans:property name="lineMapper">
<beans:bean class="org.springframework.batch.item.file.mapping.
DefaultLineMapper">
<beans:property name="lineTokenizer">
<beans:bean class="org.springframework.batch.item.file.transform.
FixedLengthTokenizer">
<beans:property name="names" value="ID, lastName, firstName, designation, department, yearOfJoining"/>
<beans:property name="columns" value="1-2,3-12,13-22,23-32,33-47,48-51"/>
</beans:bean>
</beans:property>
<beans:property name="fieldSetMapper">
</beans:bean>
</beans:property>
</beans:bean>
</beans:property>
</beans:bean>
<beans:bean id="employee" class="" />batchstep
定界文件
定界符平面文件包含每行中由特定字符分隔的字段。以下是一个定界符文件的示例,每个字段由逗号分隔。让我们以从定界符平面文件中读取员工详细信息为例。
以下为文件的规格说明:
-
ID
-
姓氏
-
名字
-
职称
-
部门
-
入职年份
每个字段都应该用逗号与其他字段分隔。以下是一个示例文件内容(employees.csv):
1,Alden,Richie,associate,sales,1996
2,Casey,Stanley,manager,sales,1999
3,Rex,An,architect,development,2001
4,Royce,Dan,writer,development,2006
5,Merlin,Sams,accountant,finance,1995
6,Olin,Covey,manager,finance,1989
定界文件需要像固定宽度平面文件一样处理,除了在这种情况下使用的LineTokenizer应该是DelimitedLineTokenizer。以下是为了读取作为批处理作业一部分处理的定界符平面文件而实现的 Java 代码:
// Delimited File Read
FlatFileItemReader<Employee> itemReader = new FlatFileItemReader<Employee>();
itemReader.setResource(new FileSystemResource("employees.csv"));
// DelimitedLineTokenizer defaults to comma as its delimiter
DefaultLineMapper<Employee> lineMapper = new DefaultLineMapper<Employee>();
DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer();
lineTokenizer.setNames(new String[] { "ID", "lastName", "firstName", "designation", "department", "yearOfJoining" });
lineMapper.setLineTokenizer(lineTokenizer);
lineMapper.setFieldSetMapper(new EmployeeFieldSetMapper());
itemReader.setLineMapper(lineMapper);
itemReader.open(new ExecutionContext());
employee = itemReader.read();
if (employee == null) {
return RepeatStatus.FINISHED;
}
System.out.println(employee.toString());
}
使用定界符文件,我们不需要设置列的属性。除非定界符是逗号,否则必须设置使用的定界符。执行此程序应将定界符平面文件读入 Java 对象,输出如下:
** Executing the delimited file read batch job! **
Employee: ID=1, Last Name=Alden, First Name=Richie, Designation=associate, Department=sales,Year of joining=1996
Employee: ID=2, Last Name=Casey, First Name=Stanley, Designation=manager, Department=sales,Year of joining=1999
Employee: ID=3, Last Name=Rex, First Name=An, Designation=architect, Department=development,Year of joining=2001
Employee: ID=4, Last Name=Royce, First Name=Dan, Designation=writer, Department=development,Year of joining=2006
Employee: ID=5, Last Name=Merlin, First Name=Sams, Designation=accountant, Department=finance,Year of joining=1995
Employee: ID=6, Last Name=Olin, First Name=Covey, Designation=manager, Department=finance,Year of joining=1989
Exit Status : COMPLETED
在分隔符文件的情况下,ItemReader、LineTokenizer和FieldSetMapper可以作为 bean 配置在批处理中,并在程序中如下使用:
<beans:bean id="employeeFile"
class="org.springframework.core.io.FileSystemResource"
scope="step">
<beans:constructor-arg value="#{jobParameters[employeeFile]}"/>
</beans:bean>
<beans:bean id="employeeFileReader" class="org.springframework.batch.item.file.FlatFileItemReader">
<beans:property name="resource" ref="employeeFile" />
<beans:property name="lineMapper">
<beans:bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<beans:property name="lineTokenizer">
<beans:bean class="org.springframework.batch.item.file.transform.
DelimitedLineTokenizer">
<beans:property name="names" value="ID, lastName, firstName, designation, department, yearOfJoining"/>
<beans:property name="delimiter" value=","/>
</beans:bean>
</beans:property>
<beans:property name="fieldSetMapper">
<beans:bean
class="batch.EmployeeFieldSetMapper"/>
</beans:property>
</beans:bean>
</beans:property>
</beans:bean>
如果文件的行以特定于业务的格式定义,LineTokenizer允许自定义实现和配置。PatternMatchingCompositeLineMapper可用于读取具有复杂模式的文件。例如,如果我们有一个平面文件中的多个记录类型,我们可以使用PatternMatchingCompositeLineMapper为每个记录类型提供分词器,如下所示。
一个包含多个记录类型的示例平面文件:
EMPLOYEE,Steve,Jacob,21,manager,2009
BANKINFO,524851478569,STEVEJACOB,REDROSECITY
ADDRESSINFO,No24,SUNFLOWERWAY,CASAYA
以下是为这种多记录类型进行的 bean 配置:
<bean id="employeeFileLineMapper"
class=" org.springframework.batch.item.file.mapping.PatternMatchingCompositeLineMapper">
<property name="tokenizers">
<map>
<entry key="EMPLOYEE*" value-ref="employeeTokenizer"/>
<entry key="BANKINFO*" value-ref="bankInfoTokenizer"/>
<entry key="ADDRESSINFO*" value-ref="addressInfoTokenizer"/>
</map>
</property>
<property name="fieldSetMappers">
<map>
<entry key="EMPLOYEE*" value-ref="employeeFieldSetMapper"/>
<entry key="BANKINFO*" value-ref="bankInfoFieldSetMapper"/>
<entry key="ADDRESSINFO*" value-ref="addressInfoFieldSetMapper"/>
</map>
</property>
</bean>
PatternMatchingCompositeLineMapper通过模式识别每个行,使用匹配的键让相应的Tokenizer和FieldSetMapper读取和匹配记录。
平面文件读取的异常
以下是从平面文件中可能出现的异常,例如当文件格式不正确、从文件读取数据时出现问题或平面文件中的数据不一致时:
-
FlatFileParseException: 这是FlatFileItemReader在文件读取过程中抛出的异常。 -
FlatFileFormatException: 这是LineTokenizer在数据分词过程中抛出的异常。 -
IncorrectTokenCountException: 如果指定的列数与分词的列数不匹配,则会抛出此异常。 -
IncorrectLineLengthException: 在固定宽度平面文件读取过程中,如果行/字段长度与指定的长度不匹配,则会抛出此异常。
从 XML 读取数据
可扩展 标记语言 (XML) 是一种标记语言,用于定义文档,其中包含人类和机器都能读取的数据。XML 主要用于多个系统相互交互时。
Spring Batch 使用 XML 的流式 API (StAX) 解析器。在 StAX 比喻中,程序入口点是一个表示文档中某一点的游标。应用程序将游标向前移动,'拉取'解析器所需的信息。因此,读取发生在以下图示的文件中 XML 内容的片段:

StaxItemReader让我们解析 XML 文件,考虑到每个片段的根元素是通用的(如上述示例中的employee)。unmarshaller将数据转换为 Java 对象。
以下是对employeeFile和employeeFileReader的 bean 配置:
<beans:bean id="employeeFile"
class="org.springframework.core.io.FileSystemResource" scope="step">
<beans:constructor-arg value="#{jobParameters[employeeFile]}"/>
</beans:bean>
<beans:bean id="employeeFileReader"
class="org.springframework.batch.item.xml.StaxEventItemReader">
<beans:property name="fragmentRootElementName" value="employee" />
<beans:property name="resource" ref="employeeFile" />
<beans:property name="unmarshaller" ref="employeeMarshaller" />
</beans:bean>
我们可以使用不同的反序列化技术,包括 JAXB、XStream 绑定、JiBX 和 XML Beans。我们使用 StAX 作为序列化的引擎。让我们考虑 XStream 绑定及其以下配置:
<bean id="employeeMarshaller"
class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="aliases">
<util:map id="aliases">
<entry key="employee"
value="batch.Employee" />
<entry key="ID" value="java.lang.Integer" />
</util:map>
</property>
</bean>
StaxEventItemReader xmlStaxEventItemReader = ;
Resource resource = ( .getBytes());
Map aliases = new HashMap();
aliases.put("employee","batch.Employee");
aliases.put("ID","java.lang.Integer");
Marshaller marshaller = newXStreamMarshaller();
marshaller.setAliases(aliases);
xmlStaxEventItemReader.setUnmarshaller(marshaller);
xmlStaxEventItemReader.setResource(resource);
xmlStaxEventItemReader.setFragmentRootElementName("employee");
xmlStaxEventItemReader.open(newExecutionContext());
boolean hasNext = true;
Employee employee = null;
while(hasNext) {
employee = xmlStaxEventItemReader.read();
if(employee == null) {
hasNext = false;
}
else{
System.out.println(employee.getName());
}
}
如果有多个文件包含要读取的 XML 细节,我们可以使用MuliResourceItemReader在读取操作中配置多个资源。
从数据库读取数据
数据库以表格的形式包含信息,具有多个列来存储每个字段。如果批处理作业需要从数据库读取数据,可以使用以下两种类型的项读取概念:
-
基于游标的 项目读取:这种读取方式读取每个片段,其中游标依次指向下一个
-
基于页面的 项目读取:这种读取方式将多个记录一起读取,将其视为一个页面
与此相比,基于游标的项读取工作得很好,因为它读取少量数据并处理,除非它们的内存泄漏与系统相关。
JdbcCursorItemReader
要使用基于游标的技巧读取数据,我们可以使用JdbcCursorItemReader。它通过RowMapper(Spring 框架)配置,将数据库中的每个属性匹配到 Java 对象的属性。
员工示例的RowMapper可以按以下方式实现:
public class EmployeeRowMapper implements RowMapper {
public static final String ID_COLUMN = "id";
public static final String LAST_NAME_COLUMN = "lastname";
public static final String FIRST_NAME_COLUMN = "firstname";
public static final String DESIGNATIoN_COLUMN = "designation";
public static final String DEPARTMENT_COLUMN = "department";
public static final String YEAR_OF_JOINING_COLUMN = "yearOfJoining";
public Object mapRow(ResultSet rs, int rowNum) throws SQLException
{
Employee employee = new Employee();
employee.setId(rs.getInt(ID_COLUMN));
employee.setLastName(rs.getString(LAST_NAME_COLUMN));
employee.setFirstName(rs.getString(FIRST_NAME_COLUMN));
employee.setDesignation(rs.getString(DESIGNATION_COLUMN));
employee.setDepartment(rs.getString(DEPARTMENT_COLUMN));
employee.setYearOfJoining(rs.getString(YEAR_OF_JOINING_COLUMN));
return employee;
}
}
使用EmployeeRowMapper从数据库读取数据的 Java 程序可以实现如下:
JdbcCursorItemReader itemReader = new JdbcCursorItemReader();
itemReader.setDataSource(dataSource);
itemReader.setSql("SELECT ID, LASTNAME, FIRSTNAME,DESIGNATION,DEPARTMENT,YEAROFJOINING from EMPLOYEE");
itemReader.setRowMapper(new EmployeeRowMapper());
int counter = 0;
ExecutionContext executionContext = new ExecutionContext();
itemReader.open(executionContext);
Object employee = newObject();
while(employee != null){
employee = itemReader.read();
counter++;
}
itemReader.close(executionContext);
JdbcCursorItemReader和EmployeeRowMapper可以在批处理 XML 中按以下方式配置:
<bean id="itemReader" class=" org.springframework.batch.item.database.JdbcCursorItemReader">
<property name="dataSource" ref="dataSource"/>
<property name="sql" value=" SELECT ID, LASTNAME, FIRSTNAME,DESIGNATION,DEPARTMENT,YEAROFJOINING from EMPLOYEE "/>
<property name="rowMapper">
<bean class="batch.EmployeeRowMapper"/>
</property>
</bean>
可以通过设置忽略警告、获取大小、最大行数、查询超时、验证游标位置等属性以及相应的选项来自定义JdbcCursorItemReader。
如果我们想使用 Hibernate 框架配置数据库读取活动,可以使用HibernateCursorItemReader。基于存储过程的读取操作可以使用StoredProcedureItemReader执行。
JdbcPagingItemReader
在数据库上执行分页模式读取操作可以使用JdbcPagingItemReader。使用JdbcPagingItemReader并配置dataSource、queryProvider和具有不同子句的查询属性,可以按照以下方式进行:
<bean id="itemReader" class=" JdbcPagingItemReader.JdbcPagingItemReader">
<property name="dataSource" ref="dataSource"/>
<property name="queryProvider">
<bean class=" org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean">
<property name="selectClause" value=" SELECT ID, LASTNAME, FIRSTNAME,DESIGNATION,DEPARTMENT,YEAROFJOINING "/>
<property name="fromClause" value="from EMPLOYEE"/>
<property name="whereClause" value="where designation=:designation"/>
<property name="sortKey" value="id"/>
</bean>
</property>
<property name="parameterValues">
<map>
<entry key="designation" value="manager"/>
</map>
</property>
<property name="pageSize" value="100"/>
<property name="rowMapper" ref="employeeMapper"/>
</bean>
使用SqlPagingQueryProviderFactoryBean,我们可以分别设置select、from、where子句,以及要传递的排序键和参数。
Spring Batch 支持不同的对象关系框架以及相应的项读取器,例如为 JPA 提供的JpaPagingItemReader和为 IBatis 提供的IbatisPagingItemReader。
数据处理
Spring Batch 提供了读取输入数据的一种形式,处理它,并以所需形式的输出数据返回的手段。ItemProcessor接口是支持处理活动的接口。
ItemProcessor
Spring Batch 提供了简单的ItemProcessor接口来接收对象,对其进行处理,并将其转换为所需的格式,然后作为另一个对象返回。
以下是对ItemProcessor接口的定义:
public interface ItemProcessor<I, O> {
O process(I item) throws Exception;
}
ValidatingItemProcessor是ItemProcessor的一个实现,它允许我们在处理之前验证输入数据。如果数据未通过验证,则抛出org.springframework.batch.item.validator.ValidationException。例如,Hibernate 这样的框架具有验证框架(hibernate-validator),允许我们为 bean 配置基于注解的验证器。
ItemProcessor可以为Employee示例实现如下:
public class Employee {}
public class Associate {
public Associate (Employee employee) {}
}
public class EmployeeProcessor implements ItemProcessor<Employee,Associate>{
public Associate process(Employee employee) throws Exception {
return new Associate(employee);
}
}
前面的程序接收employee数据对象,转换对象,并返回一个Associate数据对象。
itemProcessor可以配置为以下格式的作业块:
<job id="jobId">
<step name="stepName">
<tasklet>
<chunk reader="itemReaderName" processor="itemProcessorName" writer="itemWriterName" commit-interval="2"/>
</tasklet>
</step>
</job>
链接过程
通过定义多个项处理器并将它们相互调用,可以链式处理活动,如下所示创建compositeItemProcessor:
<job id="jobId">
<step name="stepName">
<tasklet>
<chunk reader="itemReaderName" processor="compositeItemProcessorName" writer="itemWriterName"
commit-interval="2"/>
</tasklet>
</step>
</job>
<bean id="compositeItemProcessorName"
class="org.springframework.batch.item.support.CompositeItemProcessor">
<property name="delegates">
<list>
<bean class="batch.EmployeeProcessor"/>
<bean class="batch.AssociateProcessor"/>
</list>
</property>
</bean>
数据写入
Spring Batch 提供了将读取和处理的写入不同输出的配置。写入器可以轻松地与不同的关系型框架集成。它也可以针对不同的格式进行自定义。
ItemWriter
Spring Batch 提供了一个名为ItemWriter的接口来写入大量数据。以下是对ItemWriter接口的定义:
public interface ItemWriter<T> {
void write(List<? extends T> items) throws Exception;
}
根据我们必须写入数据的目标平台,我们有以下项写入器:
-
平面文件项写入器:这些将内容写入平面文件(固定宽度和分隔符)
-
XML 项写入器:这些将数据写入 XML 文件
-
数据库项写入器:这些将数据写入数据库
平面文件项写入器
从任何现有格式读取的数据可以处理成所需格式,然后写入到多种格式,包括平面文件。以下是一些帮助进行平面文件项写入的 API。
LineAggregator
LineAggregator API 将多个字段连接成一个字符串以写入平面文件。这与读取操作中的LineTokenizer正好相反。
public interface LineAggregator<T> {
public String aggregate(T item);
}
PassThroughLineAggregator
PassThroughLineAggregator是LineAggreagator的一个实现,它认为正在使用的对象已经聚合,并简单地使用toString()方法从对象返回字符串。
public class PassThroughLineAggregator<T> implements LineAggregator<T> {
public String aggregate(T item) {
return item.toString();
}
}
FlatFileItemWriter可以配置为PassThroughLineAggregator,如下所示:
<bean id="itemWriter" class=" org.springframework.batch.item.file.FlatFileItemWriter">
<property name="resource" value="file:target/outputfiles/employee_output.txt"/>
<property name="lineAggregator">
<bean class=" org.springframework.batch.item.file.transform.PassThroughLineAggregator"/>
</property>
</bean>
FieldExtractor
如果对象写入不仅仅是将其字符串形式写入文件,则需要使用FieldExtractor,其中每个对象都转换为字段数组,聚合在一起形成要写入文件的字符串。
public interface FieldExtractor<T> {
Object[] extract(T item);
}
字段提取器主要有两种类型:
-
PassThroughFieldExtractor:对于需要将对象集合仅转换为数组并传递以写入的场景 -
BeanWrapperFieldExtractor:通过为对象的每个字段在写入文件时的字符串位置进行字段级配置,这与BeanWrapperFieldSetMapper正好相反。
BeanWrapperFieldSetExtractor的工作方式如下:
BeanWrapperFieldExtractor<Employee> extractor = new BeanWrapperFieldExtractor<Employee>();
extractor.setEmployees(new String[] { "id", "lastname", "firstname","designation","department","yearofjoining"});
int id = 11;
String lastname = "Alden";
String firstname = "Richie";
String desination = "associate";
String department = "sales";
int yearofjoining = 1996;
Employee employee = new Employee(id, lastname, firstname,designation, department, yearofjoining);
Object[] values = extractor.extract(n);
assertEquals(id, values[0]);
assertEquals(lastname, values[1]);
assertEquals(firstname, values[2]);
assertEquals(designation, values[3]);
assertEquals(department, values[4]);
assertEquals(yearofjoining, values[5]);
写入分隔符文件
如果 Java 对象可以以分隔符文件格式写入平面文件,我们可以像以下示例中那样执行。让我们考虑已经定义的Employee对象。
此对象可以使用 FlatFileItemWriter、DelimitedLineAggregator 和 BeanWrapperFieldExtractor 进行配置,以执行分隔符平面文件,如下所示:
<bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
<property name="resource" ref="outputResource"/>
<property name="lineAggregator">
<bean class=" org.springframework.batch.item.file.transform.DelimitedLineAggregator">
<property name="delimiter" value=","/>
<property name="fieldExtractor">
<bean class=" org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
<property name="employees" value="id,lastname,firstname,designation,department,yearofjoining"/>
</bean>
</property>
</bean>
</property>
</bean>
写入固定宽度文件
Spring Batch 在 FormatterLineAggregator 的帮助下支持固定宽度文件写入。考虑到与分隔符平面文件写入相同的示例数据,我们可以使用以下配置执行固定宽度文件写入:
<bean id="itemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter">
<property name="resource" ref="outputResource"/>
<property name="lineAggregator">
<bean class=" org.springframework.batch.item.file.transform.FormatterLineAggregator">
<property name="fieldExtractor">
<bean class=" org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
<property name="employees" value=" id,lastname,firstname,designation,department,yearofjoining"/>
</bean>
</property>
<property name="format" value="%-2d%-10s%-10s%-10s%-15s%-4d"/>
</bean>
</property>
</bean>
格式值基于以下列出的格式化转换形成,其中 arg 代表转换的参数:
| 转换 | 类别 | 描述 |
|---|---|---|
b, B |
通用 | 这将布尔值转换为字符串格式。对于 null,值为 false |
h, H |
通用 | 这是 Integer.toHexString(arg.hashCode()) |
s, S |
通用 | 如果 arg 实现 Formattable,则 arg.formatTo() 否则,arg.toString() |
c, C |
字符 | 这是一个 Unicode 字符 |
d |
整数 | 这是一个十进制整数 |
o |
整数 | 这是一个八进制整数 |
x, X |
整数 | 这是一个十六进制整数 |
e, E |
浮点数 | 这是一个计算机化的科学记数法中的十进制数 |
f |
浮点数 | 这是一个十进制数 |
g, G |
浮点数 | 这是一种计算机化的科学记数法或十进制格式,具体取决于精度和四舍五入后的值 |
a, A |
浮点数 | 这是一个带有尾数和指数的十六进制浮点数 |
t, T |
日期/时间 | 这是日期和时间转换字符的前缀 |
% |
百分比 | 这是一个字面量 % (\u0025) |
n |
行分隔符 | 这是特定平台的行分隔符 |
FlatFileItemWriter 可以配置 shouldDeleteIfExists 选项,如果指定位置已存在文件,则删除该文件。可以通过实现 FlatFileHeaderCallBack 和 FlatFileFooterCallBack 并使用 headerCallback 和 footerCallback 属性分别包含这些豆来向平面文件添加标题和页脚。
XML 项目写入器
数据可以使用 StaxEventItemWriter 写入到 可扩展标记语言 (XML) 格式。对于员工示例,此活动的 Spring Batch 配置可以是以下内容:
<bean id="itemWriter" class="org.springframework.batch.item.xml.StaxEventItemWriter">
<property name="resource" ref="outputResource"/>
<property name="marshaller" ref="employeeMarshaller"/>
<property name="rootTagName" value="employees"/>
<property name="overwriteOutput" value="true"/>
</bean>
使用 XStream 进行序列化活动,以下是其配置:
<bean id="employeeMarshaller"
class="org.springframework.oxm.xstream.XStreamMarshaller">
<property name="aliases">
<util:map id="aliases">
<entry key="employee"
value="batch.Employee"/>
<entry key="ID" value="java.lang.Integer"/>
</util:map>
</property>
</bean>
前述配置的 Java 代码可以如下实现:
StaxEventItemWriter staxItemWriter = newStaxEventItemWriter();
FileSystemResource resource = new FileSystemResource("export/employee_output.xml")
Map aliases = newHashMap();
aliases.put("employee","batch.Employee");
aliases.put("ID","java.lang.Integer");
Marshaller marshaller = newXStreamMarshaller();
marshaller.setAliases(aliases);
staxItemWriter.setResource(resource);
staxItemWriter.setMarshaller(marshaller);
staxItemWriter.setRootTagName("employees");
staxItemWriter.setOverwriteOutput(true);
ExecutionContext executionContext = newExecutionContext();
staxItemWriter.open(executionContext);
Employee employee = new Employee();
employee.setID(11);
employee.setLastName("Alden");
employee.setFirstName("Richie");
employee.setDesignation("associate");
employee.setDepartment("sales");
employee.setYearOfJoining("1996");
staxItemWriter.write(employee);
数据库项目写入器
Spring Batch 支持两种可能的数据库项目写入访问类型:JDBC 和 ORM。
基于 JDBC 的数据库写入
Spring Batch 在 JdbcBatchItemWriter 的帮助下支持基于 JDBC 的数据库写入,JdbcBatchItemWriter 是 ItemWriter 的一个实现,它以批处理模式执行多个 SQL 语句。以下是基于 JDBC 的数据库写入的员工示例的样本配置:
<bean id="employeeWriter" class="org.springframework.batch.item.database.JdbcBatchItemWriter">
<property name="assertUpdates" value="true" />
<property name="itemPreparedStatementSetter">
<bean class="batch.EmployeePreparedStatementSetter" />
</property>
<property name="sql"
value="INSERT INTO EMPLOYEE (ID, LASTNAME, FIRSTNAME, DESIGNATION, DEPARTMENT, YEAROFJOINING) VALUES(?, ?, ?, ?, ?, ?)" />
<property name="dataSource" ref="dataSource" />
</bean>
可以按照以下方式为我们的Employee数据示例实现ItemPreparedStatementSetter:
public class EmployeePreparedStatementSetter
implements ItemPreparedStatementSetter<Employee> {
@Override
public void setValues(Employee item, PreparedStatement ps) throws SQLException {
ps.setInt(1, item.getId());
ps.setString(2, item.getLastName());
ps.setString(3, item.getFirstName());
ps.setString(4, item.getDesignation());
ps.setString(5, item.getDepartment());
ps.setInt(6, item.getYearOfJoining());
}
}
基于 ORM 的数据库写入
对象关系映射(ORM)被定义为一种编程技术,用于在面向对象编程语言中将数据在不可兼容的类型系统之间进行转换。ORM 负责从面向对象程序到数据库的数据持久化。Spring Batch 支持多个 ORM,包括 Hibernate、JPA 和 iBatis。

在我们的示例中,Employee类应该被注释为与 ORM(Hibernate/JPA)一起使用,以便进行持久化,如下所示:
@Entity("employee")
public class Employee {
@Id("id")
private int id;
@Column("lastName")
private String lastName;
@Column("firstName")
private String firstName;
@Column("designation")
private String designation;
@Column("department")
private String department;
@Column("yearOfJoining")
private int yearOfJoining;
public int getID() {
return id;
}
public void setID(int id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getDesignation() {
return designation;
}
public void setDesignation(String designation) {
this.designation = designation;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public int getYearOfJoining() {
return yearOfJoining;
}
public void setYearOfJoining(int yearOfJoining) {
this.yearOfJoining = yearOfJoining;
}
}
注释指定类Employee代表数据库中相应的表,其名称由@Entity所示,并且每个字段对应数据库中的一个列,如@ID和@Column注释所示。
下面的配置是针对员工示例使用 Hibernate 进行的:
<bean id="employeeWriter"
class="org.springframework.batch.item.database.HibernateItemWriter">
<property name="hibernateTemplate" ref="hibernateTemplate" />
</bean>
类似地,对于 JPA 和 iBatis,可以通过JpaItemWriter和IbatisBatchItemWriter分别进行配置。
自定义项目读取器和写入器
Spring Batch 支持自定义项目读取器和写入器的配置。这可以通过实现ItemReader和ItemWriter接口来完成,这些接口分别用于执行所需的读取和写入操作,并带有我们想要的企业逻辑,然后在 XML 批配置中配置ItemReader和ItemWriter。
摘要
通过本章,我们学习了基本的数据处理机制,包括从不同的来源(如平面文件、XML 和数据库)读取数据,处理数据,并将数据写入不同的目的地,包括平面文件、XML 和数据库。我们还学习了在数据处理部分转换和验证数据。我们通过理解 Spring Batch 通过实现接口来支持自定义格式,以匹配与默认格式不同的业务需求来完成本章。在下一章中,我们将详细了解使用不同配置和模式管理事务。
第四章 处理作业事务
在上一章中,我们学习了基本的数据处理机制,包括从/到不同来源(如平面文件、XML 和数据库)读取、处理和写入数据。从前几章中,我们了解到 Spring Batch 作业处理大量数据的读取、操作和写入活动。通过这些活动,在与文件/数据库交互时,确保活动的一致性非常重要。Spring Batch 通过作业处理提供强大的事务支持。
在本章中,我们将涵盖以下主题:
-
事务
-
Spring Batch 事务管理
-
自定义事务
-
事务模式
事务
作为作业处理的一部分,活动包括从不同来源读取数据、处理数据并将其写入不同来源,包括文件和数据库。数据,作为一个完整的记录集或分块,必须要么完全处理并写入最终系统,要么在发生任何错误的情况下跟踪为失败的记录。事务管理应负责此操作,以确保其一致性,通过提交正确的信息并在发生任何错误时回滚。以下是在数据库事务中涉及的活动:
-
开始事务
-
处理一组记录
-
如果在处理过程中没有发生错误,则提交事务
-
如果在处理过程中发生任何错误,则回滚事务
![事务]()
因此,事务被定义为一系列遵守以下原子性、一致性、隔离性和持久性(ACID)特性的操作:
-
原子性:这确保事务中的所有操作要么全部成功,要么全部失败
-
一致性:这确保事务将资源从一个有效状态带到另一个有效状态
-
隔离性:在并发执行期间,一个事务的状态和影响对其他所有事务都是隐藏的
-
持久性:事务的结果应该是持久的,并且在事务完成后能够经受住系统崩溃
如果一个事务遵循这些 ACID 特性,它可以通过中止事务中发生的错误,优雅地恢复系统的稳定状态来处理任何意外错误。
Spring Batch 事务管理
Spring Batch 通过步骤执行提供事务管理,其中每个事务在成功处理数据后提交,如果在处理过程中发现任何错误,则回滚。
Spring Batch 在以下两种情况下管理事务:
-
Tasklet 步骤
-
块导向步骤
-
监听器
Tasklet 步骤
在 Spring Batch 中,任务令用于处理特定业务活动,如存档、远程交互和服务调用。默认情况下,任务令的execute方法是事务性的。因此,对execute方法的每次调用都会调用一个新的事务。以下是一个示例任务令配置:
<step id="stepOne">
<tasklet ref="myTasklet"/>
</step>
<bean id="myTasklet" class="batch.MyTasklet">
<property name="targetObject">
<bean class="batch.EmployeeData"/>
</property>
<property name="targetMethod" value="updateEmployeeDetails"/>
</bean>
任务令的实现可以如下所示:
public class MyTasklet implements Tasklet {
@Override
publicRepeatStatus execute(StepContribution contribution,
ChunkContext chunkContext) throwsException {
...
returnRepeatStatus.FINISHED;
}
}
面向块的步骤
面向块的步骤处理采用读取-处理-写入机制来处理记录块,如下所示图所示。每个步骤,一旦事务开始,处理要读取、处理和写入的数据,并在这些阶段成功完成后,步骤提交事务。然后,它跟随下一个事务来处理下一组记录。如果在这些步骤中的任何一步发生错误,它将回滚事务并完成步骤执行。

因此,对于大量数据处理,更倾向于使用面向块的操作步骤,这样整个数据被分成块并在单个事务中处理。如果在任何阶段发生异常,它将回滚该事务,因此数据处理将更加高效和完整。失败的步骤,可以通过记录并使用更正后的信息重新运行,可以优雅地回滚。以下是一个面向块的步骤配置示例:
<step id="stepOne">
<tasklet allow-start-if-complete="true">
<chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
</tasklet>
</step>
<step id="stepTwo" parent="stepOne">
<tasklet start-limit="5">
<chunk processor="itemProcessor" commit-interval="5"/>
</tasklet>
</step>
在前面的配置中,我们有一个包含任务令和块的第一个步骤(stepOne)。为了确保执行顺序,stepOne是stepTwo的父步骤。当stepOne配置itemReader和itemWriter时,stepTwo配置itemProcessor。
监听器
Spring Batch 支持在事件发生前后执行某些操作的监听器。Spring Batch 处理这些事件中的每一个,每个监听器中事务的处理方式是特定的,以及它们如何处理数据。因此,观察监听器方法是否作为步骤事务的一部分处理是很重要的。如果不是,那么应用程序应该以编程方式处理此类事务。以下是一个示例监听器配置:
<bean id="myStepExecutionListener"
class="org.java.MyStepExecutionListener" />
<job id="readEmployeeDetails">
<step id="step1">
<tasklet>
<chunk reader="itemReader" writer="itemWriter"
commit-interval="1" />
<listeners>
<listener ref="myStepExecutionListener" />
</listeners>
</tasklet>
</step>
</job>
监听器的实现可以如下所示:
public class MyStepExecutionListener implements StepExecutionListener {
@Override
public void beforeStep(StepExecution stepExecution) {
System.out.println("StepExecutionListener : beforeStep");
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
System.out.println("StepExecutionListener : afterStep");
return null;
}
}
定制事务
Spring Batch 允许配置自定义处理事务的方式。如果不同事务之间的数据交换被优雅地处理和读取,则事务是清洁的。然而,我们有不同的方式来配置事务完整性对其他交互的可见性,称为隔离级别。以下是可以自定义 Spring Batch 事务的隔离级别:
-
可序列化:这是最高的隔离级别。基于基于锁或非锁的并发控制,它确保了清洁的数据读取。
-
可重复读:这种基于锁的实现维护读和写锁,因此保证了数据清洁;然而,由于没有对范围锁的支持,可能会发生幻读。
-
读取提交:这种基于锁的实现维护写锁,因此它承诺任何读取的数据在读取时都是已提交的,并限制任何中间、未提交和脏读。
-
读取未提交:这是最低的隔离级别。一个事务可以看到其他事务的未提交更改。因此,在这个级别允许脏读。
每个隔离级别都有预定义的常量。默认情况下,Spring Batch 的隔离级别配置为READ_COMMITTED。根据要读取的事务中数据的紧迫性和重要性,必须为该事务设置隔离级别。例如,银行账户事务可能只想从其他事务中读取干净、已提交的数据,并使用持久数据执行事务。在这种情况下,必须在隔离级别和应用程序性能之间做出选择。以下是一个隔离级别配置示例:
<job-repository id="jobRepository" isolation-level-for-create="SERIALIZABLE"/>
如果批处理作业与使用类似命名约定的其他框架集成的应用程序集成,那么应该小心配置。一种技术的控制意图可能控制其他技术;在这种情况下,根据需要最好禁用其中一个配置。
如第二章中所述,每个批处理组件上的属性,即“Spring Batch 作业入门”,可以帮助我们更好地定制批处理作业的事务配置。例如,可以通过配置no-rollback-exception-classes来控制特定异常场景下的回滚事务。
事务模式
Spring Batch 作业处理涉及跨多个源处理数据。这些通常反复出现的情况可以识别为事务模式。
以下已识别的模式:
-
简单事务:这是一个涉及单个应用程序和数据源(源和目标)的事务
-
全局事务:这是一个涉及多个数据源并通过同一事务处理的事务
简单事务
使用与 Spring Batch 集成的多种数据库交互技术(如 JDBC、JPA 或 Hibernate 支持)的支持,可以轻松实现具有单个批处理应用程序和数据源的单个事务。交互方式如下所示:

全球事务
如果需要通过单个事务持久化多个数据源,则此类事务被称为全局事务,可以由事务管理器管理。确保事务通过其多个数据源遵守 ACID 特性,并且数据保持一致性的责任在于事务管理器。
然而,如果一个应用程序部署在一个支持事务管理器的集成企业服务器上,那么它也可以被认为是基于Java 事务 API(JTA)的事务管理器。以下是一个受管理事务的表示。

这些事务也可以配置为保持一个数据库模式作为另一个数据库模式中的同义词的引用,以便在虚拟上作为本地事务来引用。然而,必须考虑创建此类同义词的努力。
摘要
通过本章,我们学习了事务及其关键特性。我们还了解了 Spring Batch 在不同场景下如何执行事务管理,包括 tasklet 步骤、面向块步骤和监听器。我们还学习了如何通过隔离级别和属性配置来自定义事务。我们以对批处理应用程序中单源和多源常用事务模式的理解结束了本章。
在下一章中,我们将详细了解作业的流程以及执行作业步骤之间的数据共享。
第五章:步骤执行
在上一章中,我们学习了事务以及在不同场景下管理事务,包括使用隔离级别和模式配置单数据源和多数据源的事务属性。到目前为止,我们已经讨论了简单的作业,其中流程是线性的,包含一个步骤接着另一个步骤执行的作业。在现实世界应用中,我们需要配置带有步骤组合的作业,它们之间共享数据,并在运行时决定执行哪个步骤。
在本章中,我们将涵盖以下主题:
-
控制作业流
-
数据共享
-
外部化和终止
控制作业流
到目前为止,我们已经看到了配置了步骤以线性方式连续执行的批处理作业。在批处理作业执行过程中,可能会出现根据前一步的执行结果来决定执行哪个步骤的情况,这是一种非线性执行。
下图显示了批处理作业中线性步骤执行的情况:

下图显示了批处理作业中非线性步骤执行的情况:

让我们了解如何处理这样的作业流。主要有两种处理方式:
-
使用退出码
-
使用决策逻辑
使用退出码
可以根据步骤的退出状态以及带有on和to属性的next标签的配置来处理作业流。以下是一个使用退出码的示例配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns ="http://www.springframework.org/schema/batch"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch-3.0.xsd">
<beans:import resource="context.xml" />
<beans:bean id="testTasklet" class=" batch.TestTasklet">
<beans:property name="success" value="true"/>
</beans:bean>
<beans:bean id="successTasklet" class=" batch.StatusTasklet">
<beans:property name="status" value="Success"/>
</beans:bean>
<beans:bean id="failTasklet" class=" batch.StatusTasklet">
<beans:property name="status" value="Failure"/>
</beans:bean>
<job id="nonLinearJob">
<step id="stepOne">
<tasklet ref="testTasklet"/>
<next on="*" to="stepTwo"/>
<next on="FAILED" to="stepThree"/>
</step>
<step id="stepTwo">
<tasklet ref="successTasklet"/>
</step>
<step id="stepThree">
<tasklet ref="failTasklet"/>
</step>
</job>
</beans:beans>
在前面的配置中,stepOne是批处理作业中要执行的第一步。根据此步骤的输出(ExitStatus),包括testTasklet,下一个标签决定执行哪个步骤。如果testTasklet返回FAILED状态,则执行stepThree,否则执行stepTwo。状态可以由作业执行或步骤执行的属性返回。以下是一些不同的状态:
-
字符串:退出状态应与字符串匹配,例如
COMPLETED/FAILED,这可以从FlowExecutionStatus中验证。 -
*: 可以匹配零个或多个字符。它可以匹配任何值。 -
?: 仅匹配一个字符。
使用决策逻辑
非线性作业执行也可以通过使用JobExecutionDecider实现和决策标签配置的决策逻辑来处理。
以下是对应于检查退出状态并相应返回FlowExecutionStatus的JobExecutionDecider实现:
package batch;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
public class JobFlowDecider implements JobExecutionDecider {
@Override
public FlowExecutionStatus decide(JobExecution jobExecution,
StepExecution stepExecution) {
if(!ExitStatus.FAILED.equals(stepExecution.getExitStatus())) {
return new FlowExecutionStatus(FlowExecutionStatus.FAILED.getName());
} else {
return new FlowExecutionStatus(FlowExecutionStatus.COMPLETED.getName());
}
}
}
以下包含JobExecutionDecider实现和决策标签配置的作业配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns ="http://www.springframework.org/schema/batch"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch-3.0.xsd">
<beans:bean id="decider" class="batch.JobFlowDecider"/>
<beans:bean id="successTasklet" class=" batch.StatusTasklet">
<beans:property name="status" value="Success"/>
</beans:bean>
<beans:bean id="failTasklet" class=" batch.StatusTasklet">
<beans:property name="status" value="Failure"/>
</beans:bean>
<job id="nonLinearJob">
<step id="stepOne" next="decision">
<tasklet>
<chunk reader="itemReader" writer="itemWriter"
commit-interval="20"/>
</tasklet>
</step>
<decision id="decision"> decider="decider"
<next on="*" to="stepTwo"/>
<next on="FAILED" to="stepThree"/>
</decision>
<step id="stepTwo">
<tasklet ref="successTasklet"/>
</step>
<step id="stepThree">
<tasklet ref="failTasklet"/>
</step>
</job>
可以根据监控状态需求在这两种选项(退出码和决策逻辑)之间进行选择;作业执行决策者使配置更易于阅读。
数据共享
在理想情况下,每个步骤都应该配置为独立执行,但在现实场景中,步骤需要共享数据。数据可以通过不同的方式在步骤之间共享。以下是一些选项:
-
使用执行上下文
-
使用 Spring 持有者 beans
使用执行上下文
从前面的章节中我们了解到,Spring Batch 作业在称为批作业元数据的上下文中维护作业执行信息。我们可以使用这个上下文在步骤之间共享数据。基于键值的数据由 org.springframework.batch.item.ExecutionContext 在其使用中维护。以下是从中获取/设置数据的方式:
String importId = jobExecutionContext.getString("importId");
executionContext.putString("importId", importId);
作业和步骤都有它们自己的执行上下文,形式为 JobExecutionContext 和 StepExecutionContext。虽然作业有一个唯一的执行上下文,但作业中的每个步骤都维护其自己的步骤执行上下文。步骤上下文可以通过块上下文(org.springframework.batch.core.scope.context.ChunkContext)访问,而作业上下文可以通过步骤上下文访问。
使用 Spring 持有者 beans
步骤之间的数据也可以通过使用 Spring 持有者 beans 的概念进行共享。元数据配置由 ImportMetadata 表示,通过它可以设置和获取数据。我们可以编写一个可以持有 ImportMetadata 引用的 bean,并在作业配置中将它与 ImportMetadataHolder 配置相同。以下是对 ImportMetadataHolder 的示例配置:
package batch;
public class ImportMetadataHolder {
private ImportMetadata importMetadata;
public ImportMetadata get() {
return importMetadata;
}
public void set(ImportMetadata importMetadata) {
this.importMetadata = importMetadata;
}
}
可以使用以下语法从持有者设置和获取数据:
importMetadataHolder.set(
batchService.extractMetadata(outputDirectory));
importMetadataHolder.get().getImportId();
ImportMetadataHolder 可以像任何其他 bean 一样进行配置,并通过属性规范注入到 tasklets 中。
外部化和终止
外部化和终止是帮助使 Spring Batch 的组件可重用并优雅地处理作业终止的概念。
外部化
Spring Batch 允许使用外部化进行代码重用,即分离可重用操作步骤并将其包含在所需作业中。除了将单个步骤作为 beans 进行配置并包含在每个作业中之外,外部化还可以通过以下方式实现:
-
外部流程定义及其包含在所需作业中
-
继承作业机制
外部流程定义及其包含在所需作业中
以下是对外部流程定义及其包含在所需作业中的示例配置:
<flow id="externalFlow">
<step id="stepOne" next="stepTwo">
<tasklet ref="taskletOne"/>
</step>
<step id="stepTwo">
<tasklet ref="taskletTwo"/>
</step>
</flow>
<job id="mainJob">
<flow parent="externalFlow" id="mainStep" next="stepThree"/>
<step id="stepThree">
<tasklet ref="taskletThree"/>
</step>
</job>
继承作业机制
将流程外部化的另一种方式是通过继承一个作业到另一个作业,这意味着定义一个独立的作业,并在另一个作业中将其作为其一部分进行引用。以下是对其的示例配置:
<job id="mainJob">
<step id="stepOne" next="stepTwo">
<tasklet ref="taskletOne"/>
</step>
<step id="stepTwo">
<tasklet ref="taskletTwo"/>
</step>
</job>
<job id="subJob">
<step id="stepThree" next="stepFour">
<job ref="mainJob" job-parameters-extractor="jobParametersExtractor" />
</step>
<step id="stepFour" parent="runBatch"/>
</job>
主要作业有几个步骤,子作业被定义为在它的第一步中引用主要作业。
终止
以编程方式结束执行是批处理作业执行的一个重要方面。为了能够有效地编程,应该了解作业可以终止的不同状态。不同的状态如下:
-
COMPLETED:此结束状态可以用来告诉 Spring Batch 处理已成功结束。当一个作业实例以这种结束状态终止时,不允许使用相同的参数集重新运行。 -
FAILED:此结束状态可以用来告诉 Spring Batch 处理已失败。Spring Batch 允许失败的作业使用相同的参数集重新运行。 -
STOPPED:此结束状态类似于暂停正在执行的作业。如果以这种状态结束,Spring Batch 不仅允许我们重新启动作业,而且允许我们从上次停止的地方重新启动,即使执行过程中没有错误。
以COMPLETED状态终止
以下是基于带有 end 标签配置的ExitStatus终止作业在COMPLETED状态的配置:
<job id="nonLinearJob">
<step id="stepOne">
<tasklet ref="successTasklet"/>
<end on="*"/>
<next on="FAILED" to="stepTwo"/>
</step>
<step id="stepTwo">
<tasklet ref="failureTasklet"/>
</step>
</job>
此配置在成功执行后结束作业,并且我们不能使用相同的参数集重新运行作业。第一步配置为在第二步失败时调用第二步。
以FAILED状态终止
以下是基于带有 fail 标签配置的ExitStatus终止作业在FAILED状态的配置:
<job id="nonLinearJob">
<step id="stepOne">
<tasklet ref="successTasklet"/>
<next on="*" to="stepTwo"/>
<fail on="FAILED" exit-code="STEP-ONE-FAILED"/>
</step>
<step id="stepTwo">
<tasklet ref="failureTasklet"/>
</step>
</job>
如果退出状态是FAILED,则此配置将以FAILED状态结束作业,并且我们可以使用相同的参数集重新运行作业。
以STOPPED状态终止
以下是基于带有 stop 标签配置的ExitStatus终止作业在STOPPED状态的配置:
<job id="nonLinearJob">
<step id="stepOne">
<tasklet ref="successTasklet"/>
<next on="*" to="stepTwo"/>
<stop on="FAILED" restart="stepTwo"/>
</step>
<step id="stepTwo">
<tasklet ref="failureTasklet"/>
</step>
</job>
如果退出状态是FAILED,则此配置将以STOPPED状态结束作业,并且我们可以使用相同的参数集重新运行作业。
摘要
通过本章,我们学习了如何使用退出代码和决策逻辑控制批处理作业的流程。我们还学习了如何在执行步骤之间通过执行上下文和 holder beans 共享数据。我们还学习了通过外部化和继承作业机制来重用流程。我们通过理解在不同状态下终止批处理作业及其重要性来结束本章。在下一章中,我们将详细了解使用 Spring 集成和 RESTful 作业处理的企业集成。
第六章。集成 Spring Batch
在上一章中,我们学习了如何使用退出代码和决策逻辑控制批处理作业的流程,在执行步骤之间共享数据,并通过外部化和继承作业机制来重用流程。我们还学习了如何在不同状态下终止批处理作业及其重要性。一个组织借助多种工具进行其操作,并在不同地点维护其数据和应用程序。使用一种合理的机制来集成这些应用程序中的数据,以同步系统是很重要的。
在本章中,我们将介绍以下主题:
-
企业集成
-
Spring 集成
-
RESTful 作业处理
企业集成
到目前为止,我们已经看到了配置了不同步骤的批处理作业;从不同的来源读取数据,执行操作,并将数据写入不同的目的地。在实时操作中,组织使用不同的应用程序来执行其操作。用于维护员工信息和处理工资单的应用程序可能与负责物流和销售的应用程序不同。在这种情况下,无缝集成这些应用程序以在任何特定点一起处理整个数据并执行系统操作是很重要的。
下图显示了企业资源规划(ERP)系统如何集成不同的模块,从其系统中访问信息,并将其作为实体维护。

以下是企业应用程序集成的方式:
-
基于文件的数据传输:应用程序基于平面文件交换数据;源系统将数据写入平面文件并将文件导出到目标系统。目标系统从平面文件中读取数据并将其导入其目标数据库。
-
资源共享:应用程序共享通用资源,例如文件系统或数据库,以执行其操作。实际上,它们作为独立系统运行;然而,它们在通用系统上填充/写入数据。
-
服务调用:应用程序将其操作公开为服务(近年来是 Web 服务)以允许其他应用程序调用它们。可以根据它们的设计方式从这些服务中传输/接收数据。
-
消息服务:应用程序使用一个公共消息服务器;一个应用程序可以发送消息,另一个接收它。
Spring 集成
Spring 项目将 Spring 集成定义为 Spring 编程模型的企业集成扩展。Spring 集成是为了在基于 Spring 的应用程序中支持轻量级消息而开发的,并通过声明式适配器支持与外部系统的系统集成。这些适配器提供了 Spring 对远程、消息和调度的支持抽象。

当 Spring Batch 在基于文件或数据库的集成系统上操作时,Spring Integration 提供了基于消息的应用程序集成。将此消息功能添加到 Spring Batch 应用程序中可以自动化其操作,并分离关键操作关注点。让我们了解如何使 Spring Integration 配置成为集成企业应用程序的一部分。以下是一些可以通过消息集成执行的关键操作:
-
触发批处理作业执行
-
触发带有作业完成/失败状态的消息
-
异步处理器的操作
-
外部化
以下是在 Spring XML 应用程序上下文中启用 Spring Batch 集成的文件:
<beans
xmlns:batch-integrate="http://www.springframework.org/schema/batch-integration"
xsi:schemaLocation="http://www.springframework.org/schema/batch-integration
http://www.springframework.org/schema/batch-integration/spring-batch-integration.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
...
</beans>
触发批处理作业执行
到目前为止,我们一直通过命令行、从应用程序中以编程方式触发作业。然而,某些应用程序必须使用远程文件传输(FTP/SFTP)传输数据,并启动将数据导入应用程序的作业。Spring Integration 提供了不同的适配器,以便轻松进行启动配置。Spring Integration 的JobLaunchingMessageHandler是一个易于实现的、基于事件的JobLauncher执行。Spring Integration 提供JobLaunchRequest作为JobLaunchingMessageHandler的输入。

以下是将JobLaunchRequest从文件转换的列表:
package com.java.batchJob;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.integration.launch.JobLaunchRequest;
import org.springframework.integration.annotation.Transformer;
import org.springframework.messaging.Message;
import java.io.File;
public class FileMessageToJobRequest {
private Job job;
private String fileParameterName;
public void setFileParameterName(String fileParameterName) {
this.fileParameterName = fileParameterName;
}
public void setJob(Job job) {
this.job = job;
}
@Transformer
Public JobLaunchRequest toRequest(Message<File> message) {
JobParametersBuilder jobParametersBuilder =
new JobParametersBuilder();
jobParametersBuilder.addString(fileParameterName, message.getPayload().getAbsolutePath());
return new JobLaunchRequest(job, jobParametersBuilder.toJobParameters());
}
}
作业执行状态通过JobExecution实例返回。JobExecution ID 帮助用户通过JobRepository跟踪作业执行状态。
以下配置是通过适配器获取文件输入(CSV 文件),通过转换器FileMessageToJobRequest将其转换为JobRequest,通过JobLaunchingGateway启动作业,并记录JobExecution的输出。
<integrate:channel id="inputFileRepository"/>
<integrate:channel id="jobRequestChannel"/>
<integrate:channel id="jobTriggeringStatusChannel"/>
<integrate-file:inbound-channel-adapter id="inputFile"
channel="inputFileRepository"
directory="file:/tmp/batchfiles/"
filename-pattern="*.csv">
<integrate:poller fixed-rate="1000"/>
</integrate-file:inbound-channel-adapter>
<integrate:transformer input-channel="inputFileRepository"
output-channel="jobRequestChannel">
<bean class="batchJob.FileMessageToJobRequest">
<property name="job" ref="employeeJob"/>
<property name="fileParameterName" value="input.file.name"/>
</bean>
</integrate:transformer>
<batch-integrate:job-launching-gateway request-channel="jobRequestChannel"
reply-channel="jobTriggeringStatusChannel"/>
<integrate:logging-channel-adapter channel="jobTriggeringStatusChannel"/>
项目读取器可以被配置为从以下配置中选取输入文件名作为作业参数:
<bean id="itemReader" class="org.springframework.batch.item.file.FlatFileItemReader"
scope="step">
<property name="resource" value="file://#{jobParameters['input.file.name']}"/>
...
</bean>
Spring Integration 从 Spring 应用程序上下文中获取消息访问。因此,批处理作业也可以通过从应用程序上下文中访问的请求来触发,如下面的代码所示:
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-integration-job.xml");
EmployeeJobLaunchRequest employeeJobLaunchRequest = new EmployeeJobLaunchRequest("employeeJob", Collections.singletonMap("key", "value"));
Message<EmployeeJobLaunchRequest> msg = MessageBuilder.withPayload( employeeJobLaunchRequest).build();
MessageChannel jobRequestsChannel = ctx.getBean("inputFileRepository", MessageChannel.class);
jobRequestsChannel.send(msg);
在前面的代码中,EmployeeJobLaunchRequest是用户定义的JobLaunchRequest,它被 Spring Integration 消息包装。生成消息的 Spring Integration 类是MessageBuilder。使用此请求,我们可以传递输入请求详情,例如文件存储库,并启动作业。Spring Integration 的详细信息可以在Spring Integration Essentials,Chandan Pandey,Packt Publishing中学习。
RESTful 作业处理
网络服务是在网络上两个电子设备之间通信的方法。它是在 Web 上通过网络地址提供的软件功能,服务始终处于开启状态,就像在计算效用概念中一样。
REST 是一种架构风格,它由一组协调的架构约束组成,应用于分布式超媒体系统中的组件、连接器和数据元素。REST 架构风格也应用于 Web 服务的开发。与 REST 兼容的 Web 服务的主要目的是使用一组无状态操作来操作 Web 资源的 XML 表示。
Spring Batch 支持使用 REST Web 服务通过Put/Post方法启动和批处理作业。以下是一个使用 Spring CXF(一个开源服务框架)的示例列表:
@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job job;
public boolean startJob() throws Exception {
try {
final JobParameters jobParameters = new JobParametersBuilder(). addLong("time", System.nanoTime()).toJobParameters();
final JobExecution execution = jobLauncher.run(job, jobParameters);
final ExitStatus status = execution.getExitStatus();
if (ExitStatus.COMPLETED.getExitCode().equals(status.getExitCode()))
{
result = true;
}
}
} catch (JobExecutionAlreadyRunningException ex) {
System.out.println("Exception" + ex);
} catch (JobRestartException ex) {
System.out.println("Exception" + ex);
} catch (JobInstanceAlreadyCompleteException ex) {
System.out.println("Exception" + ex);
} catch (JobParametersInvalidException ex) {
System.out.println("Exception" + ex);
} catch (IOException ex) {
System.out.println("Exception" + ex);
}
return false;
}
自动装配的JobLauncher和Job对象被注入到应用程序中。startJob()方法使用JobParametersBuilder创建JobParameters,并通过jobLauncher.run()触发作业。从 Web 服务调用批处理作业的此调用在同步线程中触发批处理作业。可以从JobExecution对象访问ExitStatus。任何在作业启动期间的异常都可以通过前面提到的适当异常处理来捕获。
摘要
通过本章,我们学习了企业集成以及可用于企业应用程序集成的不同方法。我们还了解了 Spring Integration 项目如何通过其消息驱动方法与企业批处理应用程序集成。我们还学习了如何通过访问应用程序上下文中的 Spring Integration 组件来启动批处理作业。我们以对 RESTful 作业处理技术的理解结束了本章。
在下一章中,我们将学习如何检查 Spring Batch 作业,包括访问执行数据、监听器和 Web 监控。
第七章:检查 Spring Batch 作业
在上一章中,我们学习了企业集成、各种企业应用程序集成以及 Spring Integration 项目,该项目通过消息驱动方法将 Spring Batch 应用程序与企业集成。我们还学习了使用 Spring Integration 和 RESTful 作业处理技术启动批量作业。Spring Batch 作业执行涉及大量数据,这些数据会随时间变化。有时这些变化的数据可能会损坏,导致作业执行失败。密切关注此类失败非常重要,并且应将失败原因以建设性的方式保存,以便未来的跟踪和修复。
在本章中,我们将涵盖以下主题:
-
批量作业监控
-
访问执行数据
-
监听器
-
网络监控
批量作业监控
到目前为止,我们已经看到了各种批量作业配置和执行情况,它们处理来自不同来源的数据,对其进行处理,并将结果推送到另一个数据存储。只要作业按照我们的配置执行,一切看起来都很顺利。一个应用程序的稳定性可以通过其对外部环境问题的响应强度和详细程度来衡量,即应用程序运行的环境,外部系统的可用性和可访问性,以及提供给应用程序的数据的正确性。
应用程序应该能够生成关于功能、谁在使用它、性能如何以及应用程序面临的详细问题/错误跟踪信息的清晰信息。Spring Batch 解决了这些参数,并生成一个更大的基础设施来监控批量作业处理并存储这些监控信息。
应用程序基础设施应负责识别任何此类问题,并通过预先配置的通信渠道向相关部门报告。Spring Batch 具有强大的基础设施,可以在数据库中维护监控的作业信息。让我们了解数据库基础设施以及每个实体是如何相互关联的。
以下是由 Spring Batch 定义的架构图:

前面的图示展示了负责作业执行信息的批量作业架构。以下是对这些实体的意义说明:
-
BATCH_JOB_INSTANCE:此实体维护了批量作业的高级信息以及每个作业的实例。它包含不同作业实例的唯一标识符,这些实例是为同一作业创建的,具有不同的作业参数(JOB_KEY),以及每个记录的作业名称和版本。 -
BATCH_JOB_PARAMS:此实体维护与每套作业参数实例相关的信息。它维护要传递给作业的作业参数的键/值对。 -
BATCH_JOB_EXECUTION: 这维护作业每个实例的作业执行信息。它通过连接BATCH_JOB_INSTANCE为批处理作业的每次执行维护单独的记录。 -
BATCH_STEP_EXECUTION: 这维护作业实例每个步骤的步骤执行信息。它通过连接BATCH_JOB_EXECUTION来维护每个作业执行实例的步骤执行信息。 -
BATCH_JOB_EXECUTION_CONTEXT: 这是每个作业执行实例所需的信息。对于每个执行,这是唯一的,因此对于重试作业,会考虑与上次运行相同的信息。因此,它与BATCH_JOB_EXECUTION相连接,以维护每个执行的实例。 -
BATCH_STEP_EXECUTION_CONTEXT: 这与BATCH_JOB_EXECUTION_CONTEXT类似,但它维护每个步骤执行的上下文信息。因此,它与BATCH_STEP_EXECUTION相连接,以维护每个步骤执行实例的唯一实例。
访问执行数据
虽然 Spring Batch 将所有监控和作业信息保存到数据库中,但让我们了解 Spring Batch 的每个管理组件,它们如何相互交互以及它们的配置。

Database
数据库保存作业相关信息,并作为监控作业执行信息的来源。
可以使用以下语法配置数据库:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${batch.jdbc.driver}"/>
<property name="url" value="${batch.jdbc.url}"/>
<property name="username" value="${batch.jdbc.user}"/>
<property name="password" value="${batch.jdbc.password}"/>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager" lazy-init="true">
<property name="dataSource" ref="dataSource"/>
</bean>
driverClassName、url、username 和 password 的值可以针对数据库和特定用户与该数据库的连接而特定。DataSourceTransactionManager 是这里的交易管理器,它通过 datasource 属性引用数据库。
JobRepository
org.springframework.batch.core.repository.JobRepository 接口是访问作业相关信息的中心点。它从数据库访问批处理作业的状态和元数据,并将其提供给其他资源。
JobRepository 的配置遵循以下语法:
<job-repository id="jobRepository"
data-source="dataSource" transaction-manager="transactionManager" />
JobLauncher
org.springframework.batch.core.launch.JobLauncher 接口负责作业执行,并更新 JobRepository 中的作业状态。
JobLauncher 的配置遵循以下语法:
<job-repository id="jobRepository"
data-source="dataSource" transaction-manager="transactionManager" />
<bean id="taskExecutor"
class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository"/>
< property name="taskExecutor" ref="taskExecutor"/>
</bean>
JobOperator
org.springframework.batch.core.launch.JobOperator 接口作为批处理作业处理的控制点。它通过从 JobExplorer 访问作业信息,向另一位管理员,即 JobLauncher 发送启动、停止和重启信号。
JobOperator 的配置遵循以下语法:
<bean id="jobOperator"
class="org.springframework.batch.core.launch.support.SimpleJobOperator"
prop:jobLauncher-ref="jobLauncher" prop:jobExplorer-ref="jobExplorer"
prop:jobRepository-ref="jobRepository" prop:jobRegistry-ref="jobRegistry" />
JobExplorer
org.springframework.batch.core.explore.JobExplorer 接口从数据库读取与作业相关的信息,并将信息提供给其他管理员,例如在作业执行中的 JobOperator,它们具有只读访问权限。
JobExplorer 的配置遵循以下语法:
<bean id="jobExplorer"
class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean"
prop:dataSource-ref="dataSource" />
Listeners
在前面的章节中,我们讨论了监听器是执行中由预配置事件触发的组件。我们可以使用此类监听器在批量作业上触发特定事件,并作为相应问题的监控工具。监听器还可以配置为报告执行中特定问题的相应部门。

以下是一个用于监控批量作业问题的示例监听器定义和配置。
可以定义一个监听器类,在作业执行前后执行,如下面的代码所示:
public class JobMonitoringListener {
@BeforeJob
public void executeBeforeJob(JobExecution jobExecution) {
//pre-run executions
}
@AfterJob
public void executeAfterJob(JobExecution jobExecution) {
if(jobExecution.getStatus() == BatchStatus.FAILED) {
/* Report the departments for a failed job execution status.
The reporter can be a preconfigured mail-sender or an SMS
sender or any other channel of communication.*/
reporter.report(jobExecution);
}
}
}
配置带有监控监听器的批量作业可以如下所示:
<batch:job id="firstBatchJob">
<batch:step id="importEmployees">
..
</batch:step>
<batch:listeners>
<batch:listener ref="jobMonitoringListener"/>
</batch:listeners>
</batch:job>
<bean id=" jobMonitoringListener" class="org.java.JobMonitoringListener"/>
网络监控
Spring Batch 作业执行可以通过 Spring 开源项目提供的网络界面进行监控和检查,即Spring Batch Admin。这是一个使用 Spring MVC 用户界面构建的简单网络应用程序,作为 Spring Batch 应用程序和系统的管理控制台。在此项目中开发的主要用例包括检查作业、启动作业、检查执行情况和停止执行情况。
注意
有关安装和使用的详细信息,请参阅 Spring Batch Admin 参考指南docs.spring.io/spring-batch-admin/reference.html。
摘要
通过本章,我们学习了作业执行监控和 Spring Batch 作业监控基础设施的重要性。我们还学习了如何在管理员配置的帮助下访问作业执行信息。此外,我们还学习了在监听器的帮助下监控和报告批量作业问题。我们通过了解 Spring Batch Administration 项目功能和它如何帮助监控批量作业来结束本章。
在下一章中,我们将详细了解批量扩展模型、并行处理和分区概念。
第八章。使用 Spring Batch 进行扩展
在上一章中,我们学习了如何使用监听器、访问执行信息和管理配置来监控、获取执行信息和理解 Spring Batch 管理功能。Spring Batch 作业执行处理的是随时间变化的大量数据。这种详细处理消耗了巨大的基础设施。显然,可以预期这些作业在组织数据规模增长时能够高效执行并满足扩展需求。
在本章中,我们将涵盖以下主题:
-
批量扩展模型
-
线程模型
-
并行处理
-
远程分块
-
分区
批量扩展模型
到目前为止,我们已经看到了如何处理不同类型的批量作业、配置和执行。随着组织规模的日益增长,每个批量作业需要处理的数据量也相应增加。设计并配置我们的批量作业以满足这些性能和扩展预期是很重要的。
我们编写的具有特定业务逻辑的批量作业,保持不同资源之间的交互,不能每次看到数据负载或性能问题时都进行更改。Spring Batch 提供了丰富的配置基础设施,可以通过调整配置信息来扩展作业,而无需更改它们。
基础设施的扩展可以通过以下两种方式之一进行:
-
通过增加系统硬件的容量:在这种扩展方式中,我们可以用更强大的基础设施替换现有的缓慢基础设施。
-
添加更多服务器:在这种扩展方式中,我们可以在现有基础设施中并行添加更多相同容量的处理系统。这些额外的节点分担工作并增加了整个系统的扩展性。
Spring Batch 提供了以下方式来扩展批量应用程序:
-
线程模型:这是一个单进程的多线程步骤
-
并行处理:这是一个单进程的并行步骤执行
-
远程分块:这是多进程步骤的远程分块
-
分区:这是步骤的分区;它可以是一个或多个进程
线程模型
默认情况下,步骤执行是单线程模型。Spring Batch 允许我们配置步骤以多块执行,从而使单个步骤能够在org.springframework.core.task.TaskExecutor的帮助下以多线程模型执行。
下面的图展示了步骤执行的线程模型:

下面的示例配置是带有TaskExecutor的多线程步骤:
<step id="employeePayProcessing">
<tasklet task-executor="taskExecutor">
<chunk reader="employeeWorkingDaysReader" processor="employeePayProcessor"
writer="employeeSalariesWriter"/>
</tasklet>
</step>
<beans:bean id="taskExecutor"
class="org.springframework.core.task.SimpleAsyncTaskExecutor">
<beans:property name="concurrencyLimit" value="20"/>
</beans:bean>
通过前面的配置,employeePayProcessing 步骤考虑了为任务和任务执行配置的读取器、处理器和写入器,通过 org.springframework.core.task.SimpleAsyncTaskExecutor 拥有 20 个线程的线程池,每个线程并行处理每个线程中的数据块。
就像任何其他多线程模型一样,Spring Batch 的多线程模型也考虑了多个线程使用的资源,以及它们是否是线程安全的。"ItemReader" 是这样一个过程,它不是线程安全的。
要配置线程安全的操作,建议通过同步读取方法来同步 ItemReader 过程。
并行处理
虽然多线程允许单个步骤在多个线程中处理数据块,但 Spring Batch 允许我们通过并行处理同时处理多个步骤和流程。这个特性使得独立的步骤可以并行执行,并确保更快的处理速度。
下图显示了正在并行执行的多个步骤:

使用并行处理,独立的步骤不需要等待其他步骤完成后再执行。
下面的示例配置了处理中的并行步骤:
<job id="employeePayProcessing">
<split id="splitProcess" task-executor="taskExecutor" next="payCalculations">
<flow>
<step id="readEmployeeData" parent="stepOne" next="processEmployeeData"/>
<step id="processEmployeeData" parent="stepTwo"/>
</flow>
<flow>
<step id="organizationDataSetup" parent="stepThree"/>
</flow>
</split>
<step id="payCalculations" parent="stepFour"/>
</job>
<beans:bean id="taskExecutor" class=" org.springframework.core.task.SimpleAsyncTaskExecutor"/>
在前面的配置中,readEmployeeData 和 processEmployeeData 步骤与 organizationDataSetup 并行执行。默认情况下,taskExecutor 是 SyncTaskExecutor;通过前面的配置,我们将其更改为 SimpleAsyncTaskExecutor 以支持并行步骤处理。
远程分块
远程分块是原始步骤读取数据调用远程过程进行处理,并将处理后的数据写回或接收回系统上的过程。由于远程分块涉及将数据传输到远程系统,我们还应该考虑建立此基础设施的成本与我们在远程处理中获得的优势之间的成本。实际步骤(主步骤)执行读取过程,远程从属步骤(监听器)可以是执行处理和写入步骤的 JMS 监听器,或者将处理后的信息返回给主步骤。
下图展示了远程分块步骤:

ChunkProvider 接口从 ItemReader 返回数据块:
public interface ChunkProvider<T> {
void postProcess(StepContribution contribution, Chunk<T> chunk);
Chunk<T> provide(StepContribution contribution) throws Exception;
}
ChunkProcessor 接口处理数据块:
public interface ChunkProcessor<I> {
void process(StepContribution contribution, Chunk<I> chunk) throws Exception;
}
为了能够有效地执行远程交互,远程分块过程可以包含 Spring Integration 项目以处理资源的集成。
分区
虽然远程分块在主节点读取数据并处理到另一个远程系统(从属节点),但分区通过具有完整处理能力的多个系统并行执行整个流程(读取、处理和写入)。在此,主步骤负责理解作业并将任务交给多个从属节点,而从属节点必须负责剩余的任务(读取、处理和写入)。本质上,从属节点构成了在其自己的世界中负责读取、处理和写入的步骤。
与远程分块相比,分区的好处包括不需要数据传输,因为从属系统也负责读取步骤。

尽管在此模式中,主节点发送给从属节点的通信未能成功传递,但 JobRepository 中的批处理元数据确保每个从属节点在每个作业执行中只执行一次。
Spring Batch 分区 服务 提供者接口(SPI)具有以下基础设施以实现有效的分区:
-
PartitionHandler: 这向远程步骤发送StepExecution请求。它不必知道如何拆分或整合数据,而TaskExecutorPartitionHandler是PartitionHandler的默认实现。 -
Partitioner: 这为分区步骤生成步骤执行(仅适用于新的步骤执行)。SimplePartitioner是Partitioner的默认实现。 -
StepExecutionSplitter: 这为分区步骤执行生成输入执行上下文,而SimpleStepExecutionSplitter是默认实现。
以下是一个示例分区步骤执行配置:
<step id="initialStep">
<partition step="stepPartition" handler="handler"/>
</step>
<beans:bean class="org.springframework.batch.core.partition.support.TaskExecutorPartitionHandler">
<beans:property name="taskExecutor" ref="taskExecutor"/>
<beans:property name="step" ref="stepPartition"/>
<beans:property name="gridSize" value="10"/>
</beans:bean>
上述配置以 initialStep 开始执行,并将执行权交给分区步骤。网格大小表示要创建的不同步骤的数量。
虽然多线程模型适合于块处理的基本调整,但并行处理使我们能够配置独立步骤以并行执行。远程分块需要相对较大的基础设施和配置,但适合于分布式节点处理。分区有助于快速复制批处理基础设施,并将整个流程配置为在并行节点上执行,其中存储库的单一点作为主节点。
根据系统需求和现有基础设施的可行性,可以选择之前提到的任何一种扩展策略来执行批处理作业。
摘要
通过本章,我们学习了批处理性能和扩展的重要性。我们还了解了 Spring Batch 提供的扩展批处理应用的方法。此外,我们还学习了线程模型、并行处理、远程分块和分区技术的详细信息和配置。我们通过理解如何利用现有基础设施选择合适的策略来扩展批处理应用,结束了本章的学习。
在下一章中,我们将详细了解在 Spring Batch 应用上执行不同类型的测试。
第九章:测试 Spring Batch
在上一章中,我们学习了通过不同的配置(即线程模型、并行处理、远程分块和分区技术)来提高批处理应用程序的性能和可扩展性的重要性,以及如何选择合适的策略来利用现有基础设施扩展批处理应用程序。Spring Batch 应用程序是通过单个组件和不同的集成进行开发和配置的,因此测试单个功能以及集成项目以验证其预期行为是很重要的。
在本章中,我们将涵盖以下主题:
-
Spring Batch 的测试类型
-
单元测试
-
集成测试
-
功能测试
Spring Batch 的测试类型
任何软件测试的主要目的是检测软件故障并纠正它们。软件测试的范围可以从验证软件组件到验证软件功能,以及软件在各种环境和条件下的运行。
以下是我们可能希望在 Spring Batch 应用程序上执行的一些软件测试类型:
-
单元测试:也称为组件测试,这指的是验证特定代码片段的功能。单元测试通常由开发者编写。
-
集成测试:这识别了接口和集成组件之间的缺陷。由于软件组件是迭代增量地集成的,因此集成测试是大型项目中重要的测试方面。
-
功能测试:这验证了特定代码组件或代码组件组的功能,如特定应用程序的功能规范中定义的那样。
-
性能测试:这验证了整个系统是否满足从指定的环境或运行条件中期望的性能标准。
功能测试和性能测试通常在系统测试中一起进行。
单元测试
单元测试是由开发者执行的对组件级别的测试,开发者准备源代码和测试计划。如果单元测试失败,开发者可以修复组件的问题并再次执行单元测试。以下图展示了单元测试场景。

JUnit
JUnit 是执行单元测试的标准 Java 框架。大多数 IDE 都内置了对 JUnit 的支持。TestNG 也可以用作 JUnit 的类似物。
JUnit 测试用例可以编写为简单的 Java 类,通过在执行测试操作的方法上使用@Test注解来执行。
以下是一个 JUnit 在 Java 字符串连接操作上的示例:
public class MyClass {
public String concatenate(String former, String later){
return former + latter;
}
}
测试此 Java 类的 JUnit 类可以如下所示:
import org.junit.Test;
import static org.junit.Assert.*;
public class MyClassTest {
@Test
public void testConcatenate() {
MyClass myclass = new MyClass();
String output = myClass.concatenate("Spring", "Batch");
assertEquals("SpringBatch", output);
}
}
在前面的类中,带有 @Test 注解的 testConcatenate 方法验证了 MyClass java 组件。assertEquals() 方法通过比较 MyClass.concatenate() 方法的输出与预期输出来进行实际测试。如果比较失败,assertEquals() 方法将抛出异常。我们还可以编写方法,使用 @Before 注解在单元测试方法执行之前设置操作,并使用 @After 注解在单元测试操作之后清理任务。可以参考 JUnit API(junit.sourceforge.net/javadoc/)获取 JUnit 类的详细列表及其用法。
Mockito
由于我们需要执行批处理应用程序测试,每个组件都可以通过某些依赖关系访问其他组件。需要复制所有这些类来创建此类对象的实例并将其提供给正在测试的组件。Mockito 是一个开源的 Java 框架,它使我们能够轻松地创建用于测试的测试双对象(模拟对象)。
可以通过简单的 Maven 依赖关系将 Mockito 添加到应用程序中,例如以下内容:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.8</version>
<scope>test</scope>
</dependency>
在批处理应用程序中,我们有一个 EmployeeReader 类,在测试执行期间需要创建一个对象。这可以通过在 JUnit 测试类中使用 Mockito 来完成,如下所示:
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Test;
public class EmployeeReaderUnitTest {
EmployeeReader empReaderMock;
@Before
public void setUp() {
empReaderMock = mock(EmployeeReader.class);
}
@Test
public void testEmpReader()throws Exception {
...
}
}
在前面的代码片段中,我们使用 JUnit 的 @Before(设置)方法中的 Mockito 的 mock() 方法创建了 EmployeeReader 对象。我们将在 @Test 方法中使用此对象来验证组件的功能。
同样,任何 Spring Batch 组件,如监听器和验证器,都必须进行单元测试以验证其功能。
集成测试
集成测试以迭代增量方式将软件组件集成时识别缺陷。集成测试是大型项目中的重要测试方面。
单元测试的模块被分组到更大的聚合中,根据集成测试计划进行测试,然后测试的应用程序作为功能测试的系统准备进行下一级测试。

以下是在 Spring Batch 中执行组件测试的两种方法。
基于监听器的方法
以下类级别的注解有助于测试 Spring Batch 组件:
-
@RunWith(SpringJUnit4ClassRunner.class): 这个注解表示该类应使用 Spring 对 JUnit 服务的支持。 -
@ContextConfiguration: 这让程序了解包含应用程序上下文的配置属性。 -
@TestExecutionListeners: 这有助于配置监听器以帮助测试执行设置依赖注入和步骤范围测试执行等能力。
以下是一个使用这些注解的示例配置:
@ContextConfiguration(locations = { "/app-context.xml" })
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
StepScopeTestExecutionListener.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class CarFileReaderIntegrationTest {
...
}
步骤范围测试工具方法
StepScopeTestUtils 实用工具类用于在单元测试中创建和操作 StepScope。这有助于使用 Spring 测试支持并将依赖项注入到应用程序上下文中作为步骤范围的测试用例。
@Test
public void testEmployeeReader() throws Exception {
StepExecution execution = getStepExecution();
int empReads =
StepScopeTestUtils.doInStepScope(stepExecution, new
Callable<Integer>() {
@Override
public int call() throws Exception {
((ItemStream) employeeReader).open(new ExecutionContext());
int count = 0;
while(employeeReader.read() != null) {
count++;
}
return count;
}
});
assertEquals(empReads, 10);
}
StepScopeTestUtils 的 doInStepScope() 方法接受 stepExecution 和可调用实现;它自动进行运行时依赖注入并返回结果。其余的测试是使用 JUnit 的 assertEquals() 方法验证 empReads 的数量与预期数量。
功能测试
功能测试验证特定代码组件或一组代码组件的功能,正如特定应用程序的功能规范中定义的那样,为组件提供输入数据,并将输出行为与预期行为进行比较。功能测试是“黑盒”测试,因为我们只处理特定输入的预期输出,只涉及外部系统行为。

在 Spring Batch 应用程序中,整个作业被视为执行单元,并且可以使用 JobLauncherTestUtils(一个用于测试 Spring Batch 作业的实用工具类)来测试其功能。JobLauncherTestUtils 提供了启动整个 AbstractJob 的方法,允许对单个步骤进行端到端测试,而无需运行作业中的每个步骤。JobLauncherTestUtils 还提供了从 FlowJob 或 SimpleJob 运行步骤的能力。通过在作业中单独启动步骤,可以执行单个步骤的端到端测试,而无需运行作业中的每个步骤。
以下代码片段是使用 JobLauncherTestUtils 来执行作业和步骤启动的示例:
@Before
public void setup() {
jobLaunchParameters = new JobParametersBuilder().addString("employeeData", EMPFILE_LOCATION)
.addString("resultsData", "file:/" + RESULTFILE_LOCATION)
.toJobParameters();
}
@Test
public void testEmployeeJob() throws Exception {
JobExecution execution = jobLauncherTestUtils.launchJob(jobLaunchParameters);
assertEquals(ExitStatus.COMPLETED, execution.getExitStatus());
StepExecution stepExecution =
execution.getStepExecutions().iterator().next();
assertEquals(ExitStatus.COMPLETED, stepExecution.getExitStatus());
}
在前面的代码中,借助 JobLauncherTestUtils,我们能够通过 JUnit @Test 方法的一部分简单 API 启动批处理作业,以及特定的步骤。@Before(设置)方法使用要处理的输入 employeeData 文件详情和要存储的输出结果文件位置来准备 JobLaunchParameters。
摘要
通过本章,我们学习了软件测试的重要性以及我们可能希望在 Spring Batch 应用程序上执行的各种软件测试类型。我们还了解了不同的开源框架,例如 JUnit 和 Mockito,用于对 Spring Batch 组件进行单元测试。我们以对 Spring 支持的理解结束本章,包括执行 Spring Batch 应用程序的单元测试、集成测试和功能测试的 API。
在附录部分,我们详细讨论了设置开发环境、项目配置和 Spring Batch 管理员。
附录 A. 附录
在上一章中,我们学习了软件测试的重要性,以及使用 JUnit、Mockito 等框架在 Spring 批量应用程序上执行软件测试的类型。Spring 批量项目开发设置需要一个包含 Java 和 IDE(Eclipse)的系统,并且需要设置一个带有依赖项配置的项目。此外,Spring 批量管理也是理解的一个重要方面。
在本节中,我们将涵盖以下主题:
-
设置 Java
-
设置 Eclipse IDE
-
设置项目和其依赖项
-
Spring 批量管理
设置 Java
Java 软件开发工具包(JDK)是由 Oracle 发布的应用平台,旨在为使用 Solaris、Linux、Mac OS X 或 Windows 的 Java 开发者提供支持。JDK 可从 Oracle 网络下载(www.oracle.com/technetwork/java/javase/downloads/index.html)。
它可以使用同一页面上提供的安装说明进行安装。

设置 Eclipse IDE
Eclipse 是最突出的 集成 开发环境(IDE)之一,具有基础工作区以用于项目开发,以及可扩展的插件以进行自定义。IntelliJ IDEA 和 NetBeans 是其他一些突出的 IDE。我们选择 Eclipse IDE 作为 Java EE 开发者的首选,通过 www.eclipse.org/downloads/packages/eclipse-ide-java-ee-developers/keplersr2。

通常,Eclipse IDE 会下载压缩版本,并可以解压以获取 eclipse 文件夹。在 eclipse 目录下,我们可以观察到可执行的 eclipse 程序(eclipse.exe),如图所示,确保 Java 开发工具包已安装在机器上,并且可在系统路径上使用。

设置项目和其依赖项
如 www.eclipse.org/m2e/ 指令中所述,需要将 Maven 软件集成到 Eclipse IDE 中,并且如 第一章 中 作业设计和执行 部分所述,带有 Maven 依赖项的 Java 项目可以管理 Spring 批量作业所需的依赖项。
Spring 批量管理
管理任务包括启动和停止流程作业,以及监控作业执行的性能统计信息。Spring 提供了带有管理项目的 Spring Batch,以拥有一个基于 Web 的控制工具,即 Spring Batch Admin 项目。Spring Batch Admin 是一个易于部署的项目,拥有非常好的文档。Spring Batch Admin 项目的文档可在docs.spring.io/spring-batch-admin/getting-started.html找到。

通过本附录,我们介绍了如何设置 Java,如何设置 Eclipse IDE,如何设置带有依赖项的项目,以及如何在 Spring Batch Administration 的帮助下管理批处理应用程序。
这总结了为 Spring Batch 作业应用开发需要学习的核心概念。










浙公网安备 33010602011771号