SpringBatch 使用

Spring batch是用来处理大量数据操作的一个框架,主要用来读取大量数据,然后进行一定处理后输出成指定的形式。

  Spring batch主要有以下部分组成:

  • JobRepository       用来注册job的容器
  • JobLauncher             用来启动Job的接口
  • Job                          实际执行的任务,包含一个或多个Step
  • Step                        step包含ItemReader、ItemProcessor和ItemWriter
  • ItemReader              用来读取数据的接口
  • ItemProcessor          用来处理数据的接口
  • ItemWriter               用来输出数据的接口

以上Spring Batch的主要组成部分只需要注册成Spring的Bean即可。若想开启批处理的支持还需在配置类上使用@EnableBatchProcessing,在Spring Batch中提供了大量的ItemReader和ItemWriter的实现,用来读取不同的数据来源,数据的处理和校验都要通过ItemProcessor接口实现来完成。

 

Spring Boot的支持

  Spring Boot对Spring Batch支持的源码位于org.springframework.boot.autoconfigure.batch下。

  Spring Boot为我们自动初始化了Spring Batch存储批处理记录的数据库。

  spring batch会自动加载hsqldb驱动,根据需求选择去留。

下面是一个spring boot支持spring batch 的例子:

  1. 实体类

复制代码

 1 public class Person {
 2     
 3     @Size(max=4,min=2) //使用JSR-303注解来校验注解
 4     private String name;
 5     
 6     private int age;
 7     
 8     private String nation;
 9     
10     private String address;
11 
12     public String getName() {
13         return name;
14     }
15 
16     public void setName(String name) {
17         this.name = name;
18     }
19 
20     public int getAge() {
21         return age;
22     }
23 
24     public void setAge(int age) {
25         this.age = age;
26     }
27 
28     public String getNation() {
29         return nation;
30     }
31 
32     public void setNation(String nation) {
33         this.nation = nation;
34     }
35 
36     public String getAddress() {
37         return address;
38     }
39 
40     public void setAddress(String address) {
41         this.address = address;
42     }
43 }

复制代码

  2. 校验器

复制代码

 1 public class CsvBeanValidator implements Validator,InitializingBean {
 2     private javax.validation.Validator validator; 
 3     @Override
 4     public void afterPropertiesSet() throws Exception { //使用JSR-303的Validator来校验我们的数据,在此处进行JSR-303的Validator的初始化
 5         ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
 6         validator = validatorFactory.usingContext().getValidator();
 7     }
 8 
 9     @Override
10     public void validate(T value) throws ValidationException {
11         Set> constraintViolations = validator.validate(value); //使用Validator的validate方法校验数据
12         if(constraintViolations.size()>0){
13             
14             StringBuilder message = new StringBuilder();
15             for (ConstraintViolation constraintViolation : constraintViolations) {
16                 message.append(constraintViolation.getMessage() + "\n");
17             }
18             throw new ValidationException(message.toString());
19 
20         }
21 
22     }
23 
24 }

复制代码

  3. ItemProcessor  

复制代码

 1 public class CsvItemProcessor  extends ValidatingItemProcessor{
 2 
 3     @Override
 4     public Person process(Person item) throws ValidationException {
 5         super.process(item); //需要执行super.process(item)才会调用自定义校验器
 6 
 7         if(item.getNation().equals("汉族")){ //对数据做简单的处理,若民族为汉族,则数据转换成01,其余转换成02
 8             item.setNation("01");
 9         }else{
10             item.setNation("02");
11         }
12         return item;
13     }
14 
15 
16 }

复制代码

  4. Job监听(监听器要实现JobExecutionListener接口,并重写其beforeJob、afterJob方法即可)

复制代码

 1 public class CsvJobListener implements JobExecutionListener{ 
 2 
 3     long startTime;
 4     long endTime;
 5     @Override
 6     public void beforeJob(JobExecution jobExecution) {
 7         startTime = System.currentTimeMillis();
 8         System.out.println("任务处理开始");
 9     }
10 
11     @Override
12     public void afterJob(JobExecution jobExecution) {
13         endTime = System.currentTimeMillis();
14         System.out.println("任务处理结束");
15         System.out.println("耗时:" + (endTime - startTime) + "ms");
16     }
17 
18 }

