🦇

「Spring Batch 入門ガイド」を実用化する

2024/02/28に公開

目的

Spring Batch入門ガイド(バッチサービスの作成)」を基礎として、追加要件を組み込み、より実用的なバッチプログラムの作成方法を解説します。ここでの主な焦点は、Spring Batch自体ではなく、Spring Batchを導入する際に必要となる機能についてです。

背景

実際に、Spring Batchによるバッチプログラムを運用する場合に、欲しい機能があります。

  • プロパティファイルの読み込み
  • ログ出力
  • 例外エラー処理
  • プロファイルによる環境の切り替え
  • 実行ジョブの指定

これらの機能を実装するために、以下の要件を設定しました。

要件

  1. 複数のプロパティファイルを読み込む
    バッチプログラム全体とジョブ固有のプロパティファイルを設ける
    プロパティクラスにバインドする
    編集可能にするため、外部プロパティファイルを設ける
    不測の事態を考慮して、組み込みプロパティファイルも設ける
  2. ログにLogbackを使用する
    ログファイルのファイル名に日時を付ける
    ログファイルは30日間保持する
  3. 例外エラー発生時にメール通知する
    外部プロパティファイルが存在しない場合、組み込みプロパティファイルから情報取得する
  4. プロファイルにより環境を切り替える
    開発環境と本番環境のプロファイルを設け、プロパティファイルを切り替える
  5. コマンドライン引数に実行ジョブを指定する
    複数のジョブが存在する場合、指定したジョブのみを実行する
    存在しないジョブを指定した場合はエラーとする
  6. その他
    組み込みWebサーバー無効、バナー非表示とする

これらの要件を満たすように、「Spring Batch入門ガイド」のバッチプログラムを修正追加します。

1. 複数のプロパティファイルを読み込む

バッチプログラム全体とジョブ固有の2つのプロパティファイルを読み込みます。

種類 ファイル名 説明
アプリケーションプロパティ application.properties バッチプログラム全体の設定
ジョブプロパティ job1.properties 各ジョブの設定

これらのプロパティファイルは編集できるように、外部プロパティファイルとします。

1.1 ジョブプロパティのインポート

アプリケーションプロパティは自動的に読み込まれますが、ジョブプロパティはコマンドライン引数に指定します。

java --spring.config.import=optional:file:job1.properties -jar batch-processing-complete.jar

1.2 プロパティクラスのバインディング

プロパティ値を扱いやすくするために、プロパティクラスにバインディングします。

プロパティファイル

アプリケーションプロパティ、ジョブプロパティを以下のように設定します。

application.properties
batch.name=BatchProcessing
job1.properties
batch.jobName=job1
batch.mail.from=sender@example.com
batch.mail.to=receiver@example.com

プロパティクラス

以下のクラスにプロパティをバインディングします。

BatchProperties.java
@ConfigurationProperties(prefix = "batch")
@Data
public class BatchProperties {
    private String batchName;
    private String jobName;
    private Mail mail;

    public BatchProperties() {
        this.mail = new Mail();
    }

    @Data
    public static class Mail {
        private String from;
        private String to;
    }
}

プロパティ値の参照

プロパティ値を参照するクラスに@EnableConfigurationProperties({BatchProperties.class})を追加し、コンストラクタを通じてBatchPropertiesを注入します。それにより、batchProperties.getBatchName()を呼び出して設定値を簡単に取得できます。

JobCompletionNotificationListener.java
@Component
@EnableConfigurationProperties({BatchProperties.class})
public class JobCompletionNotificationListener implements JobExecutionListener {
    private static final Logger log = LoggerFactory.getLogger(JobCompletionNotificationListener.class);
    private final JdbcTemplate jdbcTemplate;
    private final BatchProperties batchProperties;

    public JobCompletionNotificationListener(JdbcTemplate jdbcTemplate, BatchProperties batchProperties) {
        this.jdbcTemplate = jdbcTemplate;
        this.batchProperties = batchProperties;
    }

