点击上方 码匠笔记 ,选择 设为星标
优质文章,及时送达
作者:李东/codenotfound
原文:https://urlify.cn/Mzmqyi
我将向您展示如何使用
Spring Boot
创建一个的
Spring Batch
的
Hello World
示例。
(循序渐进)
因此,如果您是
Spring Batch
的初学者, 您一定会喜欢本指南
。
准备好了吗?
如果您想了解更多关于
Spring Batch
的信息,请访问Spring Batch教程页面。
在深入研究代码之前,让我们先看看
Spring Batch
框架。它包含以下主要构建块:
一个
Batch
(批处理)过程由一个
Job
(作业)组成。这个实体封装了整个批处理过程。
一个
Job
(作业)可以由一个或多个
Step
(步骤)组成。在大多数情况下,一个步骤将读取数据(通过
ItemReader
),处理数据(使用
ItemProcessor
),然后写入数据(通过
ItemWriter
)。
JobLauncher
处理启动一个
Job
(作业)。
最后,
JobRepository
存储关于配置和执行的
Job
(作业)的元数据。
为了演示
Spring Batch
是如何工作的,让我们构建一个简单的Hello World批处理作业。
在本例中,我们
从person.csv
文件中读取一个人的姓和名。从这些数据生成一个问候语。然后将此问候语写入
greeting .txt
文件。
我们会使用以下工具/框架:
Spring Batch 4.1
Spring Boot 2.1
Maven 3.6
我们的项目目录结构如下:
我们使用 Maven 构建并运行示例。如果还没有,下载并安装Apache Maven。
让我们使用Spring Initializr来生成Maven项目。确保选择
Batch
作为依赖项。
单击
Generate Project
生成并下载Spring Boot项目模板。在项目的根目录中,您将发现一个
pom.xml
文件,它是Maven项目的XML配置文件。
为了避免必须管理不同Spring依赖项的版本兼容性,我们将从
spring-boot-starter-parent
父POM继承默认配置。
生成的项目包含Spring Boo Starters管理着不同的Spring依赖项。
spring-boot-starter-batch
导入
Spring Boot
和
Spring Batch
依赖项。
spring-boot-starter-test
包含用于测试Spring引导应用程序的依赖项。它导入了包括JUnit、Hamcrest和Mockito在内的库。
这个也有依赖性
spring-batch-test
。这个库包含一些帮助类,它们将帮助测试批处理作业。
在plugins部分,您将找到Spring Boot Maven插件:
spring-boot-maven- plugin
。它帮助我们构建一个单一的、可运行的
uber-jar
。这是执行和发布代码的一种方便方法。此外,插件允许您通过Maven命令启动示例。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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>com.codenotfound</groupId>
<artifactId>spring-batch-hello-world</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-batch-hello-world</name>
<description>Spring Batch Hello World Example</description>
<url>https://codenotfound.com/spring-batch-example.html</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
我们使用Spring Boot,目的是让一个Spring Batch应用程序可以“直接运行”。
首先创建一个
SpringBatchApplication
类。它包含
main()
方法,该方法使用
Spring Boot
的
SpringApplication.run()
启动应用程序。
注意
@SpringBootApplication
是一个方便的注解,它添加了:
@Configuration
、
@EnableAutoConfiguration
和
@ComponentScan
。
有关Spring Boot的更多信息,请查看Spring Boot入门指南。
默认情况下,
Spring Batch
使用数据库存储已配置的批处理作业上的元数据。
在本例中,我们不直接使用数据库,而是使用基于内存映射的
Map
,运行
Spring Batch
。
spring-boot-starter-batch starter
依赖于
spring-boot-starter-jdbc
,并将尝试实例化数据源。添加
exclude = {DataSourceAutoConfiguration.class}
注解中添加
@SpringBootApplication
。这可以防止Spring Boot为数据库连接自动配置
DataSource
。
package com.codenotfound;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class SpringBatchApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBatchApplication.class, args);
}
}
在处理数据之前,通常希望将其映射到实体对象。
在我的示例中,输入数据存储在
src/test/resources/csv/persons.csv
文件中。
文件中的每一行都包含一个逗号分隔的姓和名。
John, Doe
Jane, Doe
我们将把这个数据映射到
Person
对象。这是一个包含姓和名的简单 POJO
。
package com.codenotfound.model;
public class Person {
private String firstName;
private String lastName;
public Person() {
// default constructor
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public String toString() {
return firstName + " " + lastName;
}
}
我们首先创建一个
BatchConfig
类,它将配置Spring Batch。类顶部的@Configuration注解表明Spring可以使用该类作为bean定义的源。
我们添加了@EnableBatchProcessing注解,它支持所有所需
Spring Batch
特性。它还提供了设置批处理作业的基本配置。
通过添加这个注解会需要很多操作。下面是
@EnableBatchProcessing
创建的概述:
JobRepository (bean名称 "jobRepository")
JobLauncher (bean名称 "jobLauncher")
JobRegistry (bean名称 "jobRegistry")
JobExplorer (bean名称 "jobExplorer")
PlatformTransactionManager (bean名称 "transactionManager")
JobBuilderFactory (bean名称"jobBuilders"),它可以方便地防止您必须将作业存储库注入到每个
Job
(作业)中
StepBuilderFactory (bean名称 "stepBuilders"),以方便您避免将作业存储库和事务管理器注入到每个
Step
(步骤)中
为了使
Spring Batch
使用基于Map的
JobRepository
,我们需要扩展
DefaultBatchConfigurer
。重写
setDataSource()
方法以不设置
DataSource
。这将导致自动配置使用基于Map的
JobRepository
。
@Configuration
@EnableBatchProcessing
public class BatchConfig extends DefaultBatchConfigurer {
@Override
public void setDataSource(DataSource dataSource) {
// initialize will use a Map based JobRepository (instead of database)
}
}
现在让我们继续配置Hello World Spring Batch 作业。
创建一个
HelloWorldJobConfig
配置类,并用添加
@Configuration
注解。
在
HelloWorldJobConfig
Bean中,我们使用
JobBuilderFactory
来创建作业。我们传递
Job
(作业)的名称和需要运行的
Step
(步骤)。
注意
在
helloWorlJob()
Bean中,Spring将自动注入
jobBuilders
和
stepBuilders
Beans。
在
HelloWorldStep
Bean中定义了我们的步骤执行的不同项。我们使用
StepBuilderFactory
创建步骤。
首先,我们传入步骤的名称。使用
chunk()
,我们指定每个事务中处理的项的数量。Chunk还指定步骤的输入(
Person
)和输出(
String
)类型。然后,我们将
ItemReader (reader)
、
ItemProcessor (processor)
和
ItemWriter (writer)
添加到步骤中。
我们使用FlatFileItemReader读取person CSV文件。这个类提供了读取和解析CSV文件的基本功能。
有一个
FlatFileItemReaderBuilder
实现,它允许我们创建一个
FlatFileItemReader
。我们首先指定读取文件中每一行的结果是
Person
对象。然后,我们将使用
name()
方法为
FlatFileItemReader
添加一个名称,并指定需要读取的资源(在本例中是
persons.csv
文件)。
为了让
FlatFileItemReader
处理我们的文件,我们需要指定一些额外的信息。首先,我们定义文件中的数据是带分隔符的(默认为逗号作为分隔符)。
Person
对象。这是使用
names()
来完成的,通过将名称与对象上的setter匹配,可以使Spring Batch映射字段。
在本文的例子中,一行的第一个字段将使用
firstName
setter进行映射。为了实现这一点,我们还需要指定
targetType
,即
Person
对象。
注意:
names(new String[] {"firstName", "lastName"})
PersonItemProcessor
处理数据。它将一个
Person
转换成一个问候
String
。我们将在下面的一个单独的类中定义它。
一旦数据被处理,我们将把它写入一个文本文件。我们使用
FlatFileItemWriter
来完成这项任务。
我们使用
FlatFileItemWriterBuilder
实现来创建一个
FlatFileItemWriter
。我们为
writer
添加一个名称,并指定需要将数据写入其中的资源(在本例中是
greeting.txt
文件)。
FlatFileItemWriter
需要知道如何将生成的输出转换成可以写入文件的单个字符串。在本例中,我们的输出已经是一个字符串,我们可以使用
PassThroughLineAggregator
。这是最基本的实现,它假定对象已经是一个字符串。
@Configuration
public class HelloWorldJobConfig {
@Bean
public Job helloWorlJob(JobBuilderFactory jobBuilders,
StepBuilderFactory stepBuilders) {
return jobBuilders.get("helloWorldJob")
.start(helloWorldStep(stepBuilders)).build();
}
@Bean
public Step helloWorldStep(StepBuilderFactory stepBuilders) {
return stepBuilders.get("helloWorldStep")
.<Person, String>chunk(10).reader(reader())
.processor(processor()).writer(writer()).build();
}
@Bean
public FlatFileItemReader<Person> reader() {
return new FlatFileItemReaderBuilder<Person>()
.name("personItemReader")
.resource(new ClassPathResource("csv/persons.csv"))
.delimited().names(new String[] {"firstName", "lastName"})
.targetType(Person.class).build();
}
@Bean
public PersonItemProcessor processor() {
return new PersonItemProcessor();
}
@Bean
public FlatFileItemWriter<String> writer() {
return new FlatFileItemWriterBuilder<String>()
.name("greetingItemWriter")
.resource(new FileSystemResource(
"target/test-outputs/greetings.txt"))
.lineAggregator(new PassThroughLineAggregator<>()).build();
}
}
在大多数情况下,您将希望在批处理作业期间应用一些数据处理。可以使用ItemProcessor来操作。
在我们的示例中,我们将
Person
对象转换为一个简单的问候语
String
为此,我们创建一个实现
ItemProcessor
接口的
PersonItemProcessor
。我们实现了
process()
方法,它将人名和姓氏添加到字符串中。
调试的过程中,我们记录日志结果。
public class PersonItemProcessor
implements ItemProcessor<Person, String> {
private static final Logger LOGGER =
LoggerFactory.getLogger(PersonItemProcessor.class);
@Override
public String process(Person person) throws Exception {
String greeting = "Hello " + person.getFirstName() + " "
+ person.getLastName() + "!";
LOGGER.info("converting '{}' into '{}'", person, greeting);
return greeting;
}
}
为了测试本的例子,我们创建了一个基本的单元测试用例。它将运行批处理作业并检查是否成功完成。
我们使用
@RunWith
和
@SpringBootTest
测试注解告诉
JUnit
使用Spring的测试支持运行,并使用SpringBoot的支持引导。
Spring Batch
附带一个
JobLauncherTestUtils
实用程序类,用于测试批处理作业。
我们首先创建一个内部
BatchTestConfig
类,将helloWorld作业添加到
JobLauncherTestUtils
bean中。然后使用此bean的
launchJob()
方法运行批处理作业。
如果执行的作业没有任何错误,则
ExitCode
的值为
COMPLETED
。
@RunWith(SpringRunner.class)
@SpringBootTest(
classes = {SpringBatchApplicationTests.BatchTestConfig.class})
public class SpringBatchApplicationTests {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Test
public void testHelloWorldJob() throws Exception {
JobExecution jobExecution = jobLauncherTestUtils.launchJob();
assertThat(jobExecution.getExitStatus().getExitCode())
.isEqualTo("COMPLETED");
}
@Configuration
@Import({BatchConfig.class, HelloWorldJobConfig.class})
static class BatchTestConfig {
@Autowired
private Job helloWorlJob;
@Bean
JobLauncherTestUtils jobLauncherTestUtils()
throws NoSuchJobException {
JobLauncherTestUtils jobLauncherTestUtils =
new JobLauncherTestUtils();
jobLauncherTestUtils.setJob(helloWorlJob);
return jobLauncherTestUtils;
}
}
}
要触发上述测试用例,请在项目根文件夹中打开命令提示符,并执行以下
Maven
命令:
mvn test
结果是构建成功,并在此期间执行批处理作业。
. ____ _ __ _ _
/// / ___'_ __ _ _(_)_ __ __ _ / / / /
( ( )/___ | '_ | '_| | '_ // _` | / / / /
/// ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_/__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.5.RELEASE)
2019-05-30 19:11:12.784 INFO 14588 --- [ main] c.c.SpringBatchApplicationTests : Starting SpringBatchApplicationTests on DESKTOP-2RB3C1U with PID 14588 (started by Codenotfound in C:/Users/Codenotfound/repos/spring-batch/spring-batch-hello-world)
2019-05-30 19:11:12.785 INFO 14588 --- [ main] c.c.SpringBatchApplicationTests : No active profile set, falling back to default profiles: default
2019-05-30 19:11:13.305 WARN 14588 --- [ main] o.s.b.c.c.a.DefaultBatchConfigurer : No datasource was provided...using a Map based JobRepository
2019-05-30 19:11:13.306 WARN 14588 --- [ main] o.s.b.c.c.a.DefaultBatchConfigurer : No transaction manager was provided, using a ResourcelessTransactionManager
2019-05-30 19:11:13.328 INFO 14588 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : No TaskExecutor has been set, defaulting to synchronous executor.
2019-05-30 19:11:13.350 INFO 14588 --- [ main] c.c.SpringBatchApplicationTests : Started SpringBatchApplicationTests in 0.894 seconds (JVM running for 1.777)
2019-05-30 19:11:13.732 INFO 14588 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=helloWorldJob]] launched with the following parameters: [{random=459672}]
2019-05-30 19:11:13.759 INFO 14588 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [helloWorldStep]
2019-05-30 19:11:13.812 INFO 14588 --- [ main] c.c.batch.PersonItemProcessor : converting 'John Doe' into 'Hello John Doe!'
2019-05-30 19:11:13.822 INFO 14588 --- [ main] c.c.batch.PersonItemProcessor : converting 'Jane Doe' into 'Hello Jane Doe!'
2019-05-30 19:11:13.842 INFO 14588 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=helloWorldJob]] completed with the following parameters: [{random=459672}] and the following status: [COMPLETED]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.953 s - in com.codenotfound.SpringBatchApplicationTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.852 s
[INFO] Finished at: 2019-05-30T19:11:14+02:00
[INFO] ------------------------------------------------------------------------
您可以在
target/test-output /greeting .txt
文件中找到结果:
Hello John Doe!
Hello Jane Doe!
如果您想运行上面的代码示例,您可以在这里获得完整的源代码。
在本入门教程中,您学习了如何使用Spring Boot和Maven创建一个简单的Spring Batch示例。
遗漏热文?赶紧标星
1. 阿里社招面试指南
2. 阿里应届生面试指南
3. 探寻线程池是如何工作的
4. 到底线程池应该设置多少合适?
5. 跳槽的必备条件是有一份好的简历
6. 不是所有的 Github 都适合写在简历上
7. 所没有项目经验找工作处处碰壁怎么办
8. 每一个开发人员都应该懂得的 UML 规范
9. 工作环境没机会接触高并发、分布式怎么办?
10. 这算是有史以来讲数据库连接池数最清楚的文章了
11. 你以为认为 count(1) 比 count(*) 效率高么?
12. 用了这么多年 Spring Boot 你知道他爹有多大背景吗?