在 Spring Batch 中进行数据及参数传递的方法。

1 引言

本文是 Spring Batch 系列文章的第9篇,有兴趣的可见文章:

前面文章以实例的方式对 Spring Batch 进行批处理进行详细说明,相信大家对字符串、文件,关系型数据库及 NoSQL 数据库的读取,处理,写入流程已比较熟悉。有小伙伴就问,针对这个任务流程,期间有多个步骤,从任务( Job )启动,到作业步( Step )的执行,其中又包含读组件、处理组件、写组件,那么,针对这个流程,若中间需要传递自定义的数据,该如何处理?本文将对 Spring Batch 进行数据传递的方法进行描述,依然会使用代码实例的方式进行讲解。包括以下几个内容:

  • 基于 Mybatis-plus 集成多数据源的数据库访问
  • 使用 ExecutionContext 共享数据
  • StepScope 动态绑定参数传递

2 开发环境

  • JDK环境: jdk1.8
  • Spring Boot: 2.1.4.RELEASE
  • Spring Batch:4.1.2.RELEASE
  • 开发IDE: IDEA
  • 构建工具Maven: 3.3.9
  • 日志组件logback:1.2.3
  • lombok:1.18.6
  • MySQL: 5.6.26
  • Mybatis-plus: 3.4.0

本示例源码已放至githubhttps://github.com/mianshenglee/spring-batch-example/tree/master/spring-batch-param,请结合示例代码进行阅读。

3 基于 Mybatis-plus 集成多数据源的数据库访问

本示例还是使用原来示例功能,从源数据库读取用户数据,处理数据,然后写入到目标数据库。其中会在任务启动时传递参数,并在作业步中传递参数。之前已经介绍过如何使用 beetlsql 进行多数据源配置([便捷的数据读写-spring batch(5)结合beetlSql进行数据读写][5]),实现数据批处理。还有很多朋友使用 Mybatis 或 Mybatis-plus 进行数据库读写,因此,有必要提一下 Spring Batch 如何结合 Mybatis 或 Mybatis-plus 配置多数据源操作。本示例以 Mybatis-plus 为例。

示例工程中的sql目录有相应的数据库脚本,其中源数据库mytest.sql脚本创建一个test_user表,并有相应的测试数据。目标数据库 my_test1.sqlmytest.sql表结构一致,spring-batch-mysql.sql是 Spring Batch 本身提供的数据库脚本。

3.1 pom 文件中引入 Mybatis-plus

<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>

3.2 配置及使用多数据源

本示例会涉及三个数据库,分别是 Spring Batch 本身数据库,需要批处理的源数据库,批处理的目标数据库。因此需要处理多个数据库,利用多套源策略,可以很简单就完成多套数据源的处理。简单来说主要分为以下几个步骤:

  • 配置多数据源连接信息
  • 根据不同数据源划分mapper 包,entity包,mapper.xml文件包
  • 根据不同数据源配置独立的SqlSessionFactory
  • 根据不同的应用场景,使用不同的 mapper

关于多数据源多套源策略的详细配置过程,可以参考我的另一篇文章《搞定SpringBoot多数据源(1):多套源策略

4 ExecutionContext 传递参数

关于 Spring Batch 的读数据( ItemReader )、处理数据( ItemProcessor )、写数据( ItemWriter )的配置流程,可以参考前面系列文章,本文不再详细描述。我们需要记住的是,当一个作业( Job )启动,Spring Batch 是通过作业名称( Job name)及 作业参数( JobParameters )作为唯一标识来区分不同的作业。一个 Job 下可以有多个作业步( Step ),每个 Step 中就是有具体的操作逻辑(读、处理、写)。在 Job 和 Step 下的各个操作步骤间,如何传递,,这里就需要理解 ExecutionContext 的概念。

4.1 ExecutionContext 概念

在 Job 的运行及 Step 的运行过程中,Spring Batch 提供 ExecutionContext 进行运行数据持久化,利用它,可以根据业务进行数据共享,如用来重启的静态数据与状态数据。如下图:

Execution Context 本质上来讲就是一个 Map<String,Object> ,它是Spring Batch 框架提供的持久化与控制的 key/value 对,可以让开发者在 Step 运行或Job 运行过程中保存需要进行持久化的状态,它可以。分为两类,一类是Job 运行的上下文(对应数据表:BATCH_JOB_EXECUTION_CONTEXT),另一类是Step Execution的上下文(对应数据表BATCH_STEP_EXECUTION_CONTEXT)。两类上下文关系:一个 Job 运行对应一个 Job Execution 的上下文(如上图中蓝色部分的 ExecutionContext ),每个 Step 运行对应一个 Step Execution 上下文(如上图中粉色部分的 ExecutionContext );同一个 Job 中的 Step Execution 共用 Job Execution 的上下文。也就是说,它们的作用范围有区别。因此,如果同一个 Job 的不同 Step 间需要共享数据时,可以通过 Job Execution 的上下文共享数据。根据 ExecutionContext 的共享数据特性,则可以实现在不同步骤间传递数据。

