Spring Batch 入門

一、應用場景

在銀行、電信等一些大型的企業應用上,經常需要處理大批量的數據。比如,銀行的交易流水文件的處理等。這些大批量數據的處理有一些共同點,從文件或數據庫中讀取數據,進行加工處理,再寫入到文件或數據庫中。Spring Batch 正是完成這樣的功能。Spring Batch 的出現,讓我們可以專注業務編程,而不去關心批量如何執行

Spring Batch 的主要功能:

1、與quartz整合,實現定時批任務處理;當然,spring batch 內部也有集成調度框架,不過沒有quartz強大。

2、可以並行處理批任務;

3、可以按順序定義相關的處理步驟;

4、支持事務;

5、支持對批任務的失敗重試;

.......

 

二、基本架構

官網上,spring batch 的架構圖如下:

 

簡單點說,JobRepository 用於存放批處理的結果,不管成功或者 失敗,都會保存在JobRepository 中。JobRepository 可以是內存,也可以是數據庫

而JobLauncher則用於啓動一個批任務。

Job、step需要程序員自己定義,一個job可以有多個step,而一個step下,又分別包含一個ItemReader、ItemProcessor、ItemWriter,這三個接口用於讀取文件(或數據庫)、處理讀取的內容(對數據進行加工)、處理後的寫入操作(可以簡單打印,也可以進行持久化)。

有些簡單的批處理任務,可以不要中間的ItemProcessor,這表示讀取到的文件可以直接給ItemWriter進行輸出或持久化。

下面用一個簡單的小程序展示以上這幾個類及接口的使用。

 

三、入門程序

以下展示一個Spring Batch 的簡易程序。主要的業務場景是:從文件中讀取出學生的信息,根據學生的成績生成學生的成績等級,然後打印輸出。

項目的目錄結構如下:

 

 

 

 

主要的java類介紹:

其中Bootstrap 是啓動類,Student是學生pojo類,StudentProcessor是處理學生信息的處理器類,StudentWriter 是輸出學習信息的類。

下面是源代碼及解析。

1、首先,配置一個批處理的上下文job-context.xml,配置一個jobRepository,以及jobLauncher。transactionManager 是一個事務管理器。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd">

    <!--
        JobRepository存儲執行期的元數據,提供兩種默認實現,一種是存放在內存中,默認實現類爲:MapJobRepositoryFactoryBean(即下面的配置)、
        另一種是存入數據庫中,可以隨時監控批處理Job的執行狀態,查看Job執行結果是成功還是失敗,並且使得在Job失敗的情況下重新啓動Job成爲可能。
    -->
    <bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"></bean>

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

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

</beans>

 JobRepository存儲執行期的元數據,提供兩種默認實現,一種是存放在內存中,默認實現類爲:MapJobRepositoryFactoryBean(即本示例的配置)。

另一種是存入數據庫中,可以隨時監控批處理Job的執行狀態,查看Job執行結果是成功還是失敗,並且使得在Job失敗的情況下重新啓動Job成爲可能。

 

2、接着,編寫一個Student的pojo類。爲了節省篇幅,我把getter、setter都省去了。

package example.ch01;

/**
 * @author maplezhang
 * @date 2019/11/14
 */
public class Student {
    private String name;
    private int score;
    private String grade;

    public Student() { }

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", score=" + score +
                ", grade='" + grade + '\'' +
                '}';
    }
}

3、造一個純文本文件,裏面是學生的基本信息,包括學生的姓名和分數,grade則後面由程序計算得出,因此不會在文本中給出。具體數據見:student_info.txt

張三,99
李四,52
王五,86
趙六,75
劉七,60

4、有了文件,則需要一個讀取文件的讀取器,對應上面框架圖中的ItemReader。ItemReader是一個接口。

但這裏,我們不需要編寫自定義的ItemReader, SpringBatch已經提供了一個類FlatFileItemReader用於讀取txt文件。

 

FlatFileItemReader用於讀取Flat格式的文件,什麼是Flat格式的文件?

Flat文件是一種包含沒有相對關係結構的記錄的文件。可以簡單理解爲純文件的文件,如txt,csv等格式的文件就是Flat文件。

 

