Spring Batch学习之路

文章目录

  • 导读
  • Spring Batch简介
    • 业务方案
    • 技术目标:
    • Spring Batch的结构:
  • 框架流程简单介绍
    • Spring Batch流程介绍:
    • Step执行过程:
  • Sample(Hello World)
    • 说明:
    • 工程结构
      • application.xml文件配置如下:
      • spring-batch-hello.xml文件配置如下:
      • writeTasklet类的代码如下:
      • 启动类HelloJobLaunch类的代码如下:
  • Sample(CSV文件操作)
    • 工程结构
    • spring-batch-csv.xml 配置如下:
      • csvItemReader
      • CsvItemProcessor
      • csvItemWriter
      • StudentCsv
      • 注意事项
  • Sample(游标方式读写DB数据表)
    • 工程结构
    • spring-batch-jdbc.xml 配置如下:
      • 读操作
      • 写操作

导读

  最近一个项目使用了Spring Batch框架做了一个对账文件的批处理。当时网上资料很少,官网的文档也是只讲了一些入门的基础知识,在实际开发过程中遇到了很多的问题。因此,特将自己学习、应用Spring Batch的过程总结成一个个小实例写成随笔。

Spring Batch简介

  Spring Batch是一个轻量级的,完全面向Sprin的批处理框架,可以应用于企业级大量的数据处理系统。Spring Batch以POJO和大家熟知的Spring框架为基础,使开发者更容易的访问和利用企业级服务。Spring Batch可以提供大量的,可重复的数据处理功能,包括日志记录/跟踪,事务管理,作业处理统计工作重新启动、跳过,和资源管理等重要功能。

业务方案

  1. 批处理定期提交。
  2. 并行批处理:并行处理工作。
  3. 企业消息驱动处理
  4. 大规模的并行处理
  5. 手动或是有计划的重启
  6. 局部处理:跳过记录(如:回滚)

技术目标:

  1. 利用Spring编程模型:使程序员专注于业务处理,让Spring框架管理流程。
  2. 明确分离批处理的执行环境和应用。
  3. 提供核心的,共通的接口。
  4. 提供开箱即用(out of the box)的简单的默认的核心执行接口。
  5. 提供Spring框架中配置、自定义、和扩展服务。
  6. 所有存在的核心服务可以很容的被替换和扩展,不影响基础层。
  7. 提供一个简单的部署模式,利用Maven构建独立的Jar文件。

Spring Batch的结构:

  这种分层结构有三个重要的组成部分:应用层、核心层、基础架构层。应用层包含所有的批处理作业,通过Spring框架管理程序员自定义的代码。核心层包含了Batch启动和控制所需要的核心类,如:JobLauncher、Job和step等。应用层和核心层建立在基础构架层之上,基础构架层提供共通的读(ItemReader)、写(ItemWriter)、和服务(如RetryTemplate:重试模块。可以被应用层和核心层使用)。

框架流程简单介绍

Spring Batch流程介绍:

上图描绘了Spring Batch的执行过程。说明如下:

  每个Batch都会包含一个Job。Job就像一个容器,这个容器里装了若干Step,Batch中实际干活的也就是这些Step,至于Step干什么活,无外乎读取数据,处理数据,然后将这些数据存储起来(ItemReader用来读取数据,ItemProcessor用来处理数据,ItemWriter用来写数据) 。JobLauncher用来启动Job,JobRepository是上述处理提供的一种持久化机制,它为JobLauncher,Job,和Step实例提供CRUD操作。

  外部控制器调用JobLauncher启动一个Job,Job调用自己的Step去实现对数据的操作,Step处理完成后,再将处理结果一步步返回给上一层,这就是Batch处理实现的一个简单流程。

