3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Spring-Retry 攻略記

Last updated at Posted at 2021-11-04

#はじめに
とある既存システムの改修でDBコネクション切れを契機とするエラーに対し、Spring-Retryを利用したリトライ処理の実装を行った。
その際に得られた知見・対応策を今後の覚書として残そうと思います。

※既存システムに対する改修という性質故に、書き方としてはあまり良くないかもしれないのはご容赦を。。。

#Spring-Retry導入
build.gradleに下記の依存関係を追加でOK

build.gradle
compile('org.springframework.retry:spring-retry')
compile('org.springframework.boot:spring-boot-starter-aop')

#実装
###1. EnableRetry
対象のSpringアプリケーションのエントリークラス(@SpringBootApplication)に@EnableRetryを追記

RetrySampleApplication.java
package retrysample.app

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;

@SpringBootApplication
@EnableRetry
public class RetrySampleApplication {

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

}

もしくは@Configurationクラスに追記

RetrySampleConfig.java
package retrysample.app.config

import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;

@Configuration
@EnableRetry
public class RetrySampleConfig {
    // any code...
}

###2. Retryable
特定の例外発生時にリトライさせたい@Controllerもしくは@Serviceのメソッドに@Retryableを追記

RetrySampleService.java
package retrysample.app.service

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

@Service
public class RetrySampleService {

    @Retryable(value = {CannotCreateTransactionException.class, TransactionSystemException.class}, maxAttempts = 3, backoff = @Backoff(delay = 500))
    public void retrySampleService() {
        // any code...
    }
}

この時@Retryableに下記のような設定が出来ます。

  • value(include):リトライ対象とする例外クラス、コンマ区切りで複数指定可能
  • exclude:上記のvalue(include)で指定した例外のうちリトライ対象外とする例外クラス
  • maxAttempts:最大リトライ回数
  • backoff:リトライ間隔、単位はミリ秒

###3. Recover
指定した回数リトライ後に@Retryableで指定した例外が起きた時の独自処理を行う場合は、@Recoverをつけたメソッドを用意

RetrySampleService.java
package retrysample.app.service

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

@Service
public class RetrySampleService {

    @Retryable(value = {CannotCreateTransactionException.class, TransactionSystemException.class}, maxAttempts = 3, backoff = @Backoff(delay = 500))
    public void retrySampleService(int id) {
        // any code...
    }

    // any code..

    @Recover
    public void recover(CannotCreateTransactionException e, int id) {
        // any code...
    }

    @Recover
    public void recover(TransactionSystemException e, int id) {
        // any code...
    }
}

この@Recoverメソッドの注意点として

  • @Retryableメソッドと同一クラスもしくはスーパークラスに書く必要がある
  • @Retryableメソッドと戻り値の型を一致させる必要がある

また、@Retryableメソッドの引数と@Recoverメソッドの引数に利用することも出来ます。(エラーログ出すときに便利)

#個人的な気付き・躓き
これは私自身の思い込み・勘違いに拠るところが大きいのですが。。。

実装後に単体試験を実施したところ、過去に実施した異常系で失敗するようになってしまいました。
「想定してた例外と違う例外吐いてるぞ!」と怒られていたようなのでspring-Retry関連の資料をよく読んでみたところ

@Recoverで用意していない例外が@Retryableメソッド内で発生した場合は当該の例外がネストされたExhaustedRetryExceptionが発生する

ことを単体試験の段階で知りました。
@Retryableのvalueで設定していない例外が発生した場合はこれまで通りのエラーハンドリングをしてくれるとばかり思い込んでいたこともあり、前調査の不足を痛感することとなりました。。。

Account accountInfo = accountRepository.findById(int id);
if(Objects.isNull(accountInfo)) {
    throw new AppUniqueException("アカウントが存在しません", e);
}

このような対象アカウント存在チェック時の例外で最終的にスローされるのは、AppUniqueExceptionではなくAppUniqueExceptionをネストしたExhaustedRetryExceptionだったのです。

org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method; nested exception is retrysample.app.exception.AppUniqueException: アカウントが存在しません

####対応策
既存のエラーハンドラーにスローする用の@Recoverメソッドを用意する

RetrySampleService.java

// any code..

@Recover
public void recover(Exception e, int id) {
    throw e.getCause();
}

これでExhaustedRetryExceptionにネストされた既存の例外を既存のエラーハンドリングに戻すように改修しました。

#参考資料
https://tech.atware.co.jp/spring-retry

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?