5、處理器ItemProcessor,這是我們編寫業務處理代碼的地方,需要我們自己定義,類是StudentProcessor。代碼如下:

 

package example.ch01;

import org.springframework.batch.item.ItemProcessor;

/**
 * @author maplezhang
 * @date 2019/11/15
 */

public class StudentProcessor implements ItemProcessor<Student, Student> {

    public Student process(Student student) throws Exception {
        int score = student.getScore();
        if(score >= 90) {
            student.setGrade("A");
        } else if(score >= 80) {
            student.setGrade("B");
        } else if(score >= 70) {
            student.setGrade("C");
        } else if(score >= 60){
            student.setGrade("D");
        } else {
            student.setGrade("E");
        }
        return student;
    }
}

這是我們處理學生分數的邏輯。

當然,如果在某些場景下,你認爲不需要對數據進行處理,只需要用ItemReader將數據讀入並傳送給ItemWriter,則processor可以省去,這並不是必須的

 

6、輸出器ItemWriter,我們想把Student的信息簡單地輸出即可,所以StudentWriter 也很簡單,代碼如下:

 

package example.ch01;

import org.springframework.batch.item.ItemWriter;

import java.util.List;

/**
 * @author maplezhang
 * @date 2019/11/15
 */
public class StudentWriter implements ItemWriter<Student> {

    public void write(List<? extends Student> list) throws Exception {
        System.out.println(list);
    }
}

7、定義完相應的Reader、Writer、Processor後,我們需要加配置文件,才能用上這些類。

配置文件是:job.xml。

<?xml version="1.0" encoding="UTF-8"?>
<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/batch http://www.springframework.org/schema/batch/spring-batch-3.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd">

    <import resource="classpath:ch01/job-context.xml" />

    <!-- 定義任務和步驟 -->
    <batch:job id="job1" restartable="true" job-repository="jobRepository">
        <batch:step id="step1">
            <batch:tasklet transaction-manager="transactionManager" >
                <!-- 分別定義讀取器、寫入器、處理器以及提交間隔大小。
                 提交間隔意思是,每處理n條數據,進行一次寫入操作。
                 在數據量較大時,可以將commit-interval稍微設大些。 -->
                <batch:chunk reader="flatFileItemReader"
                             writer="studentWriter"
                             processor="studentProcessor"
                             commit-interval="1">
                </batch:chunk>
                <!--定義事務的隔離級別-->
                <batch:transaction-attributes isolation="SERIALIZABLE"/>
            </batch:tasklet>
        </batch:step>
    </batch:job>

    <!-- 
        定義文件讀取器 
        scope="step" 表示該bean的作用域爲step,作用域的概念,類似spring中的scope,只不過,spring batch中的作用域有job、step。
        -->
    <bean id="flatFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader" scope="step">
        <!-- 需要讀取的文件的位置 -->
        <property name="resource" value="classpath:ch01/data/student_info.txt"/>
        <!-- LineMapper接口 用於將一條記錄轉化爲java對象,通常由LineTokenizer和FieldSetMapper組合來實現此功能
         DefaultLineMapper 是LineMapper接口的默認實現 -->
        <property name="lineMapper">
            <bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
                <property name="fieldSetMapper">
                    <!-- BeanWrapperFieldSetMappersk 把FieldSet對象根據名字映射到給定的bean中-->
                    <bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
                        <!-- student在下面有定義 -->
                        <property name="prototypeBeanName" value="student"></property>
                    </bean>
                </property>
                <!-- 定義數據間的分割器 -->
                <property name="lineTokenizer" ref="lineTokenizer"/>
            </bean>
        </property>
    </bean>

    <!--定義分割標記器,delimiter定義分隔符,names中定義分隔後的數據用哪些java字段來接收 -->
    <bean id="lineTokenizer" class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
        <!-- 以逗號爲每個字段的分隔符 -->
        <property name="delimiter" value=","/>
        <!-- 按指定分隔符分隔出來的字符串,分別映射到Student哪些字段,在下面這個List中指定-->
        <property name="names">
            <list>
                <value>name</value>
                <value>score</value>
            </list>
        </property>
    </bean>

    <bean id="studentProcessor" scope="step" class="example.ch01.StudentProcessor"> </bean>

    <bean id="studentWriter" scope="step" class="example.ch01.StudentWriter"></bean>

    <bean id="student" class="example.ch01.Student"></bean>
