Spring Batch 之 Hello World教程

Spring Batch 之 Hello World教程SpringBatch之HelloWorld教程本文我们基于springboot和springbatch实现一个简单helloworld入门批处理程序。如果你刚刚接触springbatch,这篇教程会让你花最短时间理解springbatch框架。SpringBatch框架介绍开始代码之前,我们先了解框架中的核心组件,见下图:批处理过程有Job组成,job是封装整…

大家好,又见面了,我是你们的朋友全栈君。

Spring Batch 之 Hello World教程

本文我们基于spring boot和spring batch 实现一个简单hello world入门批处理程序。如果你刚刚接触spring batch,这篇教程会让你花最短时间理解spring batch框架。

Spring Batch 框架介绍

开始代码之前,我们先了解框架中的核心组件,见下图:
在这里插入图片描述

  • 批处理过程有Job组成,job是封装整个批处理过程的实体。
  • Job有一个或多个Step组成。大多数情况下,step负责读数据(ItemReader),处理数据(ItemProcessor)以及写数据(ItemWriter)。
  • JobLauncher 负责启动job。
  • JobRepository负责存储关于配置和执行Job的元信息。

理解基本概念之后,我们通过一个简单Hello World程序来说明框架如何工作。示例从person.csv文件读取用户的firstName 和 lastName,然后给每个用户增加问候(hello),最后把结果写入 greetings.txt文件。

项目依赖

通过idea或Spring Initializr 新建spring boot 项目,增加相应依赖:

plugins {
    id 'org.springframework.boot' version '2.1.3.RELEASE'
    id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.dataz.batch'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-batch'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.batch:spring-batch-test'
}

spring-boot-starter-batch 引入Spring Boot 和 Spring Batch 依赖.

spring-boot-starter-test 引入Spring Boot 应用的测试依赖,包括JUnit, Hamcrest 和 Mockito.

spring-batch-test 引入测试Spring batch job和step的工具类.

Spring Boot 设置

我们使用了Spring Boot可以让Spring Batch应用立刻运行。
@SpringBootApplication注解包括@Configuration, @EnableAutoConfiguration, @ComponentScan 三个注解,消除或简化配置。

Spring Batch缺省使用数据库存储配置批处理job的元数据。为了简化,我们不使用数据库,而使用基于内存(Map)存储。

spring-boot-starter-batch 自动引入spring-boot-starter-jdbc,后者会尝试实例化datasource。在@SpringBootApplication注解上增加exclude = {DataSourceAutoConfiguration.class},避免Spring Boot 自动配置Datasource至数据库连接。

完整 HelloWorldApplication 代码如下:

package com.dataz.batch.helloworld;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class HelloWorldApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloWorldApplication.class, args);
    }
}

创建域对象模型

处理数据之前,一般需要把数据映射为域对象。我们的数据存储在 src/resources/csv/person.csv,每行包括内容如下:

John, Doe
Jane, Doe

为了映射数据至Person对象,下面定义Person类:

package com.dataz.batch.helloworld.dto;

import lombok.Data;

@Data
public class Person {
    private String firstName;
    private String lastName;
}

配置Spring Batch

定义BatchConfig类:

package com.dataz.batch.helloworld.configuration;

import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
@EnableBatchProcessing
public class BatchConfig extends DefaultBatchConfigurer {

    @Override
    public void setDataSource(DataSource dataSource) {
        // initialize will use a Map based JobRepository (instead of database)
    }
}

我们创建BatchConfig 类用于配置 Spring Batch。@Configuration注解表明作为bead定义配置类。
@EnableBatchProcessing注解启用必要的Spring Batch特性,也提供构建job的基本配置。该注解功能强大,背后为我们做了很多事情,其创建下列bean:

  • JobRepository (bean名为 “jobRepository”)
  • JobLauncher (bean名为 “jobLauncher”)
  • JobRegistry (bean名为 “jobRegistry”)
  • JobExplorer (bean名为 “jobExplorer”)
  • PlatformTransactionManager (bean名为 “transactionManager”)
  • JobBuilderFactory (bean名为 “jobBuilders”) 用于构建job对象,避免给每个job注入jobRepository
  • StepBuilderFactory (bean名为 “stepBuilders”) 用于构建job对象,避免给每个step注入jobRepository 和事物管理bean

