資料共享-spring batch(9)上下文處理

Mason技術記錄發表於2020-11-30

在 Spring Batch 中進行資料及引數傳遞的方法。

1 引言

本文是 Spring Batch 系列文章的第9篇,有興趣的可見文章:

前面文章以例項的方式對 Spring Batch 進行批處理進行詳細說明,相信大家對字串、檔案,關係型資料庫及 NoSQL 資料庫的讀取,處理,寫入流程已比較熟悉。有小夥伴就問,針對這個任務流程,期間有多個步驟,從任務( Job )啟動,到作業步( Step )的執行,其中又包含讀元件、處理元件、寫元件,那麼,針對這個流程,若中間需要傳遞自定義的資料,該如何處理?本文將對 Spring Batch 進行資料傳遞的方法進行描述,依然會使用程式碼例項的方式進行講解。包括以下幾個內容:

  • 基於 Mybatis-plus 整合多資料來源的資料庫訪問
  • 使用 ExecutionContext 共享資料
  • StepScope 動態繫結引數傳遞

2 開發環境

  • JDK環境: jdk1.8
  • Spring Boot: 2.1.4.RELEASE
  • Spring Batch:4.1.2.RELEASE
  • 開發IDE: IDEA
  • 構建工具Maven: 3.3.9
  • 日誌元件logback:1.2.3
  • lombok:1.18.6
  • MySQL: 5.6.26
  • Mybatis-plus: 3.4.0

本示例原始碼已放至githubhttps://github.com/mianshenglee/spring-batch-example/tree/master/spring-batch-param,請結合示例程式碼進行閱讀。

3 基於 Mybatis-plus 整合多資料來源的資料庫訪問

本示例還是使用原來示例功能,從源資料庫讀取使用者資料,處理資料,然後寫入到目標資料庫。其中會在任務啟動時傳遞引數,並在作業步中傳遞引數。之前已經介紹過如何使用 beetlsql 進行多資料來源配置([便捷的資料讀寫-spring batch(5)結合beetlSql進行資料讀寫][5]),實現資料批處理。還有很多朋友使用 Mybatis 或 Mybatis-plus 進行資料庫讀寫,因此,有必要提一下 Spring Batch 如何結合 Mybatis 或 Mybatis-plus 配置多資料來源操作。本示例以 Mybatis-plus 為例。

示例工程中的sql目錄有相應的資料庫指令碼,其中源資料庫mytest.sql指令碼建立一個test_user表,並有相應的測試資料。目標資料庫 my_test1.sqlmytest.sql表結構一致,spring-batch-mysql.sql是 Spring Batch 本身提供的資料庫指令碼。

3.1 pom 檔案中引入 Mybatis-plus

<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>

3.2 配置及使用多資料來源

本示例會涉及三個資料庫,分別是 Spring Batch 本身資料庫,需要批處理的源資料庫,批處理的目標資料庫。因此需要處理多個資料庫,利用多套源策略,可以很簡單就完成多套資料來源的處理。簡單來說主要分為以下幾個步驟:

  • 配置多資料來源連線資訊
  • 根據不同資料來源劃分mapper 包,entity包,mapper.xml檔案包
  • 根據不同資料來源配置獨立的SqlSessionFactory
  • 根據不同的應用場景,使用不同的 mapper

關於多資料來源多套源策略的詳細配置過程,可以參考我的另一篇文章《搞定SpringBoot多資料來源(1):多套源策略

4 ExecutionContext 傳遞引數

關於 Spring Batch 的讀資料( ItemReader )、處理資料( ItemProcessor )、寫資料( ItemWriter )的配置流程,可以參考前面系列文章,本文不再詳細描述。我們需要記住的是,當一個作業( Job )啟動,Spring Batch 是通過作業名稱( Job name)及 作業引數( JobParameters )作為唯一標識來區分不同的作業。一個 Job 下可以有多個作業步( Step ),每個 Step 中就是有具體的操作邏輯(讀、處理、寫)。在 Job 和 Step 下的各個操作步驟間,如何傳遞,,這裡就需要理解 ExecutionContext 的概念。

4.1 ExecutionContext 概念

在 Job 的執行及 Step 的執行過程中,Spring Batch 提供 ExecutionContext 進行執行資料持久化,利用它,可以根據業務進行資料共享,如用來重啟的靜態資料與狀態資料。如下圖:

Spring Batch 引數傳遞