Step执行过程:

  从DB或是文件中取出数据的时候,read()操作每次只读取一条记录,之后将读取的这条数据传递给processor(item)处理,框架将重复做这两步操作,直到读取记录的件数达到batch配置信息中”commin-interval”设定值的时候,就会调用一次write操作。然后再重复上图的处理,直到处理完所有的数据。当这个Step的工作完成以后,或是跳到其他Step,或是结束处理。

  这就是一个SpringBatch的基本工作流程。

  下次,将通过“Hello World”实例,与大家共同探讨SpringBatch的具体应用和实现。

Sample(Hello World)

  通过上面对Spring Batch的介绍,大家应该已经对Spring Batch有个初步的概念了。现在将通过一个”Hello World!”实例,和大家一起探讨关于Spring Batch的一些基本配置和实现。使大家从开发的角度对Spring Batch有一个真切的体会。

说明:

  1. 本实例使用的是spring-batch 3.0.7.RELEASE

  2. 本实例没有像前面讲的那样配置ItemReader、ItemProcessor和ItemWriter,而是之间在Step中调用Tasklet,由Tasklet完成”Hello World!”的输出。

工程结构

  HelloJobLaunch.java类用来启动Bath,WriteTasklet.java用来完成输出工作。application.xml用来配置一些Spring信息,spring-batch-hello.xml配置Job信息。

application.xml文件配置如下:


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd"
       default-autowire="byName">

    <bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
        <property name="jobRepository" ref="jobRepository"/>
    bean>

    <bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"/>

    <bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager"/>

    <context:annotation-config/>
    <context:component-scan base-package="com.cheng.springbatch"/>

    <import resource="classpath:spring-datasource.xml"/>

    <import resource="classpath:spring-batch-hello.xml"/>
    
    
    
    
    

beans>

jobLauncher负责batch的启动工作,jobRepository负责job的整个运行过程中的CRUD操作,transactionManager负责事务的管理操作。

spring-batch-hello.xml文件配置如下:


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:batch="http://www.springframework.org/schema/batch"
       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">

    
    <batch:job id="helloWorldJob">
        <batch:step id="step_hello" next="step_world">
            <batch:tasklet ref="hello" transaction-manager="transactionManager"/>
        batch:step>
        <batch:step id="step_world">
            <batch:tasklet ref="world" transaction-manager="transactionManager"/>
        batch:step>
    batch:job>

    <bean id="hello" class="com.cheng.springbatch.hello.WriteTasklet">
        <property name="message" value="Hello "/>
    bean>
    <bean id="world" class="com.cheng.springbatch.hello.WriteTasklet">
        <property name="message" value="World!"/>
    bean>

beans>

配置了一个ID为helloWorldJob的job,此job有两个Step : step_hello和step_world,前者负责输出“Hello ”,后者负责输出“World!”,当第一个Step完成以后,执行第二个Step。

writeTasklet类的代码如下:

package com.cheng.springbatch.hello;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;

/**
 * @author chengchenrui
 * @version Id: writeTasklet.java, v 0.1 2017.2.27 14:22 chengchenrui Exp $$
 */
public class WriteTasklet implements Tasklet {

    private String message;

    public void setMessage(String message) {
        this.message = message;
    }

    public RepeatStatus execute(StepContribution stepContribution,
                                ChunkContext chunkContext) throws Exception {
        System.out.println(message);
        return RepeatStatus.FINISHED;
    }
}

此类中定义了一个message属性,通过batch.xml的“hello”和“world” Bean为其注入值。 execute方法,是由Tasklet接口继承而来的,是Tasklet实现业务逻辑的地方。此实例中只是简单的输出Message信息后,直接返回。

启动类HelloJobLaunch类的代码如下:

package com.cheng.springbatch.web.hello;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author chengchenrui
 * @version Id: CsvJobLaunch.java, v 0.1 2017.2.27 14:28 chengchenrui Exp $$
 */
public class HelloJobLaunch {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        JobLauncher launcher = (JobLauncher) context.getBean("jobLauncher");
        Job job = (Job) context.getBean("helloWorldJob");