</beans>

在這個文件裏,首先要定義本次批處理的任務及步驟,可以看到,我們定義了一個任務job,名稱是 job1;

該任務下有一個步驟Step,名稱是step1,step下是一個tasklet, tasklet下是一個chunk, chunk裏面配置着剛纔定義的reader、processor、writer,還有一個commit-interval。

上文我們提過,Job下可包含多個step,但因爲本次示例過於簡單,所以只有一個step。

step、tasklet、chunk等的關係如下圖所示:

 

關於commit-interval

commit-interval 表示提交的間隔,意思是每讀取、處理n條數據,才進行一次寫入,n就是commit-interval的值

官方文檔如下描述:

Once the number of items read equals the commit interval, the

entire chunk is written out by the ItemWriter, and then the transaction is committed. The following

image shows the process:

Figure 14. Chunk-oriented Processing

The following code shows the same concepts shown:

 

 

The following code shows the same concepts shown:

List items = new Arraylist();
for(int i = 0; i < commitInterval; i++){
    Object item = itemReader.read()
    Object processedItem = itemProcessor.process(item);
    items.add(processedItem);
}
itemWriter.write(items);

 

 

 

題外話:讀者可以試試,把上面的commit-interval值改成其它數字,看是否會有不同的執行結果。

如果commit-interval != 1,則需要在配置student 這一bean時,將作用域指定爲 prototype,即student bean 要如下配置:

<bean id="student" class="example.ch01.Student" scope="prototype"></bean>

如果Student作用域不配置爲原型,則默認的作用域是單例模式,那麼當commit-interval>1時(假設commit-interval=n),spring batch 會循環n次,讀取多個文件記錄;然而,每次將文件記錄映射到java 對象時,從容器中取出的student 對象,都是同一個對象(單例模式)。這就導致student信息會不斷被覆蓋,最終的結果就是傳到 ItmProcessor 的student信息會出現重複。

解決辦法如上所述,將student的bean作用域 設置爲 prototype。

 

事務的隔離機制

<batch:transaction-attributes isolation="SERIALIZABLE"/> 用於設置事務的隔離機制,其可選值跟mysql的事務隔離機制一樣,有四個值可選:Serializable, read_committed, read_uncommitted, repeatabled_read,默認值是SERIALIZABLE

 

關於作用域

在上面配置FlatFileItemReader 的代碼中,可以看到scope='step',表示該bean的作用域 是step

以下引用《spring batch 批處理框架》中的解釋:

 

 

FlatFileItemReader 的具體配置

參考job.xml代碼的註釋

 

四、運行結果

 

---------------------------------- start batch job ------------------------------
十一月 20, 2019 4:22:06 下午 org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
信息: Job: [FlowJob: [name=job1]] launched with the following parameters: [{}]
十一月 20, 2019 4:22:06 下午 org.springframework.batch.core.job.SimpleStepHandler handleStep
信息: Executing step: [step1]
[Student{name='張三', score=99, grade='A'}]
[Student{name='李四', score=52, grade='E'}]
[Student{name='王五', score=86, grade='B'}]
[Student{name='趙六', score=75, grade='C'}]
[Student{name='劉七', score=60, grade='D'}]
十一月 20, 2019 4:22:06 下午 org.springframework.batch.core.launch.support.SimpleJobLauncher$1 run
JobExecution: id=0, version=2, startTime=Wed Nov 20 16:22:06 CST 2019, endTime=Wed Nov 20 16:22:06 CST 2019, lastUpdated=Wed Nov 20 16:22:06 CST 2019, status=COMPLETED, exitStatus=exitCode=COMPLETED;exitDescription=, job=[JobInstance: id=0, version=0, Job=[job1]], jobParameters=[{}]
---------------------------------- end batch job ------------------------------

 

參考:

1. spring batch 官方文檔:

https://docs.spring.io/spring-batch/docs/4.2.x/reference/pdf/spring-batch-reference.pdf

2. 《Spring batch 批處理框架》

 

 

 

 

 

發佈了9 篇原創文章 · 獲贊 2 · 訪問量 5665
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章