在 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. django 连接 mysql数据库

    1.安装mysqlclient pip install mysqlclient 或 pycharm --> File --> settings --> Project: (项目名称) ...

  2. Bash echo输出带颜色和背景的文本

    Bash echo输出带颜色和背景的文本 1.先上效果图 2.bash代码 #!/bin/bash #************************************************* ...

  3. active cab inf文件编写

    最近做了一个网页下载控件.主要就是实现ActiveX控件功能. 由于自己是第一次做,不熟悉其过程.中间走了很多弯路.现在把走过得路程记录部分,希望对其他人可以有点用. 首先制作一个你自己的DLL文件. ...

  4. java 动态增加应用服务器,出现的消息队列的消费者提报错问题

    java 动态增加应用服务器,出现的消息队列的消费者提报错问题 在项目中,有这样的业务场景,在某一个时间段,客户流量瞬间增大,服务器瞬间很大,出现高并发问题.有一种解决方案就是脚本动态增加业务服务器, ...

  5. numpy数组运算

    一.四则运算   (以此为例) 1.加法 2.减法 3.乘法 4.除法 5.幂运算 二.比较运算   (以此为例) 1.<   > 2.>=    <= 3.==    != ...

  6. Flink的sink实战之二:kafka

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  7. Spring Boot API 统一返回格式封装

    今天给大家带来的是Spring Boot API 统一返回格式封装,我们在做项目的时候API 接口返回是需要统一格式的,只有这样前端的同学才可对接口返回的数据做统一处理,也可以使前后端分离 模式的开发 ...

  8. 15 CGI和WSGI

    15 CGI和WSGI CGI是通用网关接口,是连接web服务器和应用程序的接口,用户通过CGI来获取动态数据或文件等. CGI程序是一个独立的程序,它可以用几乎所有语言来写,包括perl,c,lua ...

  9. 面试题:对NotNull字段插入Null值 有啥现象?

    Hi,大家好!我是白日梦. 今天我要跟你分享的话题是:"对NotNull字段插入Null值有啥现象?" 一. 推荐阅读 首发地址:https://mp.weixin.qq.com/ ...

  10. malloc/free与new/delete的区别(转)

    相同点:都可用于申请动态内存和释放内存 不同点:(1)操作对象有所不同.malloc与free是C++/C 语言的标准库函数,new/delete 是C++的运算符.对于非内部数据类的对象而言,光用m ...