4.2 ExecutionContext 传递数据

一个 Job 启动后,会生成一个 JobExecution ,用于存放和记录 Job 运行的信息,同样,在 Step 启动后,也会有对应的 StepExecution 。如前面所说,在 JobExecution 和 StepExecution 中都会有一个 ExecutionContext ,用于存储上下文。因此,数据传递的思路就是确定数据使用范围,然后通过 ExecutionContext 传入数据,然后就可以在对应的范围内共享数据。如当前示例,需要 Job 范围内共享数据,在读组件( ItemReader )和写组件( ItemWriter )中传递读与写数据的数量( size ),在 Job 结束时,输出读及写的数据量。实际上 Spring Batch 会自动计算读写数量,本示例仅为了显示数据共享功能。

那么,如何获取对应的 Execution ?,Spring Batch 提供了 JobExecutionListener 和 StepExecutionListener 监听器接口,通过实现监听器接口,分别可以在开启作业前( beforeJob )和 完成作业后( afterJob )afterJob ),开启作业步前( beforeStep)及 完成作业步后( afterStep )获取对应的 Execution ,然后进行操作。

4.2.1 实现监听器接口

在自定义的 UserItemReader 和 UserItemWriter 中,实现 StepExecutionListener 接口,其中使用 StepExecution 作为成员,从 beforeStep 中获取。如下:

public class UserItemWriter implements ItemWriter<TargetUser>, StepExecutionListener {
private StepExecution stepExecution;
//...略
@Override
public void beforeStep(StepExecution stepExecution) {
this.stepExecution = stepExecution;
}
}

读组件( UserItemReader )也使用同样的方式。而在作业结束后,获取参数,则可以继承 JobExecutionListenerSupport ,实现自己感兴趣的方法,也从参数中获取 JobExecution,然后获取参数进行处理。

public class ParamJobEndListener extends JobExecutionListenerSupport {
@Override
public void afterJob(JobExecution jobExecution) {}
}

4.2.2 设置用于传递的数据

由于我们需要在 Job 范围内传递参数,获取到 StepExecution 后,可以获得相应的 JobExecution ,进而获取 Job 对应的 executionContext,这样,就可以在 Job 范围内共享参数数据了。如下是在读组件中进行配置

ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext();
executionContext.put(SyncConstants.PASS_PARAM_READ_NUM, items.size());

同样在写组件中,获取到 ExecutionContext 后,可以对参数进行处理。本示例中,是通过对 ItemReader 传递的处理数目参数进行累加处理,得到结果。

