1、SpringBatch简介
1.1、简介
- 根据Spring官网描述,Spring Batch是一个轻量级的、完善的批处理应用框架,旨在支持企业系统建立健壮、高效的批处理应用。然而Spring Batch不是一个调度框架,它只关注于任务的处理,如日志监控、事务、并发问题等,但是它可以与其它调度框架一起联合使用,完成相应的调度任务,如Quartz、Tivoli、Control-M等。
- Spring Batch提供了很多非常实用的组件,包括了日志/跟踪、事务管理、作业处理统计、作业重新启动、跳过和资源管理。它还提供了更先进的技术服务和功能,支持通过优化和分区技术实现极高容量和高性能的批处理作业。Spring Batch既可以用于简单的用例(例如将文件读入数据库或运行存储过程),也可以用于复杂的、大容量的用例(例如在数据库之间移动大容量的数据、转换数据等等)。高容量批处理作业可以以高度可伸缩的方式利用框架来处理大量信息。
1.2、业务场景
· 周期性的提交批处理
· 把一个任务并行处理
· 消息驱动应用分级处理
· 大规模并行批处理
· 手工或调度使任务失败之后重新启动
· 处理时跳过部分记录
· 成批事务:为小批量的或有的存储过程/脚本的场景使用
举个栗子
假设一家电商公司,每天从不同渠道收集大量的销售数据。这些数据包含了各种商品的销售记录,但是格式和质量可能不一致。您希望将这些销售数据进行清洗和转换,以便进行后续的分析和报告生成。 使用Spring Batch,可以创建一个批处理作业来处理销售数据。作业的步骤可以包括从不同渠道读取销售数据,对数据进行清洗和转换,例如去除无效数据、修复格式错误、计算额外的指标等。然后,将清洗和转换后的数据写入数据库,以备后续的分析和报告生成使用。
2、架构
2.1、架构图
Spring Batch使用三层架构,三层分别为应用、核心和基础服务。应用层是用户写的批处理任务。核心层包含执行和控制任务必须的核心类。如JobLauncher、Job和Step的实现。应用和核心层基于一层公用的基础服务。基础服务包括通用的Reader,Writers,RetryTemplate。
2.2、Spring Batch任务流程
- JobLauncher:任务启动器。可以理解为程序的入口。
- Job:表示一个具体的任务,一个任务可以包含一个Step,也可以包含多个Step,由任务启动器进行启动。
- Step:一个具体的执行步骤,是任务的具体执行内容,一个Step的执行过程包括读数据(ItemReader)、处理数据(ItemProcessor)、写数据(ItemWriter)。
- JobRepository:批处理任务仓库。用来记录任务状态信息,可以看做是一个数据库的接口。
2.3、Spring Batch任务执行的两种方式
从上述文章中我们可以知道,任务的具体执行内容是Step,然后每一个Step里面都会有一个tasklet,它是一个任务执行单元。
chunk是数据单元的意思,chunk包含在tasklet里面,一个数据单元的大小由你自己决定,然后Spring Batch会进行一个do{}while()循环,就是循环的读数据、处理数据、写数据,直到数据全部处理完成。
2.3.1、tasklet执行方式
根据官网的解读,tasklet的一般适用于 Step 操作不需要读操作,或不需要写操作,或两者都不需要的情景下,tasklet定义如下。
<step id="step_hello">
<tasklet ref="hello" />
</step>
2.3.1、chunk执行方式
对于chunk则实用典型的JOB处理方式:读数据、处理数据、写数据,但是需要确定一个数据单元的大小,chunk定义方式如下:
<step id="chunkStep" next="clean">
<tasklet>
<chunk reader="reader" processor="processor" writer="writer" commit-interval="100" />
</tasklet>
</step>
3、实战详细操作
引入 依赖
首先,引Spring Batch的依赖项。在Maven项目中,在pom.xml文件中添加以下依赖项:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
</dependencies>
创建一个Spring配置文件(例如batch-config.xml
),并配置Spring Batch的相关组件和属性。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:batch="http://www.springframework.org/schema/batch"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!-- 数据源配置 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mydb" />
<property name="username" value="root" />
<property name="password" value="password" />
</bean>
<!-- JobRepository配置 -->
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<property name="databaseType" value="mysql" />
</bean>
<!-- 并发任务执行器配置 -->
<task:executor id="taskExecutor" pool-size="10" />
<!-- 事务管理器配置 -->
<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<!-- JobLauncher配置 -->
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
<property name="taskExecutor" ref="taskExecutor" />
</bean>
</beans>
定义数据模型:
根据需求,定义需要清洗和转换的数据模型。例如,假设数据模型是一个简单的用户对象,包含id、姓名和年龄字段。创建一个名为User
的Java类,如下所示:
public class User {
private Long id;
private String name;
private Integer age;
// 省略构造函数、getter和setter方法
}
创建ItemReader:
创建一个实现ItemReader
接口的自定义类,用于从数据源中读取数据。以下是一个读取数据库中用户数据的示例:
public class UserItemReader implements ItemReader<User> {
private JdbcTemplate jdbcTemplate;
private int rowCount = 0;
private int currentRow = 0;
public UserItemReader(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public User read() throws Exception {
if (currentRow < rowCount) {
String sql = "SELECT id, name, age FROM users LIMIT ?, 1";
User user = jdbcTemplate.queryForObject(sql, new Object[]{currentRow}, (rs, rowNum) -> {
User u = new User();
u.setId(rs.getLong("id"));
u.setName(rs.getString("name"));
u.setAge(rs.getInt("age"));
return u;
});
currentRow++;
return user;
} else {
return null;
}
}
public void setRowCount(int rowCount) {
this.rowCount = rowCount;
}
}
在此示例中,我们使用JdbcTemplate
来执行数据库查询,并在read
方法中逐行读取用户数据。
这里就可以根据你的业务需求设置各种各样的任务
创建ItemProcessor: 创建一个实现ItemProcessor
接口的自定义类,用于对读取的数据进行清洗和转换。
temProcessor的作用是在Spring Batch的批处理作业中对读取的数据进行处理、清洗和转换。它是Spring Batch框架中的一个关键接口,用于执行中间处理逻辑,并将处理后的数据传递给ItemWriter进行写入操作。
以下是一个对用户数据进行简单处理的示例:
public class UserProcessor implements ItemProcessor<UserData, ProcessedUserData> {
@Override
public ProcessedUserData process(UserData userData) throws Exception {
// 获取用户数据
String input = userData.getData();
// 去除首尾空格
String trimmedInput = input.trim();
// 过滤敏感信息
String filteredInput = filterSensitiveData(trimmedInput);
// 转换为大写
String upperCaseInput = filteredInput.toUpperCase();
// 创建处理后的用户数据对象
ProcessedUserData processedUserData = new ProcessedUserData();
processedUserData.setProcessedData(upperCaseInput);
return processedUserData;
}
private String filterSensitiveData(String input) {
// 在这里可以根据实际需求实现敏感信息过滤逻辑
// 使用正则表达式、敏感词库或其他方法进行过滤
// 这里是过滤手机号码和邮箱地址
String filteredInput = input
.replaceAll("\\b\\d{11}\\b", "[PHONE_NUMBER]")
.replaceAll("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b", "[EMAIL]");
return filteredInput;
}
}
我们做了以下处理和转换:
- 使用
trim
方法去除用户数据字符串首尾的空格。 - 使用
filterSensitiveData
方法过滤敏感信息,例如手机号码和邮箱地址。在示例中,我们使用了简单的正则表达式来过滤手机号码和邮箱地址,并将其替换为占位符。 - 使用
toUpperCase
方法将字符串转换为大写形式。 - 创建一个
ProcessedUserData
对象,将处理后的数据设置到输出对象中。
创建ItemWriter:
创建一个实现ItemWriter
接口的自定义类,用于将处理后的数据写入目标位置。以下是一个将用户数据写入数据库的示例:
public class UserItemWriter implements ItemWriter<User> {
private JdbcTemplate jdbcTemplate;
public UserItemWriter(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void write(List<? extends User> users) throws Exception {
for (User user : users) {
String sql = "INSERT INTO processed_users (id, name, age) VALUES (?, ?, ?)";
jdbcTemplate.update(sql, user.getId(), user.getName(), user.getAge());
}
}
}
在此示例中,我们使用JdbcTemplate
将处理后的用户数据插入到名为processed_users
的数据库表中。
创建作业配置:
创建一个包含作业配置的类,用于将ItemReader、ItemProcessor和ItemWriter组合在一起,定义一个批处理作业。以下是一个示例的作业配置类:
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Autowired
private DataSource dataSource;
@Bean
public ItemReader<User> userItemReader() {
return new UserItemReader(dataSource);
}
@Bean
public ItemProcessor<User, User> userItemProcessor() {
return new UserItemProcessor();
}
@Bean
public ItemWriter<User> userItemWriter() {
return new UserItemWriter(dataSource);
}
@Bean
public Step step1(ItemReader<User> reader, ItemProcessor<User, User> processor, ItemWriter<User> writer) {
return stepBuilderFactory.get("step1")
.<User, User>chunk(10)
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}
@Bean
public Job dataCleanupJob(Step step1) {
return jobBuilderFactory.get("dataCleanupJob")
.incrementer(new RunIdIncrementer())
.flow(step1)
.end()
.build();
}
}
在此示例中,我们通过Spring Batch的注解@EnableBatchProcessing
启用批处理功能,并定义了一个名为dataCleanupJob
的作业,其中包含一个名为step1
的步骤。
运行作业:
创建Job和Step配置:使用Spring Batch的配置文件,配置Job和Step。使用JobParametersBuilder
创建一个包含当前时间戳的Job参数,然后通过jobLauncher.run()
方法启动作业。
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class BatchApplication {
public static void main(String[] args) {
// 创建Spring应用上下文
ApplicationContext context = new AnnotationConfigApplicationContext(BatchConfiguration.class);
// 获取JobLauncher和Job实例
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
Job job = context.getBean("dataCleanupJob", Job.class);
try {
// 创建Job参数
JobParameters jobParameters = new JobParametersBuilder()
.addLong("time", System.currentTimeMillis())
.toJobParameters();
// 启动作业
jobLauncher.run(job, jobParameters);
} catch (Exception e) {
e.printStackTrace();
}
}
}
监听Listener
可以通过Listener
接口对特定事件进行监听,以实现更多业务功能。比如如果处理失败,就记录一条失败日志;处理完成,就通知下游拿数据等。
import org.springframework.batch.core.*;
import org.springframework.batch.core.listener.JobExecutionListenerSupport;
public class MyJobListener extends JobExecutionListenerSupport {
@Override
public void beforeJob(JobExecution jobExecution) {
// 在作业执行之前执行的逻辑
System.out.println("作业开始执行");
}
@Override
public void afterJob(JobExecution jobExecution) {
// 在作业执行之后执行的逻辑
if (jobExecution.getStatus() == BatchStatus.COMPLETED) {
System.out.println("作业执行成功");
} else if (jobExecution.getStatus() == BatchStatus.FAILED) {
System.out.println("作业执行失败");
}
}
@Override
public void onSkipInRead(Throwable t) {
// 在读取过程中发生跳过记录的逻辑
System.out.println("跳过读取记录");
}
@Override
public void onSkipInProcess(Object item, Throwable t) {
// 在处理过程中发生跳过记录的逻辑
System.out.println("跳过处理记录");
}
@Override
public void onSkipInWrite(Object item, Throwable t) {
// 在写入过程中发生跳过记录的逻辑
System.out.println("跳过写入记录");
}
}
将这个自定义的监听器添加到作业配置中:
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
// 省略其他配置
@Bean
public Job dataCleanupJob(Step step1, JobExecutionListener jobListener) {
return jobBuilderFactory.get("dataCleanupJob")
.incrementer(new RunIdIncrementer())
.listener(jobListener) // 添加自定义的监听器
.flow(step1)
.end()
.build();
}
@Bean
public JobExecutionListener jobListener() {
return new MyJobListener();
}
// 省略其他配置
}
这样 我们就能很清晰的看到 任务运行的情况啦
Spring Batch 使用内存缓冲机制,将读取的数据记录暂存于内存中,然后批量处理这些数据。通过减少对磁盘或数据库的频繁访问,内存缓冲可以提高读取和处理的效率,而且Spring Batch 提供了批量读取的机制,允许一次性读取和处理多个数据记录,这两点都减轻 I/O 压力。