复制代码

  5. 配置

复制代码

 1 @Configuration    
 2 @EnableBatchProcessing
 3 public class CsvBatchConfig {
 4 
 5     @Bean
 6     public ItemReader reader() throws Exception {
 7         FlatFileItemReader reader = new FlatFileItemReader(); //使用FlatFileItemReader读取文件
 8         reader.setResource(new ClassPathResource("people.csv")); //使用FlatFileItemReader的setResource方法设置CSV文件的路径
 9             reader.setLineMapper(new DefaultLineMapper() {{ //在此处对CVS文件的数据和领域模型类做对应映射
10                 setLineTokenizer(new DelimitedLineTokenizer() {{
11                     setNames(new String[] { "name","age", "nation" ,"address"});
12                 }});
13                 setFieldSetMapper(new BeanWrapperFieldSetMapper() {{
14                     setTargetType(Person.class);
15                 }});
16             }});
17             return reader;
18     }
19     
20     @Bean
21     public ItemProcessor processor() {
22         CsvItemProcessor processor = new CsvItemProcessor(); //使用自定义的ItemProcessor的实现
23         processor.setValidator(csvBeanValidator()); //为Processor指定校验器
24         return processor;
25     }
26     
27     
28 
29     @Bean
30     public ItemWriter writer(DataSource dataSource) {//Spring能让容器中已有的Bean以参数的形式注入,Spring boot已经定义了DataSource
31         JdbcBatchItemWriter writer = new JdbcBatchItemWriter(); //使用JDBC批处理的JdbcBatchItemWriter来写数据到数据库
32         writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider());
33         String sql = "insert into person " + "(id,name,age,nation,address) "
34                 + "values(hibernate_sequence.nextval, :name, :age, :nation,:address)";
35         writer.setSql(sql); //在此设置要执行批处理的sql语句
36         writer.setDataSource(dataSource);
37         return writer;
38     }
39 
40     @Bean
41     public JobRepository jobRepository(DataSource dataSource, PlatformTransactionManager transactionManager)
42             throws Exception {
43         JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
44         jobRepositoryFactoryBean.setDataSource(dataSource);
45         jobRepositoryFactoryBean.setTransactionManager(transactionManager);
46         jobRepositoryFactoryBean.setDatabaseType("oracle");
47         return jobRepositoryFactoryBean.getObject();
48     }
49 
50     @Bean
51     public SimpleJobLauncher jobLauncher(DataSource dataSource, PlatformTransactionManager transactionManager)
52             throws Exception {
53         SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
54         jobLauncher.setJobRepository(jobRepository(dataSource, transactionManager));
55         return jobLauncher;
56     }
57 
58     @Bean
59     public Job importJob(JobBuilderFactory jobs, Step s1) {
60         return jobs.get("importJob")
61                 .incrementer(new RunIdIncrementer())
62                 .flow(s1) //指定step
63                 .end()
64                 .listener(csvJobListener()) //绑定监听器
65                 .build();
66     }
67 
68     @Bean
69     public Step step1(StepBuilderFactory stepBuilderFactory, ItemReader reader, ItemWriter writer,
70             ItemProcessor processor) {
71         return stepBuilderFactory
72                 .get("step1")
73                 .chunk(65000) //批处理每次提交65000条数据
74                 .reader(reader) //给step绑定reader
75                 .processor(processor) //给step绑定Processor
76                 .writer(writer) //给step绑定writer
77                 .build();
78     }
79 
80 
81 
82     @Bean
83     public CsvJobListener csvJobListener() {
84         return new CsvJobListener();
85     }
86 
87     @Bean
88     public Validator csvBeanValidator() {
89         return new CsvBeanValidator();
90     }
91     
92 
93 }

复制代码

  6.application.xml

复制代码

1 spring.datasource.driverClassName=oracle.jdbc.OracleDriver
2 spring.datasource.url=jdbc\:oracle\:thin\:@localhost\:1521\:xe
3 spring.datasource.username=boot
4 spring.datasource.password=boot
5 
6 spring.batch.job.enabled=true
7 
8 logging.level.org.springframework.web = DEBUG

