Dernière modification : 02/07/2025

Redis - Introduction et mise en cache avec Spring Boot et Java

 

Dans cet article, nous allons découvrir Redis et apprendre à l’utiliser dans une application Spring Boot pour mettre en cache des données. Nous aborderons les concepts de base de Redis, son intégration dans un projet Java / Spring, une mise en œuvre du cache via l’annotation @Cacheable, puis nous évoquerons les avantages et inconvénients de cette technologie.

Technologies utilisées : Spring Boot (Spring Data Redis), Java > 17, Docker, Redis (serveur 7.x)

 
1. Introduction

Qu’est-ce que Redis ?

Redis est une base de données NoSQL de type clé-valeur fonctionnant entièrement en mémoire. Il s’agit d’un projet open source réputé pour sa grande rapidité d’exécution. Concrètement, Redis stocke les données en RAM plutôt que sur un disque par défaut, ce qui permet d'avoir des performances d’accès en lecture/écriture élevées. Ses principaux cas d’utilisation incluent la mise en cache de données, la gestion de sessions, le système de messagerie pub / sub (publication / souscription).

 

Pourquoi utiliser Redis ?

Une utilisation courante de Redis est d’agir comme cache afin d’accélérer les applications. Lorsqu’une application interroge une base de données classique pour récupérer des informations fréquemment demandées, cela peut engendrer de la latence et une charge importante sur la base de données. En insérant Redis entre l’application et la base de données, on stocke en mémoire les données fréquemment consultées pour les servir plus rapidement.

 
2. Installation de Redis et intégration dans Spring Boot

Pour utiliser Redis dans notre application, nous avons besoin d’installer ou de lancer un serveur Redis, puis de configurer notre projet Spring Boot afin qu’il puisse communiquer avec ce serveur.

Lancer un serveur Redis localement : La méthode la plus simple pour démarrer Redis en local consiste à utiliser Docker. Si Docker est installé sur votre machine, exécutez la commande suivante dans un terminal :

# Démarrer un serveur Redis (port par défaut 6379)
docker run -p 6379:6379 -it --rm redis/redis-stack-server

Cette commande télécharge et lance l’image officielle de Redis en exposant le port par défaut 6379. Une fois le conteneur démarré, un serveur Redis tourne en arrière-plan en local.

Remarque : Si vous ne souhaitez pas utiliser Docker, vous pouvez installer Redis directement sur votre système via les binaires disponibles sur le site officiel.

 

Dépendances Maven/Gradle : Côté application Spring Boot, il est nécessaire d'ajouter la dépendance Spring Data Redis. Modifier comme suit le fichier pom.xml :

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

Cette dépendance apporte le nécessaire pour communiquer avec Redis (client Lettuce par défaut, template Spring, etc.).

 

Activation du cache dans Spring Boot : Par défaut, l’annotation de cache (@Cacheable) n’est pas active tant qu’elle n'est pas explicitement demandée. Pour cela, ajouter l’annotation @EnableCaching. Par exemple :

@Configuration
@EnableCaching
public class RedisConfig {

}

En ajoutant @EnableCaching, Spring Boot va automatiquement configurer un gestionnaire de cache (CacheManager) associé à Redis, grâce à la présence de la dépendance Spring Data Redis sur le classpath. Autrement dit, toutes les annotations de cache que nous utiliserons seront prises en charge et stockeront les données dans Redis (plutôt que dans une cache en mémoire locale ou autre).

 

3. Configuration du cache avec Redis

Ci-dessous un exemple de configuration du cache avec Redis en modifiant la configuration par défaut.

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    RedisConnectionFactory jedisConnectionFactory() {
        final RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName("localhost");
        redisStandaloneConfiguration.setPort(6379);
        return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }

    @Bean
    public RedisCacheManager cacheManager(final RedisConnectionFactory redisConnectionFactory) {
        final RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(30))
                .disableCachingNullValues()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(cacheConfiguration)
                .build();
    }
}

On crée ici tout d'abord une connexion avec Redis via RedisConnectionFactory, en utilisant le client Lettuce, qui est non-bloquant et adapté aux environnements modernes. La connexion se fait vers un Redis local, sur le port 6379.

Ensuite, on définit la gestion du cache à l’aide d’un RedisCacheManager. Celui-ci repose sur une configuration par défaut qui applique une durée de vie (TTL) de 30 secondes à chaque entrée (entryTtl). On désactive également la mise en cache des valeurs nulles (disableCachingNullValues), ce qui permet d’éviter des accès inutiles à Redis pour des résultats vides. Enfin, on précise comment les valeurs sont (dé)sérialisées : ici via GenericJackson2JsonRedisSerializer, qui transforme les objets Java en JSON, facilitant leur lecture et leur compatibilité.

