Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Integrating Redis with Spring Boot Applications

Notes May 12 3

Redis Cleints in Java

In Java applications, interacting with Redis requires a Redis client library, similar to using JDBC for MySQL operations. Several reliable Java clients are available:

  • Jedis
  • Lettuce
  • Spring Data Redis

Spring provides comprehensive integration through Spring Data Redis, and Spring Boot offers a convenient starter dependency (spring-boot-starter-data-redis) for rapid setup.

Overview of Spring Data Redis

Spring Data Redis serves as the Spring framework's solution for Redis integration, offering streamlined configuration and abstracted access to Redis services while encapsulating the underlying client libraries. This component significantly simplifies Redis operations within Spring applications.

The Maven dependency for Spring Boot projects:

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

Spring Data Redis provides the RedisTemplate class, a highly abstracted wrapper that organizes related operations into operation interfaces:

  • ValueOperations: String data operations
  • SetOperations: Set data operations
  • ZSetOperations: Sorted set operations
  • HashOperations: Hash data operations
  • ListOperations: List data operations

Project Configuration

Step 1: Add Maven Dependency

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

Step 2: Configure Redis Connection

Add the following configuration to application-dev.yml:

spring:
  redis:
    host: localhost
    port: 6379
    password: 123456
    database: 0

Note: The database parameter specifies which of the 16 default Redis databases (numbered 0-15) to use. This can be modified in the Redis configuration file.

Reference the Redis configuration in application.yml:

spring:
  profiles:
    active: dev
  redis:
    host: ${spring.redis.host}
    port: ${spring.redis.port}
    password: ${spring.redis.password}
    database: ${spring.redis.database}

Step 3: Create RedisTemplate Configuration

package com.example.config;

@Configuration
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        return template;
    }
}

Note: While Spring Boot auto-configures a RedisTemplate by default, the default key serializer (JdkSerializationRedisSerializer) produces non-human-readable keys. Using StringRedisSerializer provides cleaner, more manageable keys in Redis clients.

Step 4: Testing RedisTemplate Operations

package com.example.tests;

@SpringBootTest
public class RedisOperationsTest {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Test
    void verifyRedisTemplateInitialization() {
        ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();
        HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
        ListOperations<String, Object> listOps = redisTemplate.opsForList();
        SetOperations<String, Object> setOps = redisTemplate.opsForSet();
        ZSetOperations<String, Object> zsetOps = redisTemplate.opsForZSet();
    }
}

Working with Data Types

String Operations

@Test
void stringDataOperations() {
    redisTemplate.opsForValue().set("username", "alice");
    String retrieved = (String) redisTemplate.opsForValue().get("username");
    
    redisTemplate.opsForValue().set("verification_code", "9876", 5, TimeUnit.MINUTES);
    
    redisTemplate.opsForValue().setIfAbsent("distributed_lock", "holder1");
    redisTemplate.opsForValue().setIfAbsent("distributed_lock", "holder2");
}

Hash Operations

@Test
void hashDataOperations() {
    HashOperations<String, String, Object> hashOps = redisTemplate.opsForHash();
    
    hashOps.put("user:200", "fullname", "bob");
    hashOps.put("user:200", "age", "25");
    
    Object name = hashOps.get("user:200", "fullname");
    
    Set<String> fieldNames = hashOps.keys("user:200");
    List<Object> fieldValues = hashOps.values("user:200");
    
    hashOps.delete("user:200", "age");
}

List Operations

@Test
void listDataOperations() {
    ListOperations<String, Object> listOps = redisTemplate.opsForList();
    
    listOps.leftPushAll("task_queue", "job1", "job2", "job3");
    listOps.leftPush("task_queue", "urgent_job");
    
    List<Object> tasks = listOps.range("task_queue", 0, -1);
    
    listOps.rightPop("task_queue");
    
    Long queueSize = listOps.size("task_queue");
}

Set Operations

@Test
void setDataOperations() {
    SetOperations<String, Object> setOps = redisTemplate.opsForSet();
    
    setOps.add("category:a", "item1", "item2", "item3");
    setOps.add("category:b", "item2", "item3", "item4");
    
    Set<Object> members = setOps.members("category:a");
    
    Long count = setOps.size("category:a");
    
    Set<Object> intersection = setOps.intersect("category:a", "category:b");
    Set<Object> union = setOps.union("category:a", "category:b");
    
    setOps.remove("category:a", "item1", "item2");
}

Sorted Set Operations

@Test
void sortedSetOperations() {
    ZSetOperations<String, Object> zsetOps = redisTemplate.opsForZSet();
    
    zsetOps.add("leaderboard", "player1", 1500);
    zsetOps.add("leaderboard", "player2", 1800);
    zsetOps.add("leaderboard", "player3", 1200);
    
    Set<Object> rankings = zsetOps.range("leaderboard", 0, -1);
    
    zsetOps.incrementScore("leaderboard", "player3", 500);
    
    zsetOps.remove("leaderboard", "player1", "player2");
}

Generic Commands

@Test
void genericOperations() {
    Set<String> allKeys = redisTemplate.keys("*");
    
    Boolean hasUserKey = redisTemplate.hasKey("user:200");
    Boolean hasTaskKey = redisTemplate.hasKey("task_queue");
    
    for (String key : allKeys) {
        DataType type = redisTemplate.type(key);
    }
    
    redisTemplate.delete("task_queue");
}

Annotation-Based Caching

For straightforward caching scenarios, Spring provides annotation-driven Redis support.

Setup Requirements:

  1. Include spring-boot-starter-datta-redis in pom.xml
  2. Configure Redis connection and cache settings
  3. Ensure cached entities implement Serializable
  4. Apply either annotation-based or programmatic caching via RedisTemplate
  5. Add @EnableCaching to the application main class

@Cacheable Annotation

Can be applied to methods or classes. When applied to a method, results are cached after execution. When applied to a class, all methods support caching. Subsequent calls with identical parameters retrieve cached results without re-executing the method.

Cache entries are stored as key-value pairs, where the key can be auto-generated or custom-defined. Note that internal method calls bypass the cache mechanism.

The value attribute specifies the cache name(s) where results are stored:

@Cacheable(value = "productCache", key = "#productId")
public Product getProductDetails(Long productId) {
    return productRepository.findById(productId).orElse(null);
}

@CacheEvict Annotation

Used to invalidate cached entries. Applied to methods or classes that modify data, triggering cache cleanup after execution.

allEntries attribute: When set to true, clears all entries in the specified cache rather than individual keys:

@CacheEvict(value = "productCache", allEntries = true)
public void clearProductCatalog() {
    productService.deleteAllProducts();
}

beforeInvocation attribute: Controls when cache eviction occurs. By default, eviction happens after successful method execution (meaning no eviction if an exception occurs). Setting this to true triggers eviction before the method executes:

@CacheEvict(value = "productCache", beforeInvocation = true)
public void removeProduct(Long productId) {
    productService.delete(productId);
}

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.