    @Override
    public void afterJob(JobExecution jobExecution) {
        if(jobExecution.getStatus() == BatchStatus.COMPLETED) {
            log.info("!!! JOB FINISHED! Time to verify the results");
            jdbcTemplate
                .query("SELECT first_name, last_name FROM people", new DataClassRowMapper<>(Person.class))
                .forEach(person -> log.info("Found <{{}}> in the database.", person));
        }
        log.info(String.format("%s - %s", batchProperties.getBatchName(), batchProperties.getJobName()));
    }
}

プロパティファイルの項目検証

さらに、@Validated@NotBlank[1]をフィールドに付けることにより、必須項目として検証させることもできます。

BatchProperties.java
@ConfigurationProperties(prefix = "batch")
@Validated
@Data
public class BatchProperties {

    @Data
    public static class Mail {
        @NotBlank
        private String from;
        @NotBlank
        private String to;
    }
}

この機能を使用する際は、pom.xmlに以下を追加する必要があります。

pom.xml
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

1.3 プロパティファイルの二重化

プロパティファイルは、編集可能にするため外部プロパティファイルとしますが、不測の事態を考慮して、組み込みプロパティファイルも設けます。万が一、外部プロパティファイルが存在しない場合、組み込みプロパティファイルを読み込み、メール通知をさせます。

プロパティファイル構成

├── src
│   └── main
│       └── resources
│           ├── application.properties
│           └── job1.properties
├── application.properties
├── job1.properties

コマンドライン引数

アプリケーションプロパティは自動的に組み込みプロパティファイル、外部プロパティファイルの順に読み込み上書きします。ジョブプロパティの場合は、コマンドライン引数の設定で、組み込みプロパティファイル、外部プロパティファイルの順に読み込み上書きします。

java --spring.config.import=optional:classpath:job.properties,optional:file:job.properties -jar batch-processing-complete.jar

外部プロパティファイルの存在チェック

外部プロパティファイルの存在チェックするために、以下のプロパティ値を追加します。

BatchProperties.java
@Data
public static class Properties {
    @NotBlank
    private String application;
    @NotBlank
    private String job;
}

外部プロパティファイルに以下を追加します。

application.properties(外部プロパティファイル)
batch.properties.application=true
job1.properties(外部プロパティファイル)
batch.properties.job=true

組み込みプロパティファイルには追加せず、外部プロパティファイルが読み込めなかった場合、batch.properties.application、またはbatch.properties.jobは存在しないため、必須項目エラーとなります。

2. ログにLogbackを使用する

Spring Bootにはログ機能が含まれていますが、要件を満たすためには、Logbackなどのログ管理ツールを使用する必要があります。今回はLogbackを使用し、設定ファイルは編集できるようにjarファイルの外部に配置します。

Logback設定ファイル

logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE logback>
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>/var/log/batch.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>/var/log/batch-%d{yyyyMMdd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>
    <encoder>
      <pattern>%date [%thread] %-5level %-40.40logger{1} %message \(%file: %line\)%n</pattern>
    </encoder>
  </appender>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%date{HH:mm:ss.SSS} [%thread] %-5level %-40.40logger{1} - %message%n</pattern>
    </encoder>
  </appender>
  <root level="INFO">
    <appender-ref ref="FILE"/>
    <appender-ref ref="STDOUT"/>
  </root>
</configuration>

アプリケーションプロパティ

アプリケーションプロパティにlogbackの設定ファイルを指定します。

application.properties
logging.config=file:logback.xml
batch.name=BatchProcessing

3. 例外エラー発生時にメール通知する

3.1 mainメソッド

例外エラーを捕捉するために、mainメソッドはtry-catchブロックで囲みます。

BatchProcessingApplication.java
public static void main(String[] args) throws Exception {
    try {
         System.exit(SpringApplication.exit(SpringApplication.run(BatchProcessingApplication.class, args)));
    } catch (Exception e) {
        log.error(e.getCause().getMessage());
        try {
            sendError(e.getCause().getMessage(), args);
        } catch (Exception e1) {
            log.error(e.getMessage());
        }
    }
}

3.2 エラー通知メソッド

プロパティファイルからメール送信情報を取得し、エラー通知を送信します。

BatchProcessingApplication.java
private static void sendError(String message, String[] args) throws Exception {
    loadResourceProperties(args);
    Mail mail = new Mail(batchProperties.getMail());
    mail.send(batchProperties.getBatchName() + "エラー", message);
}

3.3 組み込みプロパティファイル読み込み

Spring Bootの機能により、プロパティファイルは自動的に読み込まれますが、プロパティファイル読み込みでエラーが発生した場合は、組み込みプロパティファイルの読み込み処理は実装する必要があります。

BatchProcessingApplication.java
private static void loadResourceProperties(String[] args) {
    if (batchProperties == null) {
        batchProperties = new BatchProperties();
        String profile = getParameter(args, "--spring\\.profiles\\.active=(\\w+)");
        // アプリケーションプロパティを読み込む
        try {
            Properties properties = new Properties();
            Resource resource = new ClassPathResource(getPropertiesFilename("application.properties", profile));
            properties = PropertiesLoaderUtils.loadProperties(resource);
            batchProperties.setBatchName(properties.getProperty("batch.batchName"));
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        // ジョブプロパティを読み込む
        try {
            Properties properties = new Properties();
            String jobProperties = getParameter(args, "--spring\\.config\\.import=.*?(?:optional:classpath:|optional:file:)?([^,]+\\.properties)");
            Resource resource = new ClassPathResource(getPropertiesFilename(jobProperties, profile));
            properties = PropertiesLoaderUtils.loadProperties(resource);
            batchProperties.setJobName(properties.getProperty("batch.jobName"));
            batchProperties.getMail().setFrom(properties.getProperty("batch.mail.from"));
				batchProperties.getMail().setTo(properties.getProperty("batch.mail.to"));
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }
}

3.4 コマンドライン引数取得

コマンドライン引数の自動取得もできないため、プロパティファイル名を取得を実装します。

BatchProcessingApplication.java
private static String getParameter(String[] args, String patternText) {
    Pattern pattern = Pattern.compile(patternText);
    String parameter = null;
    for (String arg : args) {
        Matcher matcher = pattern.matcher(arg);
        while (matcher.find()) {
            parameter = matcher.group(1);
        }
    }
    return parameter;
}

以上で、例外エラー発生時にメール通知を実装することがてきました。

3.5 メール送信処理

Mail.java
public class Mail {    
    private final BatchProperties.Mail properties;

    public Mail(BatchProperties.Mail properties) {
        this.properties = properties;
    }
    public void send(String subject, String text) throws Exception {
        String from = properties.getFrom();
        String[] to = properties.getTo().split(";");
        InternetAddress[] toAddress = new InternetAddress[to.length];
        for (int i = 0; i < to.length; i++) {
            toAddress[i] = new InternetAddress(to[i]);
        }
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(from);
        message.setTo(to);
        message.setSubject(subject);
        message.setText(text);
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        mailSender.send(message);
    }
}

この機能を使用する際は、pom.xmlに以下を追加する必要があります。

pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

4. プロファイルにより環境を切り替える

コマンドライン引数を使用して、アクティブプロファイルを設定することで、プロパティファイル切り替えることが可能です。

コマンドライン引数

今回は、開発環境(dev)と本番環境(prod)の2つのプロファイルを設定します。

開発環境
java --spring.profiles.active=dev -jar batch-processing-complete.jar
本番環境
java --spring.profiles.active=prod -jar batch-processing-complete.jar

これにより、application-{profile}.propertiesおよびjob-{profile}.propertiesが自動的に読み込まれます。

種類 ファイル名 説明
アプリケーションプロパティ application-dev.properties
application-prod.properties
バッチプログラム全体の設定
ジョブプロパティ job1-dev.properties
job1-prod.properties
各ジョブの設定

プロパティファイル構成

├── src
│   └── main
│       └── resources
│           ├── application-dev.properties
│           ├── application-prod.properties
│           ├── job1-dev.properties
│           └── job1-prod.properties
├── application-dev.properties
├── application-prod.properties
├── job1-dev.properties
├── job1-prod.properties

5. コマンドライン引数に実行ジョブを指定する

「Spring Batch入門ガイド」では、Spring Bootと組み合わせているため、ジョブは自動的に起動します。ジョブが複数ある場合は、実行するジョブを指定しないとエラーになります。

コマンドライン引数

コマンドライン引数で実行するジョブを指定します。

java --spring.batch.job.name=importUserJob -jar batch-processing-complete.jar
Spring Batch4(Java8)の場合

Spring Batch4の場合は、ジョブが複数ある場合、エラーにならず、すべてのジョブが自動起動します。

コマンドライン引数

コマンドライン引数で実行するジョブを指定します。

java --spring.batch.job.names=importUserJob -jar batch-processing-complete.jar

エラー処理

ジョブを指定しない場合、すべてのジョブが起動されますが、それをエラーとします。それを実現するために、CommandLineRunnerインターフェースを実装します。

BatchProcessingApplication.java
@SpringBootApplication
public class BatchProcessingApplication implements CommandLineRunner {

runメソッドをオーバライドし、ジョブ指定チェックを追加します。

BatchProcessingApplication.java
@Override
public void run(String... args) throws Exception {
    String jobName = environment.getProperty("spring.batch.job.names");
    if (jobName == null) {
        throw new Exception("実行するジョブが指定されていません");
    }
}

これにより、ジョブを指定しない場合、例外エラーが発生します。

6. その他

Spring Bootには、組み込みWebサーバーが含まれており、バッチプログラムには不要ですので無効にします。また、起動時にバナーが表示されますが、ログには不要ですので、非表示にします。

application.properties(組み込みプロパティファイル)
spring.main.web-application-type=none
spring.main.banner-mode=off
logging.config=file:logback.xml

ここでは、application-{profile}.propertiesではなく、application.propertiesを作成し設定します。読み込む順番はapplication.propertiesが先になりますので、本番開発環境共通のプロパティ値はapplication.propertiesに設定することができます。

更新・追加ソースファイル

「Spring Batch入門ガイド」のバッチプログラムに対して、更新、追加したファイルです。

├── src
│   └── main
│       ├── java
│       │   └── com.example.batchprocessing
│       │       ├── BatchConfiguration2.java(追加)
│       │       ├── JobCompletionNotificationListener.java(更新)
│       │       ├── BatchProcessingApplication.java(更新)
│       │       ├── BatchProperties.java(追加)
│       │       └── Mail.java(追加)
│       └── resources
│           ├── application.properties(追加)
│           ├── application-dev.properties(追加)
│           ├── application-prod.properties(追加)
│           ├── job1-dev.properties(追加)
│           └── job1-prod.properties(追加)
├── application-dev.properties(追加)
├── application-prod.properties(追加)
├── job1-dev.properties(追加)
├── job1-prod.properties(追加)
├── logback.xml(追加)
└── pom.xml(更新)

コード

https://github.com/k0buchi/gs-batch-processing-complete
Java17、Spring Batch 5.1.1、Spring Boot 3.2.3で、動作確認しています。また、Java8、Spring Batch 4.3.10、Spring Bootは 2.7.18でも、動作確認しています。ビルドツールは、Mavenを使用しています。

最後に

Spring Batch、Spring Bootを含むSpring Frameworkの学習コストは高く、要件を実現するために必要な機能に焦点を当てて習得してきました。そのため、全てを網羅しているわけではなく、現在の知識に基づいた解説となりますこと、ご了承ください。

脚注
  1. @NotBlank、@NotEmpty、@NotNullの挙動の違いをSpring Boot + Thymeleafで整理する ↩︎

Discussion