Spring Batchを試してみた

## 概要
STSを使ってSpring Batchを使ったときの備忘録を残しておきます。
主に自分用。
Spring Batchとは、Spring Frameworkをベースにしたバッチアプリケーションフレームワークです。

開発環境は次の通りです。
OS : Windows 7 Home Edition 64bit
Java : JavaSE 8 update 181
Spring Boot : 2.3.4
STS : 4.6.1

## STSのセットアップ
STSのセットアップは[自分の備忘録](https://qiita.com/harayoshi/items/fca60e68087640b26292)を参考にしました。

## プロジェクトの作成
下記のCreate new Spring Starter Projectをクリックします。
![createProject.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533981/0ff83636-3af2-9431-0e17-76b953568726.png)

プロジェクトの設定をします。
![NewSpringStarterProject.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533981/d9f96091-71c5-0340-53dc-f40ed503a11e.png)

ライブラリ参照を設定します。
![dependencies.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533981/7f4e9e52-b729-2165-cdfe-f277715ae239.png)

site infoを設定、変更無しです。
![siteInfo.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533981/a3a47f39-22dd-908e-9fca-ba3ae0e18f4c.png)

プロジェクトができました。
![ProjectExplorer.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533981/ae93e96d-bbc7-82f5-765a-2ebb9b040bd9.png)

### 注意事項
#### Lombokについて
Lombokだけはjarをダウンロードして、
インストーラーで使っているSTSを選択、インストールしてください。
インストーラーは[Lombok公式](https://projectlombok.org/)からダウンロード可能です。
Lombokはプロジェクト参照させているため、
アノテーションを使ってもコンパイルエラーになりませんが、
getter/setter、コンストラクタが自動生成されず、
これらを呼び出そうとするとコンパイルエラーになります。
getter/setter等はIDEに自動生成させる必要があるため、
STSへのインストールも必要です。

#### Oracle Driverのインストール
本記事ではDBを用いていませんが、
Spring Boot+Spring Batchの構成ではDB利用を前提としています。
そのため、application.propertiesやapplication.ymlに
DB接続情報を記載していなかったり、
そもそもOracle Driverが無いといった構成で
アプリケーションを実行すると
__datasourceが必要というエラーで実行できません。__
そのため、動作確認したPCにセットアップしていたOracleに
接続できるよう、ライブラリ参照しています。
また、実行時に__DBを起動しなくても実行可能__です。
ライブラリがあって、DBスキーマに接続しようとする構成、設定なら
OKということです。

## バッチアプリケーションの作成
Spring Batch公式のget startedは[こちら](https://spring.io/guides/gs/batch-processing/)です。
上記の場合、作ったjarを実行してcsvデータをDBのテーブルにロードする仕様です。

この記事では、cronを使わずにバッチ処理をスケジューリングしてみます。
参考にしたのは[Spring Batch scheduling tasks](https://spring.io/guides/gs/scheduling-tasks/)と
[@Scheduledのドキュメント](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html)です。

例としてBeanを作成し、その内容をログ出力する処理を実装してみます。
ログ出力用の変数宣言やBeanクラスのボイラープレートを省略するため、[Lombok](https://www.projectlombok.org/features/all)を参照します。

実装したクラスは次の通りです。

まず、@SpringBootApplicationを付与したクラス、
実行時に指定するクラスです。

```java:BatchSampleApplication.java
package jp.co.illmatics;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BatchSampleApplication {

	public static void main(String[] args) {
		SpringApplication.run(BatchSampleApplication.class, args);
	}

}
```

バッチ処理のメインルーチンです。
@EnableSchedulingを指定することで
バッチのスケジューリングが可能になり、
@Scheduledを指定したメソッドが
__cron = "${cron.pattern1}"__
で指定したタイミングで実行されます。

```java:Batch.java
package jp.co.illmatics;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import jp.co.illmatics.bean.Member;
import jp.co.illmatics.enumeration.VoicePart;
import lombok.extern.slf4j.Slf4j;

@Configuration
@EnableScheduling
@Slf4j
public class Batch {

	private String firstName = "Dummy";
	private String familyName = "Family";
	private String part = VoicePart.LEAD_VOCAL.toString();
	private String birthday = "1991/01/01";

	@Scheduled(cron = "${cron.pattern1}")
	public void execute() {
		Member member = new Member(firstName, familyName, part, birthday);
		log.info("member = " + member.toString());
	}

}
```

リソースファイルです。
キーcron.pattern1でバッチ処理の実行タイミングを
毎分0秒ごとに指定しています。
書式については[公式APIドキュメント](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/support/CronSequenceGenerator.html)をご確認ください。

```properties:application.properties
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:XE
spring.datasource.username=xxxxxx
spring.datasource.password=xxxxxx
spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect
spring.jpa.show-sql=true

cron.pattern1: 0 * * * * *
```

テストデータ用のBeanです。
Bean Validationを使ってバリデーションしています。
その中でも、カスタムバリデーションとして@ExistsInVoicePartを実装しました。

```java:Member.java
package jp.co.illmatics.bean;

import javax.validation.constraints.NotEmpty;

import org.springframework.format.annotation.DateTimeFormat;

import jp.co.illmatics.validator.annotation.ExistsInVoicePart;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Data
@AllArgsConstructor
public class Member {

	@NotEmpty(message = "Input first name.")
	private String firstName;

	@NotEmpty(message = "Input family name.")
	private String familyName;

	@ExistsInVoicePart
	private String part;

	@DateTimeFormat(pattern = "yyyy/MM/dd")
	private String birthday;

}
```

カスタムバリデーション用のアノテーションです。

```java:ExistsInVoicePart.java
package jp.co.illmatics.validator.annotation;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

import jp.co.illmatics.validator.VoicePartValidator;

@Constraint(validatedBy = {VoicePartValidator.class})
@Target({FIELD})
@Retention(RUNTIME)
public @interface ExistsInVoicePart {

	String message() default "Input voice part.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    
    @Target({FIELD})
    @Retention(RUNTIME)
    public @interface List {
        ExistsInVoicePart[] value();
    }

}
```

カスタムバリデータ用のEnumです。

```java:VoicePart.java
package jp.co.illmatics.enumeration;

public enum VoicePart {

	LEAD_VOCAL("lead vocal"),
	BEATBOXER("beatboxer"),
	BASS("bass"),
	OTHERS("others");

    @SuppressWarnings("unused")
	private final String name;
 
    private VoicePart(String name) {
        this.name = name;
    }
}
```

カスタムバリデータです。

```java:VoicePartValidator.java
package jp.co.illmatics.validator;

import java.util.Arrays;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import jp.co.illmatics.enumeration.VoicePart;
import jp.co.illmatics.validator.annotation.ExistsInVoicePart;

public class VoicePartValidator implements ConstraintValidator<ExistsInVoicePart, String> {

	public void initialize(ExistsInVoicePart annotation) {
	}

	@Override
	public boolean isValid(String value, ConstraintValidatorContext context) {
		return Arrays.stream(VoicePart.values()).filter(part -> part.name().equals(value)).findFirst().isPresent();
	}
}
```

## 動作確認
BatchSampleApplication.javaを右クリック、
Spring Boot Applicationとして実行します。

毎分0秒ごとに実行されています。
![実行結果.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533981/6a148ace-7d6f-d1c3-83e8-25a8b0782745.png)

以上です。最後までお読みいただき、ありがとうございました。