Execution Context 本質上來講就是一個 Map<String,Object> ,它是Spring Batch 框架提供的持久化與控制的 key/value 對,可以讓開發者在 Step 執行或Job 執行過程中儲存需要進行持久化的狀態,它可以。分為兩類,一類是Job 執行的上下文(對應資料表:BATCH_JOB_EXECUTION_CONTEXT),另一類是Step Execution的上下文(對應資料表BATCH_STEP_EXECUTION_CONTEXT)。兩類上下文關係:一個 Job 執行對應一個 Job Execution 的上下文(如上圖中藍色部分的 ExecutionContext ),每個 Step 執行對應一個 Step Execution 上下文(如上圖中粉色部分的 ExecutionContext );同一個 Job 中的 Step Execution 共用 Job Execution 的上下文。也就是說,它們的作用範圍有區別。因此,如果同一個 Job 的不同 Step 間需要共享資料時,可以通過 Job Execution 的上下文共享資料。根據 ExecutionContext 的共享資料特性,則可以實現在不同步驟間傳遞資料。

4.2 ExecutionContext 傳遞資料

一個 Job 啟動後,會生成一個 JobExecution ,用於存放和記錄 Job 執行的資訊,同樣,在 Step 啟動後,也會有對應的 StepExecution 。如前面所說,在 JobExecution 和 StepExecution 中都會有一個 ExecutionContext ,用於儲存上下文。因此,資料傳遞的思路就是確定資料使用範圍,然後通過 ExecutionContext 傳入資料,然後就可以在對應的範圍內共享資料。如當前示例,需要 Job 範圍內共享資料,在讀元件( ItemReader )和寫元件( ItemWriter )中傳遞讀與寫資料的數量( size ),在 Job 結束時,輸出讀及寫的資料量。實際上 Spring Batch 會自動計算讀寫數量,本示例僅為了顯示資料共享功能。

共享資料

那麼,如何獲取對應的 Execution ?,Spring Batch 提供了 JobExecutionListener 和 StepExecutionListener 監聽器介面,通過實現監聽器介面,分別可以在開啟作業前( beforeJob )和 完成作業後( afterJob )afterJob ),開啟作業步前( beforeStep)及 完成作業步後( afterStep )獲取對應的 Execution ,然後進行操作。

4.2.1 實現監聽器介面

在自定義的 UserItemReader 和 UserItemWriter 中,實現 StepExecutionListener 介面,其中使用 StepExecution 作為成員,從 beforeStep 中獲取。如下:

public class UserItemWriter implements ItemWriter<TargetUser>, StepExecutionListener {
    private StepExecution stepExecution;
    //...略
    @Override
    public void beforeStep(StepExecution stepExecution) {
        this.stepExecution = stepExecution;
    }
}

讀元件( UserItemReader )也使用同樣的方式。而在作業結束後,獲取引數,則可以繼承 JobExecutionListenerSupport ,實現自己感興趣的方法,也從引數中獲取 JobExecution,然後獲取引數進行處理。

public class ParamJobEndListener extends JobExecutionListenerSupport {
    @Override
    public void afterJob(JobExecution jobExecution) {}
}

4.2.2 設定用於傳遞的資料

由於我們需要在 Job 範圍內傳遞引數,獲取到 StepExecution 後,可以獲得相應的 JobExecution ,進而獲取 Job 對應的 executionContext,這樣,就可以在 Job 範圍內共享引數資料了。如下是在讀元件中進行配置

ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext();
            executionContext.put(SyncConstants.PASS_PARAM_READ_NUM, items.size());

同樣在寫元件中,獲取到 ExecutionContext 後,可以對引數進行處理。本示例中,是通過對 ItemReader 傳遞的處理數目引數進行累加處理,得到結果。