复制代码

 

上面的例子是自动触发批处理的,当我们需要手动触发批处理时,需要将CsvBatchConfig类的@Configuration注解注释掉,让此配置类不再起效,新建TriggerBatchConfig配置类,内容与CsvBatchConfig完全一致,除了修改定义ItemReader这个Bean;另外,还需要修改application.xml配置文件spring.batch.job.enable=false

复制代码

 1 @Configuration
 2 @EnableBatchProcessing
 3 public class TriggerBatchConfig {
 4 
 5     @Bean
 6     @StepScope
 7     public FlatFileItemReader reader(@Value("#{jobParameters['input.file.name']}") String pathToFile) throws Exception {
 8         FlatFileItemReader reader = new FlatFileItemReader(); //
 9          reader.setResource(new ClassPathResource(pathToFile)); //
10             reader.setLineMapper(new DefaultLineMapper() {{ //
11                 setLineTokenizer(new DelimitedLineTokenizer() {{
12                     setNames(new String[] { "name","age", "nation" ,"address"});
13                 }});
14                 setFieldSetMapper(new BeanWrapperFieldSetMapper() {{
15                     setTargetType(Person.class);
16                 }});
17             }});
18            
19             return reader;
20     }
21     
22     @Bean
23     public ItemProcessor processor() {
24         CsvItemProcessor processor = new CsvItemProcessor(); 
25         processor.setValidator(csvBeanValidator()); 
26         return processor;
27     }
28     
29     
30 
31     @Bean
32     public ItemWriter writer(DataSource dataSource) {
33         JdbcBatchItemWriter writer = new JdbcBatchItemWriter(); 
34         writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider());
35         String sql = "insert into person " + "(id,name,age,nation,address) "
36                 + "values(hibernate_sequence.nextval, :name, :age, :nation,:address)";
37         writer.setSql(sql); //3
38         writer.setDataSource(dataSource);
39         return writer;
40     }
41 
42     @Bean
43     public JobRepository jobRepository(DataSource dataSource, PlatformTransactionManager transactionManager)
44             throws Exception {
45         JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
46         jobRepositoryFactoryBean.setDataSource(dataSource);
47         jobRepositoryFactoryBean.setTransactionManager(transactionManager);
48         jobRepositoryFactoryBean.setDatabaseType("oracle");
49         return jobRepositoryFactoryBean.getObject();
50     }
51 
52     @Bean
53     public SimpleJobLauncher jobLauncher(DataSource dataSource, PlatformTransactionManager transactionManager)
54             throws Exception {
55         SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
56         jobLauncher.setJobRepository(jobRepository(dataSource, transactionManager));
57         return jobLauncher;
58     }
59 
60     @Bean
61     public Job importJob(JobBuilderFactory jobs, Step s1) {
62         return jobs.get("importJob")
63                 .incrementer(new RunIdIncrementer())
64                 .flow(s1) 
65                 .end()
66                 .listener(csvJobListener()) 
67                 .build();
68     }
69 
70     @Bean
71     public Step step1(StepBuilderFactory stepBuilderFactory, ItemReader reader, ItemWriter writer,
72             ItemProcessor processor) {
73         return stepBuilderFactory
74                 .get("step1")
75                 .chunk(65000) 
76                 .reader(reader) 
77                 .processor(processor) 
78                 .writer(writer) 
79                 .build();
80     }
81 
82 
83 
84     @Bean
85     public CsvJobListener csvJobListener() {
86         return new CsvJobListener();
87     }
88 
89     @Bean
90     public Validator csvBeanValidator() {
91         return new CsvBeanValidator();
92     }
93     
94 
95 }

复制代码

  控制层代码

复制代码

 1 @RestController
 2 public class DemoController {
 3     
 4         @Autowired
 5         JobLauncher jobLauncher;
 6 
 7         @Autowired
 8         Job importJob;
 9         public JobParameters   jobParameters;
10         
11         @RequestMapping("/read")
12         public String imp(String fileName) throws Exception{
13             
14             String path = fileName+".csv";
15             jobParameters = new JobParametersBuilder()
16                     .addLong("time", System.currentTimeMillis())
17                     .addString("input.file.name", path)
18                     .toJobParameters();
19             jobLauncher.run(importJob,jobParameters);
20             return "ok";
21         }
22 
23 }

 

 

