前言:
什么是Spring的批处理?简而言之,批处理执行作业。单个作业至少包含一个步骤。
前段时间公司需求让在现有系统中添加新功能。该系统必须从外部数据库获取数据。该外部数据库可通过REST API访问,但我们无法“动态”使用它,因为它花了太多时间。因此,我们决定从外部数据库和我们系统使用的数据库中复制数据。换句话说,我们必须下载我们需要的东西并将其存储到我们的数据库中。之后我们可以“在运行中”使用数据而不会产生任何性能开销。
该应用程序应执行如下操作:
- 通过REST API从外部数据库下载数据并将其另存为CSV文件。
- 每个数据集都是独立的,因此作业中的步骤也必须是独立的(有一个例外)。
- 创建与CSV文件一样多的MongoDB集合。
- 将每行从CSV文件转换为MongoDB文档。
- 将文档保存在相关集合中。
- 之后删除CSV文件。
- 保留批次,作业和步骤执行的详细信息。
项目架构:
- Java 8
- Maven 3.5.2
- Spring Boot 1.5.10
- Spring Web 4.3.13
- MongoDB 3.4
pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
Spring-Batch和MongoDB启动器创建一个Spring Boot应用程序。我为spring-web添加了一个单独的依赖项来拥有一个REST客户端。Spring-web使用httpcomponents。并且commons-io用于处理CSV文件。
操作
Spring提供的有关Spring Batch的所有文档,该步骤应如下所示:
- 收集有关CSV标头的信息
- 以CSV格式下载数据
- 将每一行转换为MongoDB文档
- 以块的形式插入数据库
- 重复上述步骤,直到下载并处理完所有内容
直接看代码。application.properties包含MongoDB详细信息以及一些其他属性:
spring.data.mongodb.host= mongodb://localhost/
spring.data.mongodb.port= 27017
spring.data.mongodb.database= db-replica
spring.data.mongodb.repositories.enabled= true
mongodb.bulk.batchSize= 1000
# [s,S] stands for seconds, [m,M] stands for minutes, [h,H] stands for hours. Format is 1m = 1 minute, 12H == 12 hours
request.connection.timeout= 2h
# csv中有多少行将被加载到程序内存 To many could cause Out Of Memory exception.
csv.chunkSize= 50000
#保存文件到服务器位置
csv.directory= ./csv
# a 日志
log.while.waiting.for.response= true
# 日志消息在日志中显示的频率(秒
# 5表示每隔15秒,日志中会显示一条消息,直到请求完成。
log.wait.interval= 5
Tasklet 为每个步骤定义了相同的抽象,因为每个步骤以相同的方式处理数据,但它必须将它们存储到不同的集合中:
abstract class AbstractStep implements Tasklet {
protected final Logger logger;
private static final String ERROR_COLLECTION_NAME = "csvErrors";
@Value("${palantir.e