Améliorer son cache Spring Boot avec Caffeine

Dans un article précédent, nous avons vu comment mettre en place un cache en mémoire simple avec Spring Boot en utilisant uniquement le ConcurrentMapCacheManager.
Cette approche est idéale pour comprendre le mécanisme et tester rapidement les annotations de Spring (@Cacheable@CachePut@CacheEvict).

Cependant, dans une application réelle, ce cache présente rapidement des limites :

Pour répondre à ces besoins, Spring Boot permet d’intégrer facilement une solution plus avancée : Caffeine.

Pourquoi Caffeine ?

Caffeine est une bibliothèque Java moderne et performante de gestion de cache (en plus, c’est open source). Elle apporte des fonctionnalités essentielles absentes du cache en mémoire basique :

[

GitHub - ben-manes/caffeine: A high performance caching library for Java

A high performance caching library for Java. Contribute to ben-manes/caffeine development by creating an account on GitHub.

GitHubben-manes

](https://github.com/ben-manes/caffeine)

La stratégie LRU : “Least Recently Used”

Quand le cache atteint sa capacité maximale, il doit choisir quel élément supprimer pour faire de la place.

Exemple illustré

Supposons que notre cache a une capacité de 3 livres.

Mise en place de Caffeine dans votre application

Dépendances Maven

Dans votre fichier pom.xml, ajoutez les dépendances suivantes :

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

Configuration

@Configuration
public class CaffeineCacheConfig {

    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("books");
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES) // expiration 10 min
                .maximumSize(500)); // limite de 500 entrées
        return cacheManager;
    }
}

Ici, nous définissons :

Service avec cache

Bonne nouvelle : la logique métier ne change pas.
On continue d’utiliser les mêmes annotations Spring (@Cacheable@CachePut@CacheEvict).

@Service
public class BookService {

    private static final Logger LOG = LoggerFactory.getLogger(BookService.class);

    private final BookRepository bookRepository;

    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @Cacheable(value = "books", key = "#isbn")
    public Book findBookByIsbn(String isbn) {
        long start = System.currentTimeMillis();

        simulateSlowService(); // simule un traitement lourd
        Book book = bookRepository.findByIsbn(isbn);

        long end = System.currentTimeMillis();
        LOG.info("findBookByIsbn({}) exécuté en {} ms", isbn, (end - start));
        LOG.info("Book : {}", book);
        return book;
    }

    @CachePut(value = "books", key = "#book.isbn")
    public Book saveOrUpdateBook(Book book) {
        LOG.info("Mise à jour / ajout du livre [{}] en base et dans le cache", book.getIsbn());
        return bookRepository.save(book);
    }

    @CacheEvict(value = "books", key = "#isbn")
    public void deleteBookByIsbn(String isbn) {
        Optional<Book> book = Optional.ofNullable(bookRepository.findByIsbn(isbn));
        book.ifPresent(b -> {
            bookRepository.delete(b);
            LOG.info("Suppression du livre [{}] en base et invalidation du cache", isbn);
        });
    }

    private void simulateSlowService() {
        try {
            Thread.sleep(3000L); // pause artificielle
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IllegalStateException(e);
        }
    }
}

C’est uniquement le CacheManager qui change, pas le code applicatif.

Comparaison : ConcurrentMap vs Caffeine

Critère ConcurrentMapCacheManager CaffeineCacheManager
Expiration auto (TTL) ❌ non géré ✅ configurable
Taille max ❌ illimitée ✅ configurable
Stratégie d’éviction ❌ aucune ✅ LRU
Performances Basique Optimisé

Conclusion

Caffeine représente ainsi une évolution naturelle vers une solution plus robuste, tout en restant simple à intégrer dans un projet Spring Boot.