Spring Cache と Redis を用いたキャッシュ統合の実装とカスタム削除処理の設計

アノテーションによるキャッシュ制御の導入方法

Spring Cache を利用して、Redis をバックエンドストレージとして使用する場合の統合手順を解説します。本稿では、主にアノテーションベースの宣言的キャッシュ管理を中心に、設定からカスタマイズまでを網羅します。

1. 必要な依存関係の追加

Maven プロジェクトにおいて、以下の依存を pom.xml に含める必要があります。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

2. Redis 接続設定

application.yml または application.properties で接続情報を定義します。

spring.data.redis.host=127.0.0.1
spring.data.redis.port=6379
spring.data.redis.password=
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-wait=-1ms
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=0
spring.data.redis.lettuce.pool.time-between-eviction-runs=30s

3. キャッシュ共通設定

Spring Cache の挙動をアプリケーション全体で制御するために、プロパティファイルで基本設定を行います。

# キャッシュプロバイダを Redis に指定
spring.cache.type=redis

# TTL(有効期間):1時間(ミリ秒)
spring.cache.redis.time-to-live=3600000

# キーにプレフィックスを使用するか
spring.cache.redis.use-key-prefix=true

# null 値もキャッシュ対象とする(キャッシュ貫通防止)
spring.cache.redis.cache-null-values=true

4. Java 設定クラスの作成

@EnableCaching を付与することで、アノテーションによるキャッシュ機能が有効化されます。

@Configuration
@EnableCaching
public class CacheConfiguration {
}

キャッシュの適用パターンと注意点

レイヤー設計における位置付け

キャッシュは通常、リポジトリ層(Repository)に配置されます。理由としては、データアクセスの直接的な抽象化であり、DB 操作とキャッシュ戦略を一元管理できるためです。特に MyBatis-Plus 等を使用している場合、Mapper 層が完全なビジネスロジックを保持していない可能性があるため、Repository 層での制御が望ましいです。

キャッシュヒット時の挙動

  • 読み取りメソッド@Cacheable により、メソッド実行前にキャッシュが検索されます。ヒットした場合は、メソッド本体が実行されず、AOP インターセプターが結果を返却します。
  • 書き込みメソッド@CacheEvict は常に実行され、DB 更新後にキャッシュを削除します。これにより、不整合を回避します。

キャッシュキー設計のベストプラクティス

引数なしメソッド

メソッド名自体をキーとして利用するのがシンプルです。

@Cacheable(key = "#root.methodName")
public List<ConfigEntry> findAllConfigs() {
    return configMapper.selectAll();
}

単一引数メソッド

引数の値をキーに使用します。

@Cacheable(key = "#id")
public User findUserById(String id) {
    return userMapper.selectById(id);
}

複数フィールドを持つオブジェクト引数

更新や削除時に複合キーに対応する必要があるため、引数には可能な限り完全なエンティティを渡すべきです。

@Caching(evict = {
    @CacheEvict(key = "#user.id"),
    @CacheEvict(key = "#user.accountId + ':' + #user.id")
})
public void updateUser(User user) {
    userMapper.update(user);
}

非推奨なパターン

  • コレクション型引数:List, Set などの動的集合はキー生成が困難なため、キャッシュ対象外とすべきです。
  • Map 引数による条件検索:フロントエンドからの多条件絞り込みなど、可変性が高いクエリには不向きです。
  • 結合クエリ(JOIN):複数テーブルのデータを含む場合、キャッシュ削除の責任範囲が広がるため避けるべきです。
  • ページネーション付きクエリ:ページ番号やサイズによって結果が異なるため、キャッシュ効率が極端に低下します。

カスタム CacheManager による evict の拡張

標準機能の限界

Spring Cache の @CacheEvict は、デフォルトで単一の文字列キーのみをサポートしており、複数キーの一括削除やワイルドカードによる削除はできません。これを解決するため、RedisCacheRedisCacheManager を拡張します。

1. カスタム RedisCache の実装

バッチ削除に対応した evict メソッドを提供します。

public class ExtendedRedisCache extends RedisCache {

    private final StringRedisTemplate template;

    protected ExtendedRedisCache(String name, RedisCacheWriter writer, 
                                RedisCacheConfiguration config, 
                                StringRedisTemplate template) {
        super(name, writer, config);
        this.template = template;
    }

    @Override
    public void evict(Object key) {
        if (key instanceof Collection) {
            Collection<?> keys = (Collection<?>) key;
            List<String> formattedKeys = keys.stream()
                .map(k -> getName() + "::" + k)
                .collect(Collectors.toList());
            template.delete(formattedKeys);
        } else {
            super.evict(key);
        }
    }
}

2. カスタム CacheManager の作成

上記キャッシュを生成するようにマネージャーをカスタマイズします。

public class ExtendedCacheManager extends RedisCacheManager {

    private final StringRedisTemplate template;

    public ExtendedCacheManager(RedisCacheWriter writer,
                               RedisCacheConfiguration defaultConfig,
                               StringRedisTemplate template) {
        super(writer, defaultConfig);
        this.template = template;
    }

    @Override
    protected RedisCache createRedisCache(String name, RedisCacheConfiguration config) {
        return new ExtendedRedisCache(name, getCacheWriter(), config, template);
    }
}

3. Bean 登録

DI コンテナにカスタムマネージャーを登録します。

@Configuration
public class CacheConfig {

    @Bean
    public CacheManager cacheManager(StringRedisTemplate template) {
        RedisCacheWriter writer = RedisCacheWriter.nonLockingRedisCacheWriter(
            template.getConnectionFactory());
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofHours(1));

        ExtendedCacheManager manager = new ExtendedCacheManager(writer, config, template);
        return manager;
    }
}

4. 使用例

カスタム実装により、リスト形式のキー削除が可能になります。

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @CacheEvict(key = "@keyGenerator.generateKeys(#users)")
    public void batchUpdate(List<User> users) {
        users.forEach(userMapper::update);
    }
}

@Component
public class KeyGenerator {
    public Collection<String> generateKeys(List<User> users) {
        return users.stream()
            .map(u -> u.getId())
            .collect(Collectors.toList());
    }
}

このように、カスタム実装により柔軟なキャッシュ無効化戦略を実現できます。

タグ: Spring Cache redis Java Spring Boot キャッシュ戦略

6月9日 15:59 投稿