これは、なにをしたくて書いたもの?
Spring Batchで、複数のItemProcessor
を使うパターンをちょっと試しておこうかなと。
CompositeItemProcessor
結論を言うと、CompositeItemProcessor
を使うことになります。
ドキュメントとしてはこちらに載っています。
Item processing / Chaining ItemProcessors
ItemProcessor
ではItem
を変換したり検証したり、なにか処理をできるわけですが。複数のItemProcessor
をつなげる場合に
CompositeItemProcessor
を使います。
ドキュメントを見たらだいたい雰囲気はわかるのですが、実際に自分でも書いてみます。
お題
今回のお題は、以下とします。
- 書籍データをCSVな
String
としてItemReader
が提供 ItemProcessor
で…- split
- 自分で用意したクラスに変換
ItemWriter
でログ出力
環境
今回の環境は、こちら。
$ java --version openjdk 17.0.3 2022-04-19 OpenJDK Runtime Environment (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1) OpenJDK 64-Bit Server VM (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1, mixed mode, sharing) $ mvn --version Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 17.0.3, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-113-generic", arch: "amd64", family: "unix"
Spring Batchが使用するデータベースには、MySQLを使用します。MySQLは、172.17.0.2で動作しているものとします。
$ mysql --version mysql Ver 8.0.29 for Linux on x86_64 (MySQL Community Server - GPL)
プロジェクトを作成する
まずはSpring Bootプロジェクトを作成します。依存関係にはbatch
とmysql
を追加。
$ curl -s https://start.spring.io/starter.tgz \ -d bootVersion=2.7.0 \ -d javaVersion=17 \ -d name=batch-chaining-itemprocessors \ -d groupId=org.littlewings \ -d artifactId=batch-chaining-itemprocessors \ -d version=0.0.1-SNAPSHOT \ -d packageName=org.littlewings.spring.batch \ -d dependencies=batch,mysql \ -d baseDir=batch-chaining-itemprocessors | tar zxvf -
プロジェクト内に移動。
$ cd batch-chaining-itemprocessors
生成されたソースコードは削除しておきます。
$ rm src/main/java/org/littlewings/spring/batch/BatchChainingItemprocessorsApplication.java src/test/java/org/littlewings/spring/batch/BatchChainingItemprocessorsApplicationTests.java
<properties> <java.version>17</java.version> </properties> <dependencies> <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.batch</groupId> <artifactId>spring-batch-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
最終的に変換先となるクラスは、こちらとします。
src/main/java/org/littlewings/spring/batch/Book.java
package org.littlewings.spring.batch; public class Book { String isbn; String title; Integer price; public static Book create(String isbn, String title, Integer price) { Book book = new Book(); book.setIsbn(isbn); book.setTitle(title); book.setPrice(price); return book; } // getter/setterは省略 }
main
クラスはこちら。
src/main/java/org/littlewings/spring/batch/App.java
package org.littlewings.spring.batch; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @EnableBatchProcessing public class App { public static void main(String... args) { SpringApplication.run(App.class, args); } }
アプリケーションの設定は、このようにします。
src/main/resources/application.properties
spring.datasource.url=jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8 spring.datasource.username=kazuhira spring.datasource.password=password spring.batch.jdbc.initialize-schema=always
ここから、ItemReader
、ItemProcessor
、ItemWriter
、Job
の定義の順に書いていきます。
ItemReader/ItemProcessor/ItemWriter/Jobを定義する
変換元のデータを提供するItemReader
はこちら。
src/main/java/org/littlewings/spring/batch/StringItemReader.java
package org.littlewings.spring.batch; import java.util.Iterator; import java.util.List; import org.springframework.batch.item.NonTransientResourceException; import org.springframework.batch.item.ParseException; import org.springframework.batch.item.UnexpectedInputException; import org.springframework.batch.item.support.AbstractItemStreamItemReader; public class StringItemReader extends AbstractItemStreamItemReader<String> { Iterator<String> iterator; public StringItemReader() { List<String> bookAsStrings = List.of( "978-4798142470,Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発,4400", "978-4774182179,[改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ,4180", "978-1492076988,Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications,6265", "978-1484237236,The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud,7361", "978-4798161488,MySQL徹底入門 第4版 MySQL 8.0対応,4180", "978-4797393118,基礎からのMySQL 第3版 (基礎からシリーズ),6038", "978-4873116389,実践ハイパフォーマンスMySQL 第3版,5280", "978-4295000198,やさしく学べるMySQL運用・管理入門【5.7対応】,2860", "978-4798147406,詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE),3960", "978-4774170206,MariaDB&MySQL全機能バイブル,3860" ); iterator = bookAsStrings.iterator(); } @Override public String read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException { if (iterator.hasNext()) { return iterator.next(); } return null; } }
こんな感じですね。
"978-4798142470,Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発,4400",
こちらをsplitするItemProcessor
。String
からList<String>
に変換します。
src/main/java/org/littlewings/spring/batch/SplitItemProcessor.java
package org.littlewings.spring.batch; import java.util.Arrays; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.batch.item.ItemProcessor; public class SplitItemProcessor implements ItemProcessor<String, List<String>> { Logger logger = LoggerFactory.getLogger(SplitItemProcessor.class); @Override public List<String> process(String item) throws Exception { logger.info("split: {}", item); return Arrays.asList(item.split(",")); } }
そして、List<String>
からBook
に変換するItemProcessor
。
src/main/java/org/littlewings/spring/batch/ToBookItemProcessor.java
package org.littlewings.spring.batch; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.batch.item.ItemProcessor; public class ToBookItemProcessor implements ItemProcessor<List<String>, Book> { Logger logger = LoggerFactory.getLogger(ToBookItemProcessor.class); @Override public Book process(List<String> item) throws Exception { logger.info("to book: {}", String.join(", ", item)); return Book.create( item.get(0), item.get(1), Integer.parseInt(item.get(2)) ); } }
ログ出力を行うItemWriter
。ここでは、Item
をBook
として扱うように作成しています。
src/main/java/org/littlewings/spring/batch/LoggingItemWriter.java
package org.littlewings.spring.batch; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.batch.item.ItemWriter; public class LoggingItemWriter implements ItemWriter<Book> { Logger logger = LoggerFactory.getLogger(LoggingItemWriter.class); @Override public void write(List<? extends Book> books) throws Exception { books.forEach(book -> logger.info("write: isbn = {}, title = {}, price = {}", book.getIsbn(), book.getTitle(), book.getPrice()) ); } }
ItemProcessor
にもログ出力は含めているのですが。
そしてJob
定義。
src/main/java/org/littlewings/spring/batch/JobConfig.java
package org.littlewings.spring.batch; import java.util.List; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.launch.support.RunIdIncrementer; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.support.CompositeItemProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class JobConfig { @Bean public Job job(JobBuilderFactory jobBuilderFactory) { return jobBuilderFactory .get("job") .incrementer(new RunIdIncrementer()) .start(step(null)) .build(); } @Bean public Step step(StepBuilderFactory stepBuilderFactory) { return stepBuilderFactory .get("step") .<String, Book>chunk(3) .reader(itemReader()) .processor(compositeItemProcessor()) .writer(itemWriter()) .build(); } @Bean @StepScope public StringItemReader itemReader() { return new StringItemReader(); } @Bean @StepScope public SplitItemProcessor splitItemProcessor() { return new SplitItemProcessor(); } @Bean @StepScope public ToBookItemProcessor toBookItemProcessor() { return new ToBookItemProcessor(); } @Bean @StepScope public CompositeItemProcessor<String, Book> compositeItemProcessor() { List<ItemProcessor<?, ?>> delegates = List.of( splitItemProcessor(), // String -> List<String> toBookItemProcessor() // List<String> -> Book ); CompositeItemProcessor<String, Book> itemProcessor = new CompositeItemProcessor<>(); itemProcessor.setDelegates(delegates); return itemProcessor; } @Bean @StepScope public LoggingItemWriter itemWriter() { return new LoggingItemWriter(); } }
ItemProcessor
にフォーカスして取り上げます。
まずは、ここまで作成したItemProcessor
をBean定義。
@Bean @StepScope public SplitItemProcessor splitItemProcessor() { return new SplitItemProcessor(); } @Bean @StepScope public ToBookItemProcessor toBookItemProcessor() { return new ToBookItemProcessor(); }
そして、CompositeItemProcessor
。
@Bean @StepScope public CompositeItemProcessor<String, Book> compositeItemProcessor() { List<ItemProcessor<?, ?>> delegates = List.of( splitItemProcessor(), // String -> List<String> toBookItemProcessor() // List<String> -> Book ); CompositeItemProcessor<String, Book> itemProcessor = new CompositeItemProcessor<>(); itemProcessor.setDelegates(delegates); return itemProcessor; }
使用するItemProcessor
をList
にまとめ、CompositeItemProcessor#setDelegates
で設定します。
List
には、ItemProcessor
を適用する順番で登録します。
これで、String
→ List<String>
→ Book
という変換が行われることになります。
あとは、CompositeItemProcessor
をItemProcessor
としてStep
に登録すればOKです。
@Bean public Step step(StepBuilderFactory stepBuilderFactory) { return stepBuilderFactory .get("step") .<String, Book>chunk(3) .reader(itemReader()) .processor(compositeItemProcessor()) .writer(itemWriter()) .build(); }
動作確認する
準備はできたので、パッケージングして
$ mvn package
実行。
$ java -jar target/batch-chaining-itemprocessors-0.0.1-SNAPSHOT.jar
ログ。
2022-05-29 00:02:09.061 INFO 114305 --- [ main] o.s.b.a.b.JobLauncherApplicationRunner : Running default command line with: [] 2022-05-29 00:02:09.225 INFO 114305 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] launched with the following parameters: [{run.id=1}] 2022-05-29 00:02:09.335 INFO 114305 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step] 2022-05-29 00:02:09.417 INFO 114305 --- [ main] o.l.spring.batch.SplitItemProcessor : split: 978-4798142470,Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発,4400 2022-05-29 00:02:09.423 INFO 114305 --- [ main] o.l.spring.batch.ToBookItemProcessor : to book: 978-4798142470, Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発, 4400 2022-05-29 00:02:09.424 INFO 114305 --- [ main] o.l.spring.batch.SplitItemProcessor : split: 978-4774182179,[改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ,4180 2022-05-29 00:02:09.424 INFO 114305 --- [ main] o.l.spring.batch.ToBookItemProcessor : to book: 978-4774182179, [改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ, 4180 2022-05-29 00:02:09.424 INFO 114305 --- [ main] o.l.spring.batch.SplitItemProcessor : split: 978-1492076988,Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications,6265 2022-05-29 00:02:09.424 INFO 114305 --- [ main] o.l.spring.batch.ToBookItemProcessor : to book: 978-1492076988, Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications, 6265 2022-05-29 00:02:09.430 INFO 114305 --- [ main] o.l.spring.batch.LoggingItemWriter : write: isbn = 978-4798142470, title = Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発, price = 4400 2022-05-29 00:02:09.430 INFO 114305 --- [ main] o.l.spring.batch.LoggingItemWriter : write: isbn = 978-4774182179, title = [ 改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ, price = 4180 2022-05-29 00:02:09.431 INFO 114305 --- [ main] o.l.spring.batch.LoggingItemWriter : write: isbn = 978-1492076988, title = Spring Boot: Up and Running: Building Cloud Native Java and Kotlin Applications, price = 6265 2022-05-29 00:02:09.465 INFO 114305 --- [ main] o.l.spring.batch.SplitItemProcessor : split: 978-1484237236,The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud,7361 2022-05-29 00:02:09.465 INFO 114305 --- [ main] o.l.spring.batch.ToBookItemProcessor : to book: 978-1484237236, The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud, 7361 2022-05-29 00:02:09.466 INFO 114305 --- [ main] o.l.spring.batch.SplitItemProcessor : split: 978-4798161488,MySQL徹底入門 第4 版 MySQL 8.0対応,4180 2022-05-29 00:02:09.466 INFO 114305 --- [ main] o.l.spring.batch.ToBookItemProcessor : to book: 978-4798161488, MySQL徹底入門 第4版 MySQL 8.0対応, 4180 2022-05-29 00:02:09.466 INFO 114305 --- [ main] o.l.spring.batch.SplitItemProcessor : split: 978-4797393118,基礎からのMySQL 第3版 (基礎からシリーズ),6038 2022-05-29 00:02:09.467 INFO 114305 --- [ main] o.l.spring.batch.ToBookItemProcessor : to book: 978-4797393118, 基礎からのMySQL 第3版 (基礎からシリーズ), 6038 2022-05-29 00:02:09.467 INFO 114305 --- [ main] o.l.spring.batch.LoggingItemWriter : write: isbn = 978-1484237236, title = The Definitive Guide to Spring Batch: Modern Finite Batch Processing in the Cloud, price = 7361 2022-05-29 00:02:09.467 INFO 114305 --- [ main] o.l.spring.batch.LoggingItemWriter : write: isbn = 978-4798161488, title = MySQL徹底入門 第4版 MySQL 8.0対応, price = 4180 2022-05-29 00:02:09.467 INFO 114305 --- [ main] o.l.spring.batch.LoggingItemWriter : write: isbn = 978-4797393118, title = 基礎からのMySQL 第3版 (基礎からシリーズ), price = 6038 2022-05-29 00:02:09.492 INFO 114305 --- [ main] o.l.spring.batch.SplitItemProcessor : split: 978-4873116389,実践ハイパフォーマンスMySQL 第3版,5280 2022-05-29 00:02:09.493 INFO 114305 --- [ main] o.l.spring.batch.ToBookItemProcessor : to book: 978-4873116389, 実践ハイパフォ ーマンスMySQL 第3版, 5280 2022-05-29 00:02:09.494 INFO 114305 --- [ main] o.l.spring.batch.SplitItemProcessor : split: 978-4295000198,やさしく学べるMySQL運用・管理入門【5.7対応】,2860 2022-05-29 00:02:09.494 INFO 114305 --- [ main] o.l.spring.batch.ToBookItemProcessor : to book: 978-4295000198, やさしく学べるMySQL運用・管理入門【5.7対応】, 2860 2022-05-29 00:02:09.495 INFO 114305 --- [ main] o.l.spring.batch.SplitItemProcessor : split: 978-4798147406,詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE),3960 2022-05-29 00:02:09.495 INFO 114305 --- [ main] o.l.spring.batch.ToBookItemProcessor : to book: 978-4798147406, 詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE), 3960 2022-05-29 00:02:09.496 INFO 114305 --- [ main] o.l.spring.batch.LoggingItemWriter : write: isbn = 978-4873116389, title = 実践ハイパフォーマンスMySQL 第3版, price = 5280 2022-05-29 00:02:09.496 INFO 114305 --- [ main] o.l.spring.batch.LoggingItemWriter : write: isbn = 978-4295000198, title = やさしく学べるMySQL運用・管理入門【5.7対応】, price = 2860 2022-05-29 00:02:09.496 INFO 114305 --- [ main] o.l.spring.batch.LoggingItemWriter : write: isbn = 978-4798147406, title = 詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE), price = 3960 2022-05-29 00:02:09.539 INFO 114305 --- [ main] o.l.spring.batch.SplitItemProcessor : split: 978-4774170206,MariaDB&MySQL全機 能バイブル,3860 2022-05-29 00:02:09.539 INFO 114305 --- [ main] o.l.spring.batch.ToBookItemProcessor : to book: 978-4774170206, MariaDB&MySQL全機能バイブル, 3860 2022-05-29 00:02:09.539 INFO 114305 --- [ main] o.l.spring.batch.LoggingItemWriter : write: isbn = 978-4774170206, title = MariaDB&MySQL全機能バイブル, price = 3860 2022-05-29 00:02:09.568 INFO 114305 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step] executed in 233ms 2022-05-29 00:02:09.624 INFO 114305 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] completed with the following parameters: [{run.id=1}] and the following status: [COMPLETED] in 350ms
OKですね。
まとめ
CompositeItemProcessor
を使って、ItemProcessor
を複数適用してみました。
特にハマりどころもなくあっさりと動かせました。よく使いたくなるものだと思うので、1度確認しておきたかったんですよね。
覚えておきましょう。