概要
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