Grâce à cette configuration, il devient très simple d’utiliser le cache en annotant directement vos méthodes avec @Cacheable, sans gérer manuellement la logique de lecture/écriture dans Redis.


4. Mise en cache de données avec Redis (@Cacheable)

L’un des usages les plus puissants de Redis est de servir de cache distribué pour votre application. Spring Boot intègre un mécanisme de cache unifié qui permet de cacher les résultats de certaines fonctions, sans écrire explicitement du code de stockage/recherche dans Redis à chaque appel. Ce mécanisme s’appuie sur les annotations telles que @Cacheable, @CacheEvict, @CachePut, etc.

Nous allons illustrer l’utilisation de @Cacheable avec Redis à travers un exemple. Imaginons une application qui gère des utilisateurs via une classe UserService. Sans cache, chaque appel à UserService.getUserById(id) irait interroger une base de données (ou une source de données lente) pour récupérer l’utilisateur correspondant. Avec le cache, nous voulons que la première requête charge l’utilisateur depuis la base puis le stocke dans Redis, et que les appels suivants récupèrent directement l’utilisateur depuis Redis sans interroger la base.

Voici comment on peut procéder :

@Service
public class UserService {

    @Autowired 
    private UserRepository userRepository;

    /** Récupère un utilisateur par son ID, en le mettant en cache Redis. */
    @Cacheable(value = "user", key = "'user:' + #id", unless = "#result == null")
    public UserModel getUserById(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("User not found"));
    }

    /** Récupère la liste des utilisateurs, avec mise en cache. */
    @Cacheable(value = "userItems")
    public List<UserModel> listAllUsers() {
        return userRepository.findAll();
    }
}

Dans ce code :

  • La méthode getUserById est annotée avec @Cacheable.
    • Le paramètre value = "user" indique le nom du cache (on peut en avoir plusieurs, ici on en définit un nommé "user").
    • Le paramètre key = "'user:' + #id" spécifie la clé sous laquelle stocker les données.
    • Concrètement, pour un appel getUserById(2), la clé sera la chaîne "user:2" dans le cache "user". Spring concaténera le nom du cache et la clé pour former une clé unique dans Redis (ce qui donnera une entrée avec une clé Redis du style user::user:2).
    • Si on n’avait pas précisé de clé, Spring aurait utilisé la valeur de l’ID directement, mais ici nous ajoutons un préfixe "user:" par clarté.
    • La première fois qu’on appelle getUserById(2), la méthode exécutera userRepository.findById(2) (imaginez que cela interroge la base de données) puis stockera le résultat dans Redis. Les appels suivants de getUserById(2) court-circuiteront la méthode : Spring détectera que la valeur est déjà en cache et la renverra directement, sans exécuter userRepository.findById à nouveau.
  • La méthode listAllUsers est également annotée avec @Cacheable.
    • Ici le nom du cache est "userItems". Nous n’avons pas spécifié de clé (key), ce qui signifie que Spring utilisera une clé par défaut basée sur les arguments de la méthode.
    • Étant donné que listAllUsers() n’a pas de paramètre, la clé par défaut sera un objet de type SimpleKey représentant l’absence de paramètres.
    • Dans Redis, la clé réelle sera donc userItems::SimpleKey [] (Spring formate ainsi la clé pour indiquer un appel sans arguments).
    • La première invocation de listAllUsers() stockera la liste retournée en cache, et les suivantes récupéreront cette liste depuis Redis tant que le cache n’aura pas été invalidé.

 

Vérification du cache dans Redis :

Une fois ces caches en place, on peut exécuter l’application et appeler quelques fois les méthodes getUserById et listAllUsers. La première invocation sera plus lente (accès aux données réelles) et les suivantes plus rapides (données retournées depuis la RAM de Redis). Pour voir les entrées créées dans Redis, on peut utiliser l’interface en ligne de commande redis-cli. Par exemple, après avoir appelé listAllUsers() et getUserById(2), ouvrez un terminal et lancez :

$ docker run -it --network host --rm redis redis-cli -h localhost -p 6379
127.0.0.1:6379> KEYS *
1) "userItems::SimpleKey []"
2) "user::user:2"

On constate que deux clés sont présentes : userItems::SimpleKey [] et user::user:2. Ces clés correspondent respectivement au résultat mis en cache de listAllUsers() et de getUserById(2). On peut inspecter leur contenu avec la commande GET :

127.0.0.1:6379> TYPE "userItems::SimpleKey []"
string

127.0.0.1:6379> GET "userItems::SimpleKey []"
"[\"java.util.ArrayList\",[{\"@class\":\"com.laulem.redis.example.model.UserModel\",\"id\":2,\"firstName\":\"Alexandre\",\"lastName\":\"Lemaire\"}]]"

