読者です 読者をやめる 読者になる 読者になる

Spring Bootでキャッシュ(Redis)からのデータ取得エラー時のカスタマイズ

(この前書いた以下の記事に近い話ですが、今回はSpring Sessionに限らない話です)

Spring Bootでキャッシュ機構としてRedis( org.springframework.boot:spring-boot-starter-data-redis )を使っている場合、デフォルトでは Redis への書き込み、Redis からの読み込みは、JdkSerializationRedisSerializer によるJavaのオブジェクトシリアライズ・デシリアライズが行われ、キャッシュの取得時にデシリアライズに失敗すると、「 @Cacheable をつけたメソッドの呼び出しそのものがエラー 」になってしまいます。

この挙動を、デシリアライズに失敗したら、 「 リザルトキャッシュを利用せずに対象メソッドを実行し、その結果をキャッシュに書き込む 」といったものに変更したいと思います。キャッシュにhitしなかった時と同じように扱うということですね。

結論として、Springの場合、 CacheErrorHandler というインタフェースが用意されているので、カスタマイズしたエラーハンドラを使うようにしてあげればよいです。

やり方は、例えば以下の記事にて紹介されています。

qiita.com

ポイントだけをまとめたconfigurationは次のような感じです。

@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {

    @Override
    public CacheErrorHandler errorHandler() {

        return new SimpleCacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
                // キャッシュ取得時のエラーハンドラの内容を書く
            }
        };
    }
}

CacheErrorHandlerは get, put, evict, clear のそれぞれにおけるエラー時のハンドラメソッドを実装しなければならないインタフェースですが、あらかじめSimpleCacheErrorHandlerという何もしない(デフォルトと同じ挙動をさせる)実装が用意されているので、必要なメソッドだけoverrideすればよいです。

今回は次のように実装しました。

@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
    Throwable cause = exception.getCause();
    if (cause != null && cause instanceof SerializationFailedException) {
        return;
    }
    throw exception;
}

ハンドラメソッドの中で例外をスローせずに return すれば、直接対象メソッドが実行されます。
また、結果はキャッシュに書き込まれ、その際、デシリアライズに失敗する古いキャッシュは上書きされる形になります(Spring Boot 1.4.2.RELEASEで検証)。そのため、ハンドラの中で cache#evict を呼んだりしなくてもよいようです。