大家应该想到些什么了吧。SpringBatch像是一个天然的Job,Quartz是完全可以做为它运作的调度器。两者结合,效果很不错。

 

本文将从0到1讲解一个Spring Batch是如何搭建并运行起来的。
本教程将讲解从一个文本文件读取数据,然后写入MySQL。

什么是 Spring Batch

Spring Batch 作为 Spring 的子项目,是一款基于 Spring 的企业批处理框架。通过它可以构建出健壮的企业批处理应用。Spring Batch 不仅提供了统一的读写接口、丰富的任务处理方式、灵活的事务管理及并发处理,同时还支持日志、监控、任务重启与跳过等特性,大大简化了批处理应用开发,将开发人员从复杂的任务配置管理过程中解放出来,使他们可以更多地去关注核心的业务处理过程。

更多的介绍可以参考官网:https://spring.io/projects/sp...

环境搭建

我是用的Intellij Idea,用gradle构建。

可以使用Spring Initializr 来创建Spring boot应用。地址:https://start.spring.io/

首先选择Gradle Project,然后选择Java。填上你的Group和Artifact名字。

最后再搜索你需要用的包,比如Batch是一定要的。另外,由于我写的Batch项目是使用JPA向MySQL插入数据,所以也添加了JPA和MySQL。其他可以根据自己需要添加。

点击Generate Project,一个项目就创建好了。

Build.gralde文件大概就长这个样子:

buildscript {
   ext {
      springBootVersion = '2.0.4.RELEASE'
   }
   repositories {
      mavenCentral()
   }
   dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
   }
}

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.demo'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
   mavenCentral()
}

dependencies {
   compile('org.springframework.boot:spring-boot-starter-batch')
   compile('org.springframework.boot:spring-boot-starter-jdbc')
   compile("org.springframework.boot:spring-boot-starter-data-jpa")
   compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-joda', version: '2.9.4'
   compile group: 'org.jadira.usertype', name: 'usertype.core', version: '6.0.1.GA'
   compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.6',
   testCompile('org.springframework.boot:spring-boot-starter-test')
   testCompile('org.springframework.batch:spring-batch-test')
}

Spring Batch 结构

网上有很多Spring Batch结构和原理的讲解,我就不详细阐述了,我这里只讲一下Spring Batch的一个基本层级结构。

首先,Spring Batch运行的基本单位是一个Job,一个Job就做一件批处理的事情。
一个Job包含很多Step,step就是每个job要执行的单个步骤。

如下图所示,Step里面,会有Tasklet,Tasklet是一个任务单元,它是属于可以重复利用的东西。
然后是Chunk,chunk就是数据块,你需要定义多大的数据量是一个chunk。

Chunk里面就是不断循环的一个流程,读数据,处理数据,然后写数据。Spring Batch会不断的循环这个流程,直到批处理数据完成。

构建Spring Batch

首先,我们需要一个全局的Configuration来配置所有的Job和一些全局配置。

代码如下:

@Configuration
@EnableAutoConfiguration
@EnableBatchProcessing(modular = true)
public class SpringBatchConfiguration {
    @Bean
    public ApplicationContextFactory firstJobContext() {
        return new GenericApplicationContextFactory(FirstJobConfiguration.class);
    }
    
    @Bean
    public ApplicationContextFactory secondJobContext() {
        return new GenericApplicationContextFactory(SecondJobConfiguration.class);
    }

}

@EnableBatchProcessing是打开Batch。如果要实现多Job的情况,需要把EnableBatchProcessing注解的modular设置为true,让每个Job使用自己的ApplicationConext。

比如上面代码的就创建了两个Job。

例子背景

本博客的例子是迁移数据,数据源是一个文本文件,数据量是上百万条,一行就是一条数据。然后我们通过Spring Batch帮我们把文本文件的数据全部迁移到MySQL数据库对应的表里面。

假设我们迁移的数据是Message,那么我们就需要提前创建一个叫Message的和数据库映射的数据类。