127.0.0.1:6379> GET "user::user:2"
"{\"@class\":\"com.laulem.redis.example.model.UserModel\",\"id\":2,\"firstName\":\"Alexandre\",\"lastName\":\"Lemaire\"}"

On observe que chaque entrée est stockée en tant que string (chaîne de caractères). Le contenu est du JSON incluant le nom de la classe Java (UserModel) et les champs de l’objet. Spring Data Redis a ici utilisé un sérialiseur JSON pour sauvegarder nos objets de manière lisible.

 

En utilisant @Cacheable, nous avons ajouté une couche de cache transparente pour l’appelant : il n’a pas besoin de savoir si la donnée vient directement de la base ou de Redis. Spring se charge de la logistique, et Redis agit comme un tampon ultra-rapide. Lorsque les données changent dans la base, il peut être nécessaire d’invalider le cache pour éviter de servir des informations obsolètes : c’est le rôle de l’annotation @CacheEvict ou de mécanismes de TTL (expiration automatique des entrées).

Prenons l’exemple suivant dans la classe UserService :

public class UserService {
....

    @Caching(evict = {
            @CacheEvict(value = "userItems", allEntries = true),
            @CacheEvict(value = "user", key = "'user:' + #id")
    })
    public void delete(final Long id) {
        userRepository.deleteById(id);
    }
}

Dans ce code, la méthode delete(Long id) supprime un utilisateur en base. Elle est annotée avec @Caching, qui permet de combiner plusieurs évictions de cache en une seule opération. On y trouve deux @CacheEvict :

  • La première (value = "userItems", allEntries = true) vide complètement le cache userItems, c’est-à-dire la liste complète des utilisateurs. Cela garantit que lors du prochain appel à listAllUsers(), la base de données sera consultée à nouveau, reflétant la suppression récente.
  • La seconde (value = "user", key = "'user:' + #id") évince l’entrée correspondant à l’utilisateur supprimé, en ciblant spécifiquement la clé "user::user:{id}".

Ce mécanisme d’éviction manuelle permet donc de synchroniser le cache avec les opérations de modification ou suppression des données. Il est également possible d’utiliser @CachePut pour forcer la mise à jour du cache après une modification.

Grâce à @CacheEvict, le cache reste cohérent avec la source de vérité (la base de données), tout en continuant à profiter de Redis pour accélérer les accès en lecture.


5. Avantages et inconvénients de Redis

Nous avons vu que Redis pouvait accélérer une application grâce au cache. Cependant, il présente des points forts et des limites.

Avantages de Redis :

  • Très rapide : toutes les données sont en mémoire, ce qui permet des réponses quasi instantanées.
  • Scalable : Redis peut facilement s’étendre sur plusieurs serveurs grâce au clustering et à la réplication.
  • Simple et universel : son modèle clé-valeur est facile à comprendre, et il existe des clients pour presque tous les langages.
  • Cache partagé : Redis peut être utilisé par plusieurs instances de l’application (ex : en microservices ou derrière un load balancer), ce qui permet de mutualiser le cache entre plusieurs nœuds et garantir une cohérence globale des données mises en cache.

Inconvénients de Redis :

  • Données volatiles : sans persistance activée, un crash du serveur peut entraîner une perte des dernières données.
  • Pas de requêtes complexes : on ne peut accéder aux données que par leur clé, il n’y a pas de langage de requête comme SQL.
  • Gestion des mises à jour coûteuse : Redis ne "sait pas" quand une entité a changé en base. C’est à l’application de gérer explicitement la mise à jour ou l’invalidation du cache (ex : via @CacheEvict, @CachePut ou manuellement) pour garder une cohérence entre toutes les données.
  • Débogage complexe : le cache peut masquer les vraies sources de données, ce qui rend l’analyse des bugs plus difficile.

 

6. Conclusion

Dans cet article, nous avons exploré Redis et son utilisation en tant que cache dans une application Spring Boot. Nous avons vu comment installer et configurer Redis, puis utiliser les annotations Spring pour mettre en place un mécanisme de cache efficace sans complexité excessive. Nous avons également exploré quelques avantages et inconvénients quant à son utilisation.

Redis est un composant incontournable pour optimiser les performances d’une application. Qu’il soit utilisé en cache simple ou dans des scénarios plus élaborés (systèmes de messagerie en temps réel, traitement de flux, etc.).
 

7. Ressources / Pour aller plus loin

 

À noter que cet article, pour des besoins de test, a été en partie rédigé par une IA et presque entièrement reformulé, complété et vérifié par un humain.