青年IT男

个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。微信公众号:青年IT男。

SpringBatch

SpringBatch

我将向您展示如何使用 SpringBoot创建一个的 SpringBatchHelloWorld示例。(循序渐进) 因此,如果您是 SpringBatch的初学者,您一定会喜欢本指南。准备好了吗? 如果您想了解更多关于 SpringBatch的信息,请访问Spring Batch教程页面。

1.Spring Batch框架工作原理

在深入研究代码之前,让我们先看看 SpringBatch框架。它包含以下主要构建块

img

一个 Batch(批处理)过程由一个 Job(作业)组成。这个实体封装了整个批处理过程。

一个 Job(作业)可以由一个或多个 Step(步骤)组成。在大多数情况下,一个步骤将读取数据(通过 ItemReader),处理数据(使用 ItemProcessor),然后写入数据(通过 ItemWriter)。

JobLauncher处理启动一个 Job(作业)。

最后, JobRepository存储关于配置和执行的 Job(作业)的元数据。

为了演示 SpringBatch是如何工作的,让我们构建一个简单的Hello World批处理作业。

在本例中,我们 从person.csv文件中读取一个人的姓和名。从这些数据生成一个问候语。然后将此问候语写入 greeting.txt文件。

2.示例概述

我们会使用以下工具/框架:

  • SpringBatch4.1
  • SpringBoot2.1
  • Maven3.6

我们的项目目录结构如下:

img

3. Maven配置

我们使用Maven构建并运行示例。如果还没有,下载并安装Apache Maven。

让我们使用Spring Initializr来生成Maven项目。确保选择 Batch作为依赖项。

img

单击 GenerateProject生成并下载Spring Boot项目模板。在项目的根目录中,您将发现一个 pom.xml文件,它是Maven项目的XML配置文件。

为了避免必须管理不同Spring依赖项的版本兼容性,我们将从 spring-boot-starter-parent 父POM继承默认配置。

生成的项目包含Spring Boo Starters管理着不同的Spring依赖项。

spring-boot-starter-batch导入 SpringBootSpringBatch依赖项。

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>

4. Spring Boot 配置

我们使用Spring Boot,目的是让一个Spring Batch应用程序可以“直接运行”。首先创建一个 SpringBatchApplication类。它包含 main()方法,该方法使用 SpringBootSpringApplication.run()启动应用程序。

注意@SpringBootApplication是一个方便的注解,它添加了: @Configuration@EnableAutoConfiguration@ComponentScan

有关Spring Boot的更多信息,请查看Spring Boot入门指南。

默认情况下, SpringBatch使用数据库存储已配置的批处理作业上的元数据。

在本例中,我们不直接使用数据库,而是使用基于内存映射的 Map,运行 SpringBatch

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);  }}

5. 创建实体模型

在处理数据之前,通常希望将其映射到实体对象。

在我的示例中,输入数据存储在 src/test/resources/csv/persons.csv文件中。

文件中的每一行都包含一个逗号分隔的姓和名。

John, DoeJane, 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;  }}

6. 配置 Spring Batch Job

我们首先创建一个 BatchConfig类,它将配置Spring Batch。类顶部的@Configuration注解表明Spring可以使用该类作为bean定义的源。

我们添加了@EnableBatchProcessing注解,它支持所有所需 SpringBatch特性。它还提供了设置批处理作业的基本配置。

通过添加这个注解会需要很多操作。下面是 @EnableBatchProcessing创建的概述:

  • JobRepository (bean名称 “jobRepository”)
  • JobLauncher (bean名称 “jobLauncher”)
  • JobRegistry (bean名称 “jobRegistry”)
  • JobExplorer (bean名称 “jobExplorer”)
  • PlatformTransactionManager (bean名称 “transactionManager”)
  • JobBuilderFactory (bean名称”jobBuilders”),它可以方便地防止您必须将作业存储库注入到每个 Job(作业)中
  • StepBuilderFactory (bean名称 “stepBuilders”),以方便您避免将作业存储库和事务管理器注入到每个 Step(步骤)中

为了使 SpringBatch使用基于Map的 JobRepository,我们需要扩展 DefaultBatchConfigurer。重写 setDataSource()方法以不设置 DataSource。这将导致自动配置使用基于Map的 JobRepository

package com.codenotfound.batch;
import javax.sql.DataSource;import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer;import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;import org.springframework.context.annotation.Configuration;
@Configuration@EnableBatchProcessingpublic 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将自动注入 jobBuildersstepBuildersBeans。

HelloWorldStepBean中定义了我们的步骤执行的不同项。我们使用 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。这是最基本的实现,它假定对象已经是一个字符串。

package com.codenotfound.batch;
import org.springframework.batch.core.Job;import org.springframework.batch.core.Step;import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;import org.springframework.batch.item.file.FlatFileItemReader;import org.springframework.batch.item.file.FlatFileItemWriter;import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder;import org.springframework.batch.item.file.transform.PassThroughLineAggregator;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.ClassPathResource;import org.springframework.core.io.FileSystemResource;import com.codenotfound.model.Person;
@Configurationpublic 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();  }}

7. 处理数据

在大多数情况下,您将希望在批处理作业期间应用一些数据处理。可以使用ItemProcessor来操作。

在我们的示例中,我们将 Person对象转换为一个简单的问候语 String

为此,我们创建一个实现 ItemProcessor接口的 PersonItemProcessor。我们实现了 process()方法,它将人名和姓氏添加到字符串中。

调试的过程中,我们记录日志结果。

package com.codenotfound.batch;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.batch.item.ItemProcessor;import com.codenotfound.model.Person;
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;  }}

8.测试Spring Batch 示例

为了测试本的例子,我们创建了一个基本的单元测试用例。它将运行批处理作业并检查是否成功完成。

我们使用 @RunWith@SpringBootTest测试注解告诉 JUnit使用Spring的测试支持运行,并使用SpringBoot的支持引导。

SpringBatch附带一个 JobLauncherTestUtils实用程序类,用于测试批处理作业。

我们首先创建一个内部 BatchTestConfig类,将helloWorld作业添加到 JobLauncherTestUtils bean中。然后使用此bean的 launchJob()方法运行批处理作业。

如果执行的作业没有任何错误,则 ExitCode的值为 COMPLETED

package com.codenotfound;
import static org.assertj.core.api.Assertions.assertThat;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.batch.core.Job;import org.springframework.batch.core.JobExecution;import org.springframework.batch.core.launch.NoSuchJobException;import org.springframework.batch.test.JobLauncherTestUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;import org.springframework.test.context.junit4.SpringRunner;import com.codenotfound.batch.job.BatchConfig;import com.codenotfound.batch.job.HelloWorldJobConfig;
@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: default2019-05-30 19:11:13.305  WARN 14588 --- [           main] o.s.b.c.c.a.DefaultBatchConfigurer       : No datasource was provided...using a Map based JobRepository2019-05-30 19:11:13.306  WARN 14588 --- [           main] o.s.b.c.c.a.DefaultBatchConfigurer       : No transaction manager was provided, using a ResourcelessTransactionManager2019-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示例。

0
青年IT男

个人从事金融行业,就职过易极付、思建科技等重庆一流技术团队,目前就职于某网约车平台负责整个支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。

评论已关闭。

This site is protected by wp-copyrightpro.com