@Entity
@Table(name = "message")
public class Message {
    @Id
    @Column(name = "object_id", nullable = false)
    private String objectId;

    @Column(name = "content")
    private String content;

    @Column(name = "last_modified_time")
    private LocalDateTime lastModifiedTime;

    @Column(name = "created_time")
    private LocalDateTime createdTime;
}

构建Job

首先我们需要一个关于这个Job的Configuration,它将在SpringBatchConfigration里面被加载。

@Configuration
@EnableAutoConfiguration
@EnableBatchProcessing(modular = true)
public class SpringBatchConfiguration {
    @Bean
    public ApplicationContextFactory messageMigrationJobContext() {
        return new GenericApplicationContextFactory(MessageMigrationJobConfiguration.class);
    }
}

下面的关于构建Job的代码都将写在这个MessageMigrationJobConfiguration里面。

public class MessageMigrationJobConfiguration {
}

我们先定义一个Job的Bean。

@Autowired
private JobBuilderFactory jobBuilderFactory;

@Bean
public Job messageMigrationJob(@Qualifier("messageMigrationStep") Step messageMigrationStep) {
    return jobBuilderFactory.get("messageMigrationJob")
            .start(messageMigrationStep)
            .build();
}

jobBuilderFactory是注入进来的,get里面的就是job的名字。
这个job只有一个step。

Step

接下来就是创建Step。

@Autowired
private StepBuilderFactory stepBuilderFactory;

@Bean
public Step messageMigrationStep(@Qualifier("jsonMessageReader") FlatFileItemReader jsonMessageReader,
                                 @Qualifier("messageItemWriter") JpaItemWriter messageItemWriter,
                                 @Qualifier("errorWriter") Writer errorWriter) {
    return stepBuilderFactory.get("messageMigrationStep")
            .chunk(CHUNK_SIZE)
            .reader(jsonMessageReader).faultTolerant().skip(JsonParseException.class).skipLimit(SKIP_LIMIT)
            .listener(new MessageItemReadListener(errorWriter))
            .writer(messageItemWriter).faultTolerant().skip(Exception.class).skipLimit(SKIP_LIMIT)
            .listener(new MessageWriteListener())
            .build();
}

stepBuilderFactory是注入进来的,然后get里面是Step的名字。
我们的Step中可以构建很多东西,比如reader,processer,writer,listener等等。

下面我们就逐个来看看step里面的这些东西是如何使用的。

Chunk

Spring batch在配置Step时采用的是基于Chunk的机制,即每次读取一条数据,再处理一条数据,累积到一定数量后再一次性交给writer进行写入操作。这样可以最大化的优化写入效率,整个事务也是基于Chunk来进行。

比如我们定义chunk size是50,那就意味着,spring batch处理了50条数据后,再统一向数据库写入。
这里有个很重要的点,chunk前面需要定义数据输入类型和输出类型,由于我们输入是Message,输出也是Message,所以两个都直接写Message了。
如果不定义这个类型,会报错。

.chunk(CHUNK_SIZE)

Reader

Reader顾名思义就是从数据源读取数据。
Spring Batch给我们提供了很多好用实用的reader,基本能满足我们所有需求。比如FlatFileItemReader,JdbcCursorItemReader,JpaPagingItemReader等。也可以自己实现Reader。

本例子里面,数据源是文本文件,所以我们就使用FlatFileItemReader。FlatFileItemReader是从文件里面一行一行的读取数据。
首先需要设置文件路径,也就是设置resource。
因为我们需要把一行文本映射为Message类,所以我们需要自己设置并实现LineMapper。

@Bean
public FlatFileItemReader jsonMessageReader() {
    FlatFileItemReader reader = new FlatFileItemReader<>();
    reader.setResource(new FileSystemResource(new File(MESSAGE_FILE)));
    reader.setLineMapper(new MessageLineMapper());
    return reader;
}

Line Mapper

LineMapper的输入就是获取一行文本,和行号,然后转换成Message。
在本例子里面,一行文本就是一个json对象,所以我们使用JsonParser来转换成Message。

public class MessageLineMapper implements LineMapper {
    private MappingJsonFactory factory = new MappingJsonFactory();