        try {
            //运行Job
            JobExecution result = launcher.run(job, new JobParameters());
            System.out.println(result.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

本例通过Spring配置的方式取得HelloJobLaunch和Job对象,然后由HelloJobLaunch的run方法启动job,参数JobParameters是标志job的一些参数,处理结束后,控制台输出处理结果。

  上面就是通过SpringBatch运行一个"Hello World”程序所需要的基本配置。由于其优势是处理大批量的数据,所以仅仅为了输出"Hello World"而编写这么多代码和配置文件,确实显得有些笨拙,也体现不出其优越性。

Sample(CSV文件操作)

  本章将通过一个完整的实例,与大家一起讨论运用Spring Batch对CSV文件的读写操作。此实例的流程是:读取一个含有四个字段的CSV文件(ID,Name,Age,Score),对读取的字段做简单的处理,然后输出到另外一个CSV文件中。

工程结构

CsvJobLaunch类用来启动Job, CsvItemProcessor类用来对Reader取得的数据进行处理, StudentCsv类是一个POJO类,用来存放映射的数据。 inputFile.csv是数据读取文件, outputFile.csv是数据输出文件。

spring-batch-csv.xml 配置如下:


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:batch="http://www.springframework.org/schema/batch"
       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">

    
    <batch:job id="csvJob">
        <batch:step id="csvStep">
            <batch:tasklet transaction-manager="transactionManager">
                <batch:chunk reader="csvItemReader"
                             writer="csvItemWriter"
                             processor="csvItemProcessor"
                             commit-interval="1"/>
            batch:tasklet>
        batch:step>
    batch:job>

    
    <bean id="csvItemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
        <property name="resource" value="file:E:\code\spring-batch-demo\spring-batch-web\src\inputFile.csv"/>
        <property name="lineMapper">
            <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
                <property name="lineTokenizer" ref="lineTokenizer"/>
                <property name="fieldSetMapper">
                    <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
                        <property name="prototypeBeanName" value="studentCsv"/>
                    bean>
                property>
            bean>
        property>
    bean>

    <bean id="studentCsv" class="com.cheng.springbatch.csv.StudentCsv" scope="prototype"/>

    <bean id="lineTokenizer" class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
        <property name="delimiter" value=","/>
        <property name="names">
            <list>
                <value>studentIdvalue>
                <value>namevalue>
                <value>agevalue>
                <value>scorevalue>
            list>
        property>
    bean>

    
    <bean id="csvItemWriter" class="org.springframework.batch.item.file.FlatFileItemWriter" scope="step">
        <property name="resource" value="file:E:\code\spring-batch-demo\spring-batch-web\src\outputFile.csv"/>
        <property name="lineAggregator">
            <bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
                <property name="delimiter" value=","/>
                <property name="fieldExtractor">
                    <bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
                        <property name="names" value="name,age,score"/>
                    bean>
                property>
            bean>
        property>
    bean>

beans>

  JOB:csvJob。本Job包含一个Step,完成一个完整的CSV文件读写功能。分别由 csvItemReader完成CSV文件的读操作,由 csvItemProcessor完成对取得数据的处理,由 csvItemWriter完成对CSV文件的写操作。

csvItemReader

  csvItemReader实现的是Spring Batch提供FlatFileItemReader类,此类主要用于Flat文件的读操作。它包含两个必要的属性 resource和 lineMapper。前者指定要读取的文件的位置,后者是将文件的每一行映射成一个Pojo对象。其中 lineMapper也有两个重要属性 lineTokenizer和 fieldSetMapper, lineTokenizer将文件的一行分解成一个 FieldSet,然后由 fieldSetMapper映射成Pojo对象。
  这种方式与DB的读操作非常类似。lineMapper类似于ResultSet,文件中的一行类似于Table中的一条记录,被封装成的FieldSet,类似于RowMapper。至于怎么将一条记录封装,这个工作由lineTokenizer的继承类DelimitedLineTokenizer完成。DelimitedLineTokenizer的delimiter属性决定文件的一行数据按照什么分解,默认的是“,”, names属性标示分解的每个字段的名字,传给fieldSetMapper(本实例用的是BeanWrapperFieldSetMapper)的时候,就可以按照这个名字取得相应的值。fieldSetMapper的属性prototypeBeanName,是映射Pojo类的名字。设置了此属性后,框架就会将lineTokenizer分解成的一个FieldSet映射成Pojo对象,映射是按照名字来完成的(lineTokenizer分解时标注的名字与Pojo对象中字段的名字对应)。
  总之,FlatFileItemReader读取一条记录由以下四步完成:1,从resource指定的文件中读取一条记录;2,lineTokenizer将这条记录按照delimiter分解成Fileset,每个字段的名字由names属性取得;3,将分解成的Fileset传递给fieldSetMapper,由其按照名字映射成Pojo对象;4,最终由FlatFileItemReader将映射成的Pojo对象返回,框架将返回的对象传递给Processor。

CsvItemProcessor

package com.cheng.springbatch.csv;

import org.springframework.batch.item.ItemProcessor;
import org.springframework.stereotype.Component;

/**
 * @author chengchenrui
 * @version Id: CsvItemProcessor.java, v 0.1 2017.2.27 15:43 chengchenrui Exp $$
 */
@Component("csvItemProcessor")
public class CsvItemProcessor implements ItemProcessor<StudentCsv, StudentCsv> {

    /**
     * 对数据进行转换
     * @param item
     * @return
     * @throws Exception
     */
    public StudentCsv process(StudentCsv item) throws Exception {

        item.setName(item.getStudentId() + "--" + item.getName());
        item.setAge(item.getAge() + 2);
        item.setScore(item.getScore() + 10);

        return item;
    }
}

csvItemWriter

  csvItemWriter实现的是FlatFileItemWriter类。此类与FlatFileItemReader类相似,也有两个重要的属性:resource和lineAggregator。前者是要输出的文件的路径,后者和lineTokenizer类似。lineAggregator(本实例用DelimitedLineAggregator类)也有两个重要的属性:delimiter和fieldExtractor。Delimiter标示输出的字段以什么分割,后者将Pojo对象组装成由Pojo对象的字段组成的一个字符串。同样FlatFileItemWriter写一条记录也有以下四步完成:

  1. Processor传递过来一个对象给lineAggregator;
  2. lineAggregator将其这个对象转化成一个数组;
  3. 再由lineAggregator的属性fieldExtractor将数组转化成按照delimiter分割一个字符串;
  4. 将这个字符串输出。

  这样,一条数据的读、处理、写操作就基本完成了。当然,读和写也可以自己写类来处理,只是要注意继承FlatFileItemReader和FlatFileItemWriter就可以了。

StudentCsv

package com.cheng.springbatch.csv;

import java.io.Serializable;

/**
 * @author chengchenrui
 * @version Id: StudentCsv.java, v 0.1 2017.2.27 15:24 chengchenrui Exp $$
 */
public class StudentCsv implements Serializable {

    /**
     * ID
     */
    private String studentId = "";
    /**
     * 名字
     */
    private String name      = "";
    /**
     * 年龄
     */
    private int    age       = 0;
    /**
     * 分数
     */
    private float  score     = 0;

    public String getStudentId() {
        return studentId;
    }

    public void setStudentId(String studentId) {
        this.studentId = studentId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public float getScore() {
        return score;
    }

    public void setScore(float score) {
        this.score = score;
    }
}

注意事项

  1. 注意Writer的resource要写成 “file:****** ”形式,不能用 “classpath:****** ” 形式。

  2. 如果将Job配置中commit-interval属性配置为大于1时,每次commit的都是最后一条记录,前面读取的被覆盖了。具体原因不明,如果将Reader的fieldSetMapper属性自己重写,就可以解决这个问题。(注:student bean添加scope属性可以解决此问题:scope:“prototype”)

Sample(游标方式读写DB数据表)

工程结构

spring-batch-jdbc.xml 配置如下:


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:batch="http://www.springframework.org/schema/batch"
       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">

    <batch:job id="jdbcJob">
        <batch:step id="jdbcStep">
            <batch:tasklet transaction-manager="transactionManager">
                <batch:chunk reader="jdbcItemReader"
                             writer="jdbcItemWriter"
                             processor="jdbcProcessor"
                             commit-interval="1"/>
            batch:tasklet>
        batch:step>
    batch:job>

    <bean id="jdbcItemReader" class="org.springframework.batch.item.database.JdbcCursorItemReader" scope="step">
        <property name="dataSource" ref="dataSource"/>
        <property name="sql" value="select ID,USER_ID,USERNAME,PASSWORD from T_USER where id < ?"/>
        <property name="rowMapper">
            <bean class="org.springframework.jdbc.core.BeanPropertyRowMapper">
                <property name="mappedClass" value="com.cheng.springbatch.jdbc.User"/>
            bean>
        property>
        <property name="preparedStatementSetter" ref="paramStatementSetter"/>
    bean>

    <bean id="paramStatementSetter" class="org.springframework.batch.core.resource.ListPreparedStatementSetter"
          scope="step">
        <property name="parameters">
            <list>
                <value>#{jobParameters['id']}value>
            list>
        property>
    bean>

    <bean id="jdbcItemWriter" class="org.springframework.batch.item.database.JdbcBatchItemWriter">
        <property name="dataSource" ref="dataSource"/>
        <property name="sql" value="insert into T_DEST_USER
        (ID,USER_ID,USERNAME,PASSWORD,UPDATE_TIME,UPDATE_USER)
        values
        (:id,:userId,:userName,:password,:updateTime,:updateUser)"/>
        <property name="itemSqlParameterSourceProvider">
            <bean class="org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider"/>
        property>
    bean>

beans>

读操作

  Spring Batch对DB基于游标的读取数据操作,是由其核心类JdbcCursorItemReader来实现的。一般来说,从DB数据表中读取数据一般有以下几个步骤。首先是DB连接,这些连接DB的基本信息(像DB服务器地址、用户名、密码等信息)由dataSource属性提供,SpringBatch没有提专门供特殊的类,用的是Spring框架的DataSource。连接上了DB,下面需要关注的就是对DB具体的查询操作了,也就是SQL文的相关信息了,由其sql属性实现,主要是拼接SQL文的一个字符串。有了sql文,有可能需要传递参数,sql文参数的一些信息,由preparedStatementSetter属性来满足,具体的实现可以由SpringBatch提供的核心类ListPreparedStatementSetter来设置。连接上了DB,执行了SQL文,最后要关心的就是查询结果的存放问题了,这一点由JdbcCursorItemReader的rowMapper属性来实现。这样就可以将DB数据表中的数据一条条映射成我们的Pojo对象了,也就完成了读操作。

写操作

  写DB和读DB思路是一样的,只不过一个是从DB里读,一个是往DB里写。是由SpringBatch框架的JdbcBatchItemWriter类实现的。也有以下几个步骤:首先是连接DB,也是由dataSource属性提供;其次是执行的SQL文,有sql属性满足,最后就是如何传递参数了。写操作传递参数的时候,跟读操作有一定区别。写操作提供两种传递参数的方式:一种是直接传递一个对象进去,itemSqlParameterSourceProvider属性设置为BeanPropertyItemSqlParameterSourceProvider的实现就可以了。SQL文中的参数也是使用【:对象属性名】的方式,并且区分大小写。第二种传递参数的方式是:设置JdbcBatchItemWriter类的itemSqlParameterSourceProvider属性(设置方式与读操作的paramStatementSetter属性的设置方式大同小异),当然,sql也和第一种方式有区别,使用如下形式:insert into T_DESTUSER (ID,USERID,USERNAME,PASSWORD,UPDATETIME,UPDATEUSER) values (?,?,?,?,?,?).两种方式各有优势,根据实际情况自行选择。

你可能感兴趣的:(软件开发)