因为Spring Batch使用Map存储 ,需要继承DefaultBatchConfigurer,并重写setDataSource方法,不设置数据源。让自动配置使用基于Map的JobRepository.

配置批处理Job

下面继续配置批处理Job,我们创建HelloWorldJobConfig配置类并增加@Configuration注解:

package com.dataz.batch.helloworld.configuration;

import com.dataz.batch.helloworld.dto.Person;
import com.dataz.batch.helloworld.item.PersonItemProcessor;
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;

@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/person.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();
    }
}

helloWorlJob bean 使用JobBuilderFactory 通过传递job和step的名称创建job,运行时通过名称进行查找。
需要注意的是jobBuilders 和 stepBuilders bean 会被自动注入。它们在上一节中通过@EnableBatchProcessing负责创建。

helloWorldStep bean 定义step中执行的不同item,这里使用StepBuilderFactory创建step。

首先传入step名称,然后使用chunk()方法指定每个事物处理数据项数量,同时也指明输入类型为person,输出为string。最后给step增加reader、process以及writer。

我们使用FlatFileItemReader读perons.csv文件,该类提供了基本的文件文件处理功能。通过FlatFileItemReaderBuilder创建FlatFileItemReader实现,首先传入reader名称,接着传入处理资源,最后是输出类型Person。FlatFileItemReader处理文件需要额外的信息,分隔符(默认为逗号)及字段映射信息(names方法指定),表明第一列映射到Person的firstName,第二列映射到lastName。

PersonItemProcessor负责处理数据,其转换每个Person对象为一个字符串。
数据处理完通过FlatFileItemWriter 组件写入一个文本文件。FlatFileItemWriterBuilder 创建FlatFileItemWriter组件,指定名称和对应处理资源(greeting.txt文件)。FlatFileItemWriter需要知道如何生成单个字符串,因为本例中输出以及是一个字符串,可以使用PassThroughLineAggregator,这是最基本的实现,其假设对象已经是一个字符串。

处理数据

大多数情况下,job执行过程中需要进行数据处理。我们示例中把Person转成字符串。PersonItemProcessor类实现ItemProcessor接口,在process方法中实现具体业务。

package com.dataz.batch.helloworld.item;

import com.dataz.batch.helloworld.dto.Person;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemProcessor;

public class PersonItemProcessor
    implements ItemProcessor<Person, String> {

    private static final Logger LOGGER =
        LoggerFactory.getLogger(PersonItemProcessor.class);

    @Override
    public String process(Person person) {
        String greeting = "Hello " + person.getFirstName() + " "
            + person.getLastName() + "!";

        LOGGER.info("converting '{}' into '{}'", person, greeting);
        return greeting;
    }
}

运行或测试Batch示例

我们可以直接运行HelloWorldApplication类。也可以通过单元测试运行,首先定义单元测试配置类:

package com.dataz.batch.helloworld.configuration;

import org.springframework.batch.core.Job;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({BatchConfig.class, HelloWorldJobConfig.class})
public class BatchTestConfig {

    @Autowired
    private Job helloWorlJob;

    @Bean
    JobLauncherTestUtils jobLauncherTestUtils() {
        JobLauncherTestUtils jobLauncherTestUtils =
            new JobLauncherTestUtils();
        jobLauncherTestUtils.setJob(helloWorlJob);

        return jobLauncherTestUtils;
    }
}

这里主要定义JobLauncherTestUtils bean,用于单元测试。

下面定义测试类:

package com.dataz.batch.helloworld;

import com.dataz.batch.helloworld.configuration.BatchTestConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {BatchTestConfig.class})
public class HelloWorldApplicationTests {

    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;

    @Test
    public void testHelloWorldJob() throws Exception {
        JobExecution jobExecution = jobLauncherTestUtils.launchJob();
        assertThat(jobExecution.getExitStatus().getExitCode()).isEqualTo("COMPLETED");
    }
}

注意这里使用@RunWith(SpringRunner.class)和@SpringBootTest(classes = {BatchTestConfig.class})注解,通知junit使用Spring test和spring Boot支持。运行结果如下:

2019-03-29 16:16:23.210  INFO 9004 --- [main] c.d.b.h.HelloWorldApplicationTests       : Started HelloWorldApplicationTests in 7.919 seconds (JVM running for 16.449)
2019-03-29 16:16:24.708  INFO 9004 --- [main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=helloWorldJob]] launched with the following parameters: [{random=360857}]
2019-03-29 16:16:24.724  INFO 9004 --- [main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [helloWorldStep]
2019-03-29 16:16:24.919  INFO 9004 --- [main] c.d.b.h.item.PersonItemProcessor         : converting 'Person(firstName=John, lastName=Doe)' into 'Hello John Doe!'
2019-03-29 16:16:24.921  INFO 9004 --- [main] c.d.b.h.item.PersonItemProcessor         : converting 'Person(firstName=Jane, lastName=Doe)' into 'Hello Jane Doe!'
2019-03-29 16:16:24.937  INFO 9004 --- [main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=helloWorldJob]] completed with the following parameters: [{random=360857}] and the following status: [COMPLETED]

最终你能看到结果在文件在target/test-outputs/greetings.txt,其结果如下:

Hello John Doe!
Hello Jane Doe!

总结

本文通过简单示例说明了Spring Batch的原理及关键概念及组件。包括job、step以及处理环节(ItemReader、ItemProcessor、ItemWriter),以及初始化Spring Batch运行环境和单元测试配置。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/140460.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Java程序员烂大街了吗?是,也不是

    Java程序员烂大街了吗?是,也不是Java程序员烂大街了吗?有网友吐槽精通的人少,半吊子的人烂大街。半吊子程序员确实不少,除了自身学习有关,还跟培训机构教学有关,工作后能不能继续学造有关,现在学编程的越来越多,所以企业要求相对也比前些年要高很多。  企业想招一个合格的Jjava程序员很不容易。烂大街的说法有网友不怎么赞同,因为市场自己会过,留下好的淘汰掉不合格的。不合格的程序员有一些会放弃,从事别的工作,也有的会坚持学习达到市场的要求。只不过还有很多人在选择进入这个行业而已,很正常的现象。  以前可能知道大概会怎么使用就能找份不

    2022年7月7日
    313
  • c++ priority queue_priority

    c++ priority queue_priority既然是队列那么先要包含头文件#include&lt;queue&gt;优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的定义:priority_queue&lt;Type,Container,Functional&gt;Type就是数据类型,Container就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用

    2022年10月21日
    0
  • Java重载和重写的区别「建议收藏」

    Java重载和重写的区别「建议收藏」1、方法的重载的概念在同一个类中,允许存在一个以上的同名方法,只要同名的参数个数或者参数类型不同即可。总结:”两同一不同”:同一个类、相同方法名参数列表不同:参数个数不同,参数类型不同2、例构成重载的举例:不能构成重载的举例:3、如何判断是否构成方法的重载?严格按照定义判断:两同一不同跟方法的权限修饰符、返回值类型、形参变量名、方法体都没关系!4、如何确定类中某一个方法的调用:方法名——>参数列表子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作重写以后,当创建子类对象以后,通过

    2022年9月9日
    0
  • DropDownList1.SelectedIndex及DropDownList1.SelectedValue的赋值[通俗易懂]

    DropDownList1.SelectedIndex及DropDownList1.SelectedValue的赋值[通俗易懂]困扰好长时间的一个问题,理解之后发现这么简单我定义了一个DropDownList绑定了一个数据表,里面没有静态数据,我想让它编辑的时候传值过来选定要编辑那项,上网找了好多方法,可怎么也不能实现,后来查了SelectedValue的定义。publicvirtualstringSelectedValue{     get     {           intnum1=this.SelectedIndex;           if(num1>=0)           {     

    2022年7月18日
    16
  • Oracle SEQUENCE 详细说明[通俗易懂]

    Oracle SEQUENCE 详细说明[通俗易懂]ORACLE SEQUENCE  ORACLE没有自增数据类型,如需生成业务无关的主键列或惟一约束列,可以用sequence序列实现。CREATESEQUENCE语句及参数介绍:创建序列:需要有CREATESEQUENCE或者CREATEANYSEQUENCE权限, CREATESEQUENCE[schema.]sequence  [{IN

    2022年10月18日
    0
  • 两种方法上传本地文件到github

    两种方法上传本地文件到github

    2021年10月15日
    32

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号