    @Override
    public Message mapLine(String line, int lineNumber) throws Exception {   
        JsonParser parser = factory.createParser(line);
        Map map = (Map) parser.readValueAs(Map.class);
        Message message = new Message();
        ... // 转换逻辑
        return message;
    }
}

Processor

由于本例子里面,数据是一行文本,通过reader变成Message的类,然后writer直接把Message写入MySQL。所以我们的例子里面就不需要Processor,关于如何写Processor其实和reader/writer是一样的道理。
从它的接口可以看出,需要定义输入和输出的类型,把输入I通过某些逻辑处理之后,返回输出O。

public interface ItemProcessor {
    O process(I item) throws Exception;
}

Writer

Writer顾名思义就是把数据写入到目标数据源里面。
Spring Batch同样给我们提供很多好用实用的writer。比如JpaItemWriter,FlatFileItemWriter,HibernateItemWriter,JdbcBatchItemWriter等。同样也可以自定义。

本例子里面,使用的是JpaItemWriter,可以直接把Message对象写到数据库里面。但是需要设置一个EntityManagerFactory,可以注入进来。

@Autowired
private EntityManagerFactory entityManager;

@Bean
public JpaItemWriter messageItemWriter() {
    JpaItemWriter writer = new JpaItemWriter<>();
    writer.setEntityManagerFactory(entityManager);
    return writer;
}

另外,你需要配置数据库的连接等东西。由于我使用的spring,所以直接在Application.properties里面配置如下:

spring.datasource.url=jdbc:mysql://database
spring.datasource.username=username
spring.datasource.password=password
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.show-sql=true
spring.jpa.properties.jadira.usertype.autoRegisterUserTypes=true
spring.jackson.serialization.write-dates-as-timestamps=false
spring.batch.initialize-schema=ALWAYS
spring.jpa.hibernate.ddl-auto=update

spring.datasource相关的设置都是在配置数据库的连接。
spring.batch.initialize-schema=always表示让spring batch在数据库里面创建默认的数据表。
spring.jpa.show-sql=true表示在控制台输出hibernate读写数据库时候的SQL。
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect是在指定MySQL的方言。

Listener

Spring Batch同样实现了非常完善全面的listener,listener很好理解,就是用来监听每个步骤的结果。比如可以有监听step的,有监听job的,有监听reader的,有监听writer的。没有你找不到的listener,只有你想不到的listener。

在本例子里面,我只关心,read的时候有没有出错,和write的时候有没有出错,所以,我只实现了ReadListener和WriteListener。
在read出错的时候,把错误结果写入一个单独的error列表文件中。

public class MessageItemReadListener implements ItemReadListener {
    private Writer errorWriter;

    public MessageItemReadListener(Writer errorWriter) {
        this.errorWriter = errorWriter;
    }

    @Override
    public void beforeRead() {
    }

    @Override
    public void afterRead(Message item) {
    }

    @Override
    public void onReadError(Exception ex) {
         errorWriter.write(format("%s%n", ex.getMessage()));
    }
}

在write出错的时候,也做同样的事情,把出错的原因写入单独的日志中。

public class MessageWriteListener implements ItemWriteListener {

    @Autowired
    private Writer errorWriter;

    @Override
    public void beforeWrite(List items) {
    }

    @Override
    public void afterWrite(List items) {
    }

    @Override
    public void onWriteError(Exception exception, List items) {
        errorWriter.write(format("%s%n", exception.getMessage()));
        for (Message message : items) {
            errorWriter.write(format("Failed writing message id: %s", message.getObjectId()));
        }
    }
}

前面有说chuck机制,所以write的listener传入参数是一个List,因为它是累积到一定的数量才一起写入。

Skip

Spring Batch提供了skip的机制,也就是说,如果出错了,可以跳过。如果你不设置skip,那么一条数据出错了,整个job都会挂掉。
设置skip的时候一定要设置什么Exception才需要跳过,并且跳过多少条数据。如果失败的数据超过你设置的skip limit,那么job就会失败。
你可以分别给reader和writer等设置skip机制。

writer(messageItemWriter).faultTolerant().skip(Exception.class).skipLimit(SKIP_LIMIT)

