Skip to content

Caching

Since Spring 6.1, the caching abstraction supports reactive return types: a @Cacheable method may return Mono or Flux, and the cache stores the completion rather than the publisher. The repository ships three samples that differ only in the CacheManager: an in-memory ConcurrentHashMap, Caffeine, and Redis. All three combine the cache with a Spring Data R2DBC backed PostRepository on PostgreSQL.

Step 1: enable caching

@Configuration
@EnableCaching
public class CacheConfig implements CachingConfigurer {

    @Bean
    @Override
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager();
    }

    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new SimpleKeyGenerator();
    }

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        return new SimpleCacheErrorHandler();
    }
}

Step 2: annotate the reactive repository

Apply the caching annotations to the repository methods — the reactive signatures work exactly like the blocking ones:

public interface PostRepository extends R2dbcRepository<Post, UUID> {
    @Override
    @Cacheable(value = "posts", key = "#p0")
    Mono<Post> findById(UUID id);

    @Override
    @CachePut(value = "posts", key = "#result.id")
    Mono<Post> save(Post post);

    @Override
    @CacheEvict(value = "posts", key = "#p0")
    Mono<Void> deleteById(UUID id);

    @Override
    @CacheEvict(value = "posts", allEntries = true)
    Mono<Void> deleteAll();
}

Step 3: choose the CacheManager

Caffeine (async cache mode)

Caffeine supports native async caches — enable them with setAsyncCacheMode(true):

@Bean
@Override
public CacheManager cacheManager() {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    cacheManager.setAsyncCacheMode(true);
    return cacheManager;
}

Add the dependency:

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

Redis

RedisCacheManager moves the cache across process boundaries. Declare the Redis connection factory and build the manager from it:

@Configuration
@EnableCaching
public class CacheConfig implements CachingConfigurer {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    @Override
    public CacheManager cacheManager() {
        return RedisCacheManager.builder().fromConnectionFactory(redisConnectionFactory)
                .build();
    }
}

Add the dependencies:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
</dependency>

Note

If both cacheManager and cacheResolver are set, the cache manager is ignored — configure one or the other.

Verify

The sample test performs the same findById twice and observes the cache interceptor in the logs (the org.springframework.cache category is set to TRACE in logback.xml): the second call hits the cache instead of the database, and the eviction annotations clear the entries on save/delete.

To run the application, start PostgreSQL (and Redis for the Redis variant):

docker-compose up postgres redis

You can explore the complete codes in cache, cache-caffeine, and cache-redis.