@Override
public void write(List<? extends TargetUser> items) {
    ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext();
    Object currentWriteNum = executionContext.get(SyncConstants.PASS_PARAM_WRITE_NUM);
    if (Objects.nonNull(currentWriteNum)) {
        log.info("currentWriteNum:{}", currentWriteNum);
        executionContext.put(SyncConstants.PASS_PARAM_WRITE_NUM, items.size()+(Integer)currentWriteNum);
    } else {
        executionContext.put(SyncConstants.PASS_PARAM_WRITE_NUM, items.size());
    }

最後在作業結束後,在實現 JobExecutionListenerSupport 的介面中,afterJob 函式中,對引數進行輸出。

public class ParamJobEndListener extends JobExecutionListenerSupport {
    @Override
    public void afterJob(JobExecution jobExecution) {
        ExecutionContext executionContext = jobExecution.getExecutionContext();
        Integer writeNum = (Integer)executionContext.get(SyncConstants.PASS_PARAM_WRITE_NUM);
        log.info(LogConstants.LOG_TAG + "writeNum:{}",writeNum);
    }
}

5 StepScope 動態繫結引數傳遞

5.1 StepScope及後期繫結

前面說到在 Job 及 Step 範圍內,使用 ExecutionContext 進行資料共享,但,如果需要在 Job 啟動前設定引數,並且每次啟動輸入的引數是動態變化的(比如增量同步時,日期是基於上一次同步的時間或者ID),也就是說,每次執行,需要根據引數新建一個操作步驟(如 ItemReader、ItemWriter等),我們知道,由於在 Spring IOC 中載入的Bean,預設都是單例模式的,因此,需要每次執行新建,執行完銷燬,新建是在執行時進行的。這就需要用到StepScope 及後期繫結技術。

在之前的示例中,已出現過 StepScope,它的作用是提供了操作步驟的作用範圍,某個 Spring Bean 使用註解StepScope,則表示此 Bean 在作業步( Step )開始的時候初始化,在 Step 結束的時候銷燬,也就是說 Bean的作用範圍是在 Step 這個生命週期中。而 Spring Batch 通過屬性後期繫結技術,在執行期獲取屬性值,並使用 SPEL 的表示式進行屬性繫結。而在 StepScope 中,Spring Batch 框架提供 JobParameters,JobExecutionContext,StepExecutionContext,當然也可以使用 Spring 容器中的 Bean ,如 JobExecution ,StepExecution。

5.2 作業引數傳遞及動態獲取 StepExecution

一個 Job 是由 Job name 及 JobParameters 作為唯一標識的,也就是說只有 job name 和 JobParameters 不一致時,Spring Batch 才會啟動一個新的 Job,一致的話就當作是同一個 Job ,若 此 Job 未執行過,則執行;若已執行過且是 FAILED 狀態,則嘗試重新執行此 Job ,若已執行過且是 COMPLETED 狀態,則會報錯。

本示例中,Job 啟動時輸入時間引數,在 ItemReader 中使用 StepScope 註解,然後把時間引數繫結到 ItemReader 中,同時繫結 StepExecution ,以便於在 ItemReader 對時間引數及 StepExecution 進行操作。

5.2.1 設定時間引數

在使用 JobLauncher 啟動 Job 時,是需要輸入 jobParameters 作為引數的。因此可以建立此物件,並設定引數。

JobParameters jobParameters = new JobParametersBuilder()
                .addLong("time",timMillis)
                .toJobParameters();

5.2.2 動態繫結引數

在配置 Step 時,需要建立ItemReader 的 Bean,為了使用動態引數,在 ItemReader 中設定 Map 存放引數,並設定 StepExecution 為成員,以便於後面使用 ExecutionContext。

public class UserItemReader implements ItemReader<User> {
    protected Map<String, Object> params;
    private StepExecution stepExecution;

    public void setStepExecution(StepExecution stepExecution) {
        this.stepExecution = stepExecution;
    }
}

使用 StepScope 進行配置:

@Bean
@StepScope
public ItemReader paramItemReader(@Value("#{stepExecution}") StepExecution stepExecution,
                                  @Value("#{jobParameters['time']}") Long timeParam) {
    UserItemReader userItemReader = new UserItemReader();
    //設定引數
    Map<String, Object> params = CollUtil.newHashMap();
    Date datetime = new Date(timeParam);
    params.put(SyncConstants.PASS_PARAM_DATETIME, datetime);
    userItemReader.setParams(params);
    userItemReader.setStepExecution(stepExecution);

    return userItemReader;
}

注意:此時 ItemReader 不可再使用實現 StepExecutionListener 的方式來對 stepExecution 賦值,由於 ItemReader 是動態繫結的,StepExecutionListener 將不再起作用,因此需要在後期繫結中來繫結 stepExecution Bean 的方式來賦值。

5.2.3 設定及傳遞引數

ItemReader 獲取到 StepExecution 後即可獲取 ExecutionContext,然後可以像前面說的使用 ExecutionContext 方式進行資料傳遞。如下:

ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext();
//readNum引數
executionContext.put(SyncConstants.PASS_PARAM_READ_NUM, items.size());
//datetime引數
executionContext.put(SyncConstants.PASS_PARAM_DATETIME,params.get(SyncConstants.PASS_PARAM_DATETIME));

6.總結

在 Job 和 Step 不同的資料範圍中,可使用 ExecutionContext 共享資料。本文以傳遞處理數量為例,使用 Mybatis-plus,基於 ExecutionContext ,結合 StepScope及後期繫結技術,實現在 Job 啟動傳入引數,然後在 ItemReader、ItemProcessor、ItemWriter 及 Job 完成後的資料共享及傳遞。如果你在使用 Spring Batch 過程中需要進行資料共享與傳遞,請試試這種方式吧。

往期文章

如果文章內容對你有幫助,歡迎轉發分享~

我的公眾號(搜尋Mason技術記錄),獲取更多技術記錄:

Mason技術記錄

相關文章