SpringBatch從入門到放棄001- HelloWorld
經常有人問我,有沒有一個 Spring Batch 的例子可以參考一下,之前的我的回答一般是百度自己找,太多了。但是後來我發現因為 Spring Batch的版本太穩定,網上的例子大部分都是基於2.X,3.X版本的,還有就是對應的 Spring Boot 的版本也是比較老的。針對這種情況,我決定根據最新 release(4.1.2)的官方文檔,來寫一套最新的例子,供朋友參考。
Spring Boot 集成了 Spring Batch,如果想在程序鍾啟用 Spring Batch 的話,只需要添加@EnableBatchProcessing 註解即可,Spring Boot 會根據內置的 @BatchAutoConfiguration引入必要的配置。
Spring Boot 官方文檔中關於 Spring Batch 的描述
下邊我們就來基於 Spring Boot 搭建一個 Hello World 的 Spring Batch。我先假想一個簡單的需求:讀取某一個目錄的文件,加工一下,再寫入另外一個文件(原諒一個理工科直男只能想到這麼一個傻逼需求)。
Step 1: 新建一個 Spring Boot 的工程
我們通過 IDEA新建一個 Spring Boot的工程,在新建的時候引入兩個必要的依賴:
Spring Batch: 運行 Spring BatchMySQL Driver: 需要通過Spring Batch 需要通過資料庫存儲Job 的運行數據
新建 Spring Boot 工程
打開 POM 我們會發現其實引入batch 的 starter。再 starter 裡面封裝了 batch-core。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
既然引入了 MySQL 的依賴,就配置一個數據源吧,打開 application.properties 文件添加數據源配置(Spring Boot 默認的資料庫連接池,所以只要指定資料庫連接信息就好了),
spring.datasource.username=XXXspring.datasource.password=XXXspring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url= jdbc:mysql://localhost:3306/batch_config?zeroDateTimeBehavior=convertToNull我們需要再程序運行的時候初始化資料庫,再application.properties 添加下邊兩個參數。
#程序啟動的時候初始化一個空數據結構
spring.batch.initialize-schema=embedded
#程序啟動的時候不運行任何 Jobspring.batch.job.enabled=false
Step2: 定義Reader/Processor/Writer
根據我們假想出來的需求,我們需要定一個 Reader 來讀取我們的文件,Spring Batch 內置了很多很好用的 Reader,後邊章節我也會詳細的介紹,這裡我們選用FlatFileItemReader來讀取文件,Spring Batch 提供了FlatFileItemReaderBuilder來幫助我們構建FlatFileItemReader,我們只需要指定幾個簡單的屬性,就可以完成一個讀取文件的 Reader。代碼如下:
@StepScope @Bean public ItemReader<String> itemReader(){ //FlatFileItemReader return new FlatFileItemReaderBuilder<String>() .name("simpleFileReader") .resource(new ClassPathResource("batch-data/2019072401.txt")) .lineMapper(new PassThroughLineMapper()) .build(); }
文件讀完之後,我們對讀出來的每一行統一添加一個字符串來模擬Batch 的處理過程。我們通過實現一個匿名來完成添加字符串的操作:
@StepScope @Bean public ItemProcessor<String,String> itemProcessor(){ return new ItemProcessor<String, String>() { @Override public String process(String item) throws Exception { return "[linghuxiong]"+item; } }; }
處理完讀出來的內容之後我們就需要將讀出來的字符串寫到資料庫或者文件系統裡面了,我們假想的需求是寫文件。同樣的 Spring Batch 官方提供了很多內置的 Writer。因為是寫文件,我們選用FlatFileItemWriter作為我們的 Writer,同樣的我們使用FlatFileItemWriterBuilder來構建我們的FlatFileItemWriter[Spring Batch 4 對FlatFileItemWriterBuilder 進行了增強,詳見官方文檔]。
@StepScope @Bean public ItemWriter<String> itemWriter(){ return new FlatFileItemWriterBuilder<String>() .name("simpleFileWriter") .lineAggregator(new PassThroughLineAggregator<String>()) .resource(new FileSystemResource("/Users/eric/Documents/dev/linghuxiong/spring-boot-demo/batch/target/2019072402.txt")) .build(); }
至此我們的Reader/Processor/Writer 就已經構建完畢了,這就相當於零件已經備好,下邊我們通過構建 Step/Job 來組裝我們的 Batch。
Step 3: 構建 Step /Job
再給項目添加@EnableBatchProcessing之後,就會默認將 Batch 需要對象注入到 Context 中,其中就有構建 Step 的StepBuilderFactory和構建 Job 的JobBuilderFactory。下邊我們就用這兩個 Factory 來構建我們的 Step 和 Job。
首先注入這兩個 Factory :
@Autowired JobBuilderFactory jobBuilderFactory; @Autowired StepBuilderFactory stepBuilderFactory;將 Step2 中的零件組裝成一個 Step:
@Bean public Step step1(){ return stepBuilderFactory.get("step1") .<String,String>chunk(2) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) .build(); }將 Step 放入 Job 中:
@Bean public Job job1(){ return jobBuilderFactory.get("job1").start(step1()).build(); }經過以上兩步,我們的 Job 就定義完成了。下邊我們就來測試一下我們寫的 Job 是否正確:
Step 4: 測試一個 Job
Spring Batch 4.X 提供了一個 @SpringBatchTest 會幫助我們構建一個 Batch 的執行環境,比如:JobLauncherTestUtils/JobRepositoryTestUtils 兩個類。
@RunWith(SpringRunner.class)@SpringBatchTest@SpringBootTestpublic class JobTest { @Autowired private JobLauncherTestUtils jobLauncherTestUtils; @Autowired private JobRepositoryTestUtils jobRepositoryTestUtils; … … … …}
在測試我們的Job 之前,我們需要先清空我們的測試庫,這樣就不會因為上一次的失敗,而引入的髒數據來影響我們這一次的測試。
@Before public void clearMetadata() { jobRepositoryTestUtils.removeJobExecutions(); }
編寫一個簡單的測試 CASE,運行我們的 JOB,並判單是否正常結束:
@Test public void testJob() throws Exception { // given JobParameters jobParameters = jobLauncherTestUtils.getUniqueJobParameters(); // when JobExecution jobExecution = jobLauncherTestUtils.launchJob(jobParameters); // then Assert.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); }
好了,截止到先,我們的 Hello World 的所有代碼都已經編寫結束了。整體的項目結構如下:
項目結構
運行我們Junit Case , 運行成功之後就會在 Writer 指定的文件目錄下邊生成我們處理之後的文件。
運行效果
同時在我們配置的資料庫中,會初始化Batch 框架自己所需要的表:
資料庫創建的表