LoginSignup
1
1

More than 3 years have passed since last update.

Spring Retryでリトライ実装し、テストする

Last updated at Posted at 2020-09-18

概要

Spring Retryを用いるとリトライ処理を容易に記述することができます。
ここではSpring Retryの機能を用いた簡単なDBアクセスリトライ処理の実装と、その機能のテストを実装します。

実装

@EnableRetry@Configurationが付与されているクラスに付与します。
@SpringBootApplication@Configurationを内包してます。

package com.example.demo;

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

@SpringBootApplication
@EnableRetry
public class DemoApplication {

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

}

O/R MapperとしてMyBatisを使います。

package com.example.demo.repository;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;

@Mapper
public interface DemoRepository { 

  @Select("select title from todo order by id fetch first 1 rows only;")
  String select();

}

上記を呼び出すためのクラスを用意します。

package com.example.demo.repository;

import org.springframework.dao.QueryTimeoutException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;

@Component
@Retryable(value = {QueryTimeoutException.class}, maxAttempts = 3, backoff = @Backoff(delay = 500)) // ①
public class ExecQuery {

  private final DemoRepository demoRepository;

  public ExecQuery(DemoRepository demoRepository) {

    this.demoRepository = demoRepository;
  }

  public String useDemoRepo() {
    return demoRepository.select();
  }

  @Recover // ②
  String recover() {
    return "recover";
  }

}

①:@Retryableアノテーションにどのようにリトライをしたいのか設定できます。
valueにはどのようなエラーを捕捉したときにリトライを実施するかを決めます。
ここではQueryTimeoutExceptionを捕捉したときにリトライすると設定しています。
maxAttemptsには最大試行回数を、backoffにはどのくらいの間隔を空けるかを設定しています。ここでは最大試行回数3回、0.5秒間隔の設定でリトライするようになっています。
②:@Recoverアノテーションにて、最大試行回数を行っても捕捉対象エラーが出た場合にどのような対応をするか記述します。ここでは3回リトライを実施してもQueryTimeoutExceptionを返してきた場合にrecover文字列を返却します。

DemoRepositoryに直接@Retryableを付与してもうまくリトライされません。
インジェクトするBeanのサブクラス関係だと思っているのですが、よくわからず。。
https://qiita.com/kazuki43zoo/items/757b557c05f548c6c5db#spring-aop%E3%81%A8%E3%81%AF

テスト

Mockitoを使って、DemoRepositoryからエラーを返すようにしています。

package com.example.demo;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

import com.example.demo.model.Demo;
import com.example.demo.repository.DemoRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;


@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ExtendWith(MockitoExtension.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
    MockitoTestExecutionListener.class})
public class RetryTest {

  @MockBean(name = "demoRepository") // name指定しないとmockが動かない。
  private DemoRepository demoRepository;

  @Autowired
  private TestRestTemplate testRestTemplate;

  @Test
  void recoveryTest() {
    given(this.demoRepository.select()).willThrow(new QueryTimeoutException("qt"));

    final ResponseEntity<Demo> responseEntity =
        testRestTemplate.getForEntity("/demo", Demo.class);

    assertThat(responseEntity.getBody().getDbData()).isEqualTo("recover");

  }

  @Test
  void retryTest() {
    given(this.demoRepository.select()).willThrow(new QueryTimeoutException("qt"))
        .willReturn("title");

    final ResponseEntity<Demo> responseEntity =
        testRestTemplate.getForEntity("/demo", Demo.class);

    assertThat(responseEntity.getBody().getDbData()).isEqualTo("title");

  }
}

参考

https://github.com/nannany/spring-retry-sample
https://github.com/spring-projects/spring-retry

1
1
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
1
1