アノテーションによるキャッシュ制御の導入方法
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 は、デフォルトで単一の文字列キーのみをサポートしており、複数キーの一括削除やワイルドカードによる削除はできません。これを解決するため、RedisCache と RedisCacheManager を拡張します。
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());
}
}
このように、カスタム実装により柔軟なキャッシュ無効化戦略を実現できます。