Retry

这个和Skip是一样的原理,就是失败之后可以重试,你同样需要设置重试的次数。
同样可以分别给reader,writer等设置retry机制。

如果同时设置了retry和skip,会先重试所有次数,然后再开始skip。比如retry是10次,skip是20,会先重试10次之后,再开始算第一次skip。

运行Job

所有东西都准备好以后,就是如何运行了。
运行就是在main方法里面用JobLauncher去运行你制定的job。

下面是我写的main方法,main方法的第一个参数是job的名字,这样我们就可以通过不同的job名字跑不同的job了。

首先我们通过运行起来的Spring application得到jobRegistry,然后通过job的名字找到对应的job。

接着,我们就可以用jobLauncher去运行这个job了,运行的时候会传一些参数,比如你job里面需要的文件路径或者文件日期等,就可以通过这个jobParameters传进去。如果没有参数,可以默认传当前时间进去。

public static void main(String[] args) {
    String jobName = args[0];

    try {
        ConfigurableApplicationContext context = SpringApplication.run(ZuociBatchApplication.class, args);
        JobRegistry jobRegistry = context.getBean(JobRegistry.class);
        Job job = jobRegistry.getJob(jobName);
        JobLauncher jobLauncher = context.getBean(JobLauncher.class);
        JobExecution jobExecution = jobLauncher.run(job, createJobParams());
        if (!jobExecution.getExitStatus().equals(ExitStatus.COMPLETED)) {
            throw new RuntimeException(format("%s Job execution failed.", jobName));
        }
    } catch (Exception e) {
        throw new RuntimeException(format("%s Job execution failed.", jobName));
    }
}

private static JobParameters createJobParams() {
    return new JobParametersBuilder().addDate("date", new Date()).toJobParameters();
}

最后,把jar包编译出来,在命令行执行下面的命令,就可以运行你的Spring Batch了。

java -jar YOUR_BATCH_NAME.jar YOUR_JOB_NAME

调试

调试主要依靠控制台输出的log,可以在application.properties里面设置log输出的级别,比如你希望输出INFO信息还是DEBUG信息。
基本上,通过查看log都能定位到问题。

logging.path=build/logs
logging.file=${logging.path}/batch.log
logging.level.com.easystudio=INFO
logging.level.root=INFO
log4j.logger.org.springframework.jdbc=INFO
log4j.logger.org.springframework.batch=INFO
logging.level.org.hibernate.SQL=INFO

Spring Batch数据表

如果你的batch最终会写入数据库,那么Spring Batch会默认在你的数据库里面创建一些batch相关的表,来记录所有job/step运行的状态和结果。

大部分表你都不需要关心,你只需要关心几张表。

batch_job_instance:这张表能看到每次运行的job名字。

batch_job_execution:这张表能看到每次运行job的开始时间,结束时间,状态,以及失败后的错误消息是什么。

batch_step_execution:这张表你能看到更多关于step的详细信息。比如step的开始时间,结束时间,提交次数,读写次数,状态,以及失败后的错误信息等。

总结

Spring Batch为我们提供了非常实用的功能,对批处理场景进行了完善的抽象,它不仅能实现小数据的迁移,也能应对大企业的大数据实践应用。它让我们开发批处理应用可以事半功倍。

 

在配置文件中 启动自动执行批处理

spring.batch.job.names = job1,job2 #启动时要执行的Job,默认执行全部Job

spring.batch.job.enabled=true #是否自动执行定义的Job,默认是

spring.batch.initializer.enabled=true #是否初始化Spring Batch的数据库,默认为是

spring.batch.schema=

spring.batch.table-prefix= #设置SpringBatch的数据库表的前缀

项目汇总

从 项目中我们可以看到 总的步骤就是 首先读取我们需要实现的文件进行解析,然后转换成需要的实体类并且绑定到reader中,二 实现我们需要的writer 并且帮到到数据库上,三实现job监听器将其绑定到步骤中 。最后开启批处理 自动执行入库即可 。这个简单步骤主要是配置中用到的 理解流程 自己也可以方便实现 批处理的流程。

你可能感兴趣的:(java)