@Override
public void write(List<? extends TargetUser> items) {
ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext();
Object currentWriteNum = executionContext.get(SyncConstants.PASS_PARAM_WRITE_NUM);
if (Objects.nonNull(currentWriteNum)) {
log.info("currentWriteNum:{}", currentWriteNum);
executionContext.put(SyncConstants.PASS_PARAM_WRITE_NUM, items.size()+(Integer)currentWriteNum);
} else {
executionContext.put(SyncConstants.PASS_PARAM_WRITE_NUM, items.size());
}

最后在作业结束后,在实现 JobExecutionListenerSupport 的接口中,afterJob 函数中,对参数进行输出。

public class ParamJobEndListener extends JobExecutionListenerSupport {
@Override
public void afterJob(JobExecution jobExecution) {
ExecutionContext executionContext = jobExecution.getExecutionContext();
Integer writeNum = (Integer)executionContext.get(SyncConstants.PASS_PARAM_WRITE_NUM);
log.info(LogConstants.LOG_TAG + "writeNum:{}",writeNum);
}
}

5 StepScope 动态绑定参数传递

5.1 StepScope及后期绑定

前面说到在 Job 及 Step 范围内,使用 ExecutionContext 进行数据共享,但,如果需要在 Job 启动前设置参数,并且每次启动输入的参数是动态变化的(比如增量同步时,日期是基于上一次同步的时间或者ID),也就是说,每次运行,需要根据参数新建一个操作步骤(如 ItemReader、ItemWriter等),我们知道,由于在 Spring IOC 中加载的Bean,默认都是单例模式的,因此,需要每次运行新建,运行完销毁,新建是在运行时进行的。这就需要用到StepScope 及后期绑定技术。

在之前的示例中,已出现过 StepScope,它的作用是提供了操作步骤的作用范围,某个 Spring Bean 使用注解StepScope,则表示此 Bean 在作业步( Step )开始的时候初始化,在 Step 结束的时候销毁,也就是说 Bean的作用范围是在 Step 这个生命周期中。而 Spring Batch 通过属性后期绑定技术,在运行期获取属性值,并使用 SPEL 的表达式进行属性绑定。而在 StepScope 中,Spring Batch 框架提供 JobParameters,JobExecutionContext,StepExecutionContext,当然也可以使用 Spring 容器中的 Bean ,如 JobExecution ,StepExecution。

5.2 作业参数传递及动态获取 StepExecution

一个 Job 是由 Job name 及 JobParameters 作为唯一标识的,也就是说只有 job name 和 JobParameters 不一致时,Spring Batch 才会启动一个新的 Job,一致的话就当作是同一个 Job ,若 此 Job 未执行过,则执行;若已执行过且是 FAILED 状态,则尝试重新运行此 Job ,若已执行过且是 COMPLETED 状态,则会报错。

本示例中,Job 启动时输入时间参数,在 ItemReader 中使用 StepScope 注解,然后把时间参数绑定到 ItemReader 中,同时绑定 StepExecution ,以便于在 ItemReader 对时间参数及 StepExecution 进行操作。

5.2.1 设置时间参数

在使用 JobLauncher 启动 Job 时,是需要输入 jobParameters 作为参数的。因此可以创建此对象,并设置参数。

JobParameters jobParameters = new JobParametersBuilder()
.addLong("time",timMillis)
.toJobParameters();

5.2.2 动态绑定参数

在配置 Step 时,需要创建ItemReader 的 Bean,为了使用动态参数,在 ItemReader 中设置 Map 存放参数,并设置 StepExecution 为成员,以便于后面使用 ExecutionContext。

public class UserItemReader implements ItemReader<User> {
protected Map<String, Object> params;
private StepExecution stepExecution; public void setStepExecution(StepExecution stepExecution) {
this.stepExecution = stepExecution;
}
}

使用 StepScope 进行配置:

@Bean
@StepScope
public ItemReader paramItemReader(@Value("#{stepExecution}") StepExecution stepExecution,
@Value("#{jobParameters['time']}") Long timeParam) {
UserItemReader userItemReader = new UserItemReader();
//设置参数
Map<String, Object> params = CollUtil.newHashMap();
Date datetime = new Date(timeParam);
params.put(SyncConstants.PASS_PARAM_DATETIME, datetime);
userItemReader.setParams(params);
userItemReader.setStepExecution(stepExecution); return userItemReader;
}

注意:此时 ItemReader 不可再使用实现 StepExecutionListener 的方式来对 stepExecution 赋值,由于 ItemReader 是动态绑定的,StepExecutionListener 将不再起作用,因此需要在后期绑定中来绑定 stepExecution Bean 的方式来赋值。

5.2.3 设置及传递参数

ItemReader 获取到 StepExecution 后即可获取 ExecutionContext,然后可以像前面说的使用 ExecutionContext 方式进行数据传递。如下:

ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext();
//readNum参数
executionContext.put(SyncConstants.PASS_PARAM_READ_NUM, items.size());
//datetime参数
executionContext.put(SyncConstants.PASS_PARAM_DATETIME,params.get(SyncConstants.PASS_PARAM_DATETIME));

6.总结

在 Job 和 Step 不同的数据范围中,可使用 ExecutionContext 共享数据。本文以传递处理数量为例,使用 Mybatis-plus,基于 ExecutionContext ,结合 StepScope及后期绑定技术,实现在 Job 启动传入参数,然后在 ItemReader、ItemProcessor、ItemWriter 及 Job 完成后的数据共享及传递。如果你在使用 Spring Batch 过程中需要进行数据共享与传递,请试试这种方式吧。

往期文章

如果文章内容对你有帮助,欢迎转发分享~

我的公众号(搜索Mason技术记录),获取更多技术记录:

数据共享-spring batch(9)上下文处理的更多相关文章

  1. Spring Batch在大型企业中的最佳实践

    在大型企业中,由于业务复杂.数据量大.数据格式不同.数据交互格式繁杂,并非所有的操作都能通过交互界面进行处理.而有一些操作需要定期读取大批量的数据,然后进行一系列的后续处理.这样的过程就是" ...

  2. Spring Batch 批处理框架

    <Spring Batch 批处理框架>基本信息作者: 刘相 出版社:电子工业出版社ISBN:9787121252419上架时间:2015-1-24出版日期:2015 年2月开本:16开页 ...

  3. Spring Batch实践

    Spring Batch在大型企业中的最佳实践 在大型企业中,由于业务复杂.数据量大.数据格式不同.数据交互格式繁杂,并非所有的操作都能通过交互界面进行处理.而有一些操作需要定期读取大批量的数据,然后 ...

  4. 图书简介:Spring Batch批处理框架

    大数据时代批处理利器,国内首度原创解析Spring Batch框架. 内容简介: <Spring Batch 批处理框架>全面.系统地介绍了批处理框架Spring Batch,通过详尽的实 ...

  5. Spring Batch 专题

    如今微服务架构讨论的如火如荼.但在企业架构里除了大量的OLTP交易外,还存在海量的批处理交易.在诸如银行的金融机构中,每天有3-4万笔的批处理作业需要处理.针对OLTP,业界有大量的开源框架.优秀的架 ...

  6. spring batch (二) 元数据表

    内容来自<Spring Batch 批处理框架>,作者:刘相. 一.spring batch 框架进行元数据管理共有六张表,三张SEQUENCE用来分配主键的,九张表分别是: BATCH_ ...

  7. spring batch (一) 常见的基本的概念介绍

    SpringBatch的基本概念介绍 内容来自<Spring Batch 批处理框架>,作者:刘相. 一.配置文件 在项目中使用spring batch 需要在配置文件中声明: 事务管理器 ...

  8. spring batch批量处理框架

    spring batch精选,一文吃透spring batch批量处理框架 前言碎语 批处理是企业级业务系统不可或缺的一部分,spring batch是一个轻量级的综合性批处理框架,可用于开发企业信息 ...

  9. spring batch批处理框架学习

    内如主要来自以下链接: http://www.importnew.com/26177.html http://www.infoq.com/cn/articles/analysis-of-large-d ...

随机推荐

  1. C#+Arduino Uno 实现声控系统完全实施手册

    话不多说先上视频,一看就懂 另外可参考这里:https://www.cnblogs.com/dehai/p/4285749.html ,这个近6年前的帖子 程序结构 程序分成上位机(PC端)与下位机( ...

  2. Shell 教程01

    Shell 教程 Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁.Shell 既是一种命令语言,又是一种程序设计语言. Shell 是指一种应用程序,这个应用程序提供了一个 ...

  3. ps怎么抠图并和另一张图片合并?

    1.首先打开PS.然后随便选取两张图片.比如下图我想把那个小花朵扣出来移动到另外一张图片上. 2.用套锁工具把那个小花朵扣出来.如图我标出了套锁工具的图标.以及扣出来的小花朵.这里也可以选择" ...

  4. 文科妹子都会用 GitHub,你这个工科生还等什么

    在某乎上刷到一条关于 GitHub 的留言,如下: 点赞人数还不少,这说明还真有不少工科生不会用 GitHub,你看大小写都没有区分(手动狗头).所以我就想写篇文章科普下,"新手如何使用 G ...

  5. 重温Java泛型,带你更深入地理解它,更好的使用它!

    1. 引言 jdk5.0中引入了Java泛型,目的是减少错误,并在类型上添加额外的抽象层. 本文将简要介绍Java中的泛型.泛型背后的目标以及如何使用泛型来提高代码的质量. 2. 为什么要用泛型? 设 ...

  6. wcf调用时时间参数问题,返回值中有日期格式得值得问题

    第一种情况,客户端在调用wcf后台服务时,参数对象有日期类型得属性,日期默认值不能是datetime.minvalue得值,需要设置大于1971-1-1,不然调不通服务, 第二种情况,服务连通了,并且 ...

  7. 内网渗透 day2-nmap和nc的使用

    nmap和nc的使用 nmap的使用 1. nmap -sSV 172.16.100.214 -T4 -F -sS进行SYN扫描,是比较隐匿的 -sV探测打开端口的服务的信息 -sSV将上面两种一起使 ...

  8. [python学习手册-笔记]002.python核心数据类型

    python核心数据类型 ❝ 本系列文章是我个人学习<python学习手册(第五版)>的学习笔记,其中大部分内容为该书的总结和个人理解,小部分内容为相关知识点的扩展. 非商业用途转载请注明 ...

  9. git clone 出现"error: RPC failed; curl 56 GnuTLS recv error (-9): A TLS packet with unexpected length was received."

    1. 最近用git pull几个大项目,总是报如下错误: error: RPC failed; curl 56 GnuTLS recv error (-9): A TLS packet with un ...

  10. css类名的使用

    ` Document .box { color: rgba(255, 0, 0, 1) } .box .box2 { color: rgba(0, 128, 0, 1) } .box.box2 { c ...