Integrating Redis with Spring Boot Applications
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:
- Include spring-boot-starter-datta-redis in pom.xml
- Configure Redis connection and cache settings
- Ensure cached entities implement
Serializable - Apply either annotation-based or programmatic caching via RedisTemplate
- Add
@EnableCachingto 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);
}