Redis 完全指南 / 18 - Spring Data Redis
Spring Data Redis
18.1 Spring Data Redis 概述
Spring Data Redis 是 Spring 官方提供的 Redis 集成框架,封装了底层的 Redis 客户端操作。
支持的客户端
| 客户端 | 特点 | 状态 |
|---|
| Lettuce | 基于 Netty,线程安全,支持响应式 | 默认(推荐) |
| Jedis | 简单直觉,连接池管理 | 可选 |
Maven 依赖
<dependencies>
<!-- Spring Boot Starter Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池(Lettuce 默认支持,Jedis 需要额外引入) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- JSON 序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
18.2 基础配置
application.yml
spring:
data:
redis:
host: localhost
port: 6379
password: yourpassword
database: 0
timeout: 5000ms
# Lettuce 连接池配置
lettuce:
pool:
max-active: 100 # 最大连接数
max-idle: 20 # 最大空闲连接
min-idle: 5 # 最小空闲连接
max-wait: 3000ms # 获取连接最大等待时间
shutdown-timeout: 200ms
# Sentinel 配置
# sentinel:
# master: mymaster
# nodes:
# - 192.168.1.1:26379
# - 192.168.1.2:26379
# - 192.168.1.3:26379
# Cluster 配置
# cluster:
# nodes:
# - 192.168.1.1:6379
# - 192.168.1.2:6379
# - 192.168.1.3:6379
# max-redirects: 3
Redis 配置类
package com.example.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// Key 序列化
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// Value 序列化
GenericJackson2JsonRedisSerializer jsonSerializer = createJsonSerializer();
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
private GenericJackson2JsonRedisSerializer createJsonSerializer() {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
);
mapper.registerModule(new JavaTimeModule());
return new GenericJackson2JsonRedisSerializer(mapper);
}
}
18.3 RedisTemplate 基本操作
注入和使用
package com.example.service;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Service
public class RedisService {
@Resource
private RedisTemplate<String, Object> redisTemplate;
// ========== String 操作 ==========
/**
* 设置值
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 设置值并指定过期时间
*/
public void set(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
/**
* 不存在才设置(分布式锁基础)
*/
public Boolean setIfAbsent(String key, Object value, long timeout, TimeUnit unit) {
return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
}
/**
* 获取值
*/
@SuppressWarnings("unchecked")
public <T> T get(String key) {
return (T) redisTemplate.opsForValue().get(key);
}
/**
* 自增
*/
public Long increment(String key) {
return redisTemplate.opsForValue().increment(key);
}
public Long increment(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
// ========== Hash 操作 ==========
public void hSet(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
public Object hGet(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
public Long hIncrement(String key, String field, long delta) {
return redisTemplate.opsForHash().increment(key, field, delta);
}
public void hDelete(String key, Object... fields) {
redisTemplate.opsForHash().delete(key, fields);
}
// ========== List 操作 ==========
public Long lPush(String key, Object value) {
return redisTemplate.opsForList().leftPush(key, value);
}
public Long rPush(String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}
public Object lPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
public Object rPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}
public List<Object> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
public Long lLen(String key) {
return redisTemplate.opsForList().size(key);
}
// ========== Set 操作 ==========
public Long sAdd(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}
public Set<Object> sMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
public Long sSize(String key) {
return redisTemplate.opsForSet().size(key);
}
// ========== ZSet 操作 ==========
public Boolean zAdd(String key, Object value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
public Set<Object> zRange(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
public Set<ZSetOperations.TypedTuple<Object>> zRangeWithScores(String key, long start, long end) {
return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
}
public Long zRank(String key, Object value) {
return redisTemplate.opsForZSet().rank(key, value);
}
public Double zIncrementScore(String key, Object value, double delta) {
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
// ========== 通用操作 ==========
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
public Long delete(Collection<String> keys) {
return redisTemplate.delete(keys);
}
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
public Long getExpire(String key) {
return redisTemplate.getExpire(key);
}
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
// ========== Pipeline 批量操作 ==========
public List<Object> batchGet(List<String> keys) {
return redisTemplate.executePipelined(connection -> {
for (String key : keys) {
connection.stringCommands().get(key.getBytes());
}
return null;
});
}
// ========== Lua 脚本执行 ==========
public Object executeScript(String script, List<String> keys, Object... args) {
return redisTemplate.execute(
new org.springframework.data.redis.core.script.DefaultRedisScript<>(script, Long.class),
keys,
args
);
}
}
18.4 缓存注解
启用缓存
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withCacheConfiguration("user",
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)))
.withCacheConfiguration("product",
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)))
.transactionAware()
.build();
}
}
缓存注解使用
package com.example.service;
import com.example.entity.User;
import com.example.repository.UserRepository;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
@Service
@CacheConfig(cacheNames = "user")
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
/**
* 缓存查询结果
*/
@Cacheable(key = "#userId")
public User getUserById(Long userId) {
System.out.println("Querying database for user: " + userId);
return userRepository.findById(userId).orElse(null);
}
/**
* 条件缓存(只缓存活跃用户)
*/
@Cacheable(key = "#userId", condition = "#result != null && #result.active")
public User getActiveUser(Long userId) {
return userRepository.findById(userId).orElse(null);
}
/**
* 缓存结果为空时不缓存
*/
@Cacheable(key = "#userId", unless = "#result == null")
public User getUserOrNull(Long userId) {
return userRepository.findById(userId).orElse(null);
}
/**
* 更新时清除缓存
*/
@CachePut(key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
/**
* 删除时清除缓存
*/
@CacheEvict(key = "#userId")
public void deleteUser(Long userId) {
userRepository.deleteById(userId);
}
/**
* 清除所有用户缓存
*/
@CacheEvict(allEntries = true)
public void clearAllCache() {
System.out.println("All user cache cleared");
}
/**
* 先更新数据库,再删除缓存
*/
@Caching(evict = {
@CacheEvict(key = "#user.id"),
@CacheEvict(key = "#user.email")
})
public User updateAndEvict(User user) {
return userRepository.save(user);
}
}
注解参数说明
| 注解 | 说明 | 常用参数 |
|---|
@Cacheable | 查询时缓存结果 | key, condition, unless, cacheNames |
@CachePut | 更新缓存 | key, condition, unless |
@CacheEvict | 删除缓存 | key, allEntries, beforeInvocation |
@Caching | 组合多个注解 | cacheable, put, evict |
@CacheConfig | 类级别缓存配置 | cacheNames, keyGenerator |
SpEL 表达式
// 常用 SpEL 表达式
@Cacheable(key = "#id") // 方法参数
@Cacheable(key = "#user.id") // 对象属性
@Cacheable(key = "#root.methodName") // 方法名
@Cacheable(key = "#root.args[0]") // 第一个参数
@Cacheable(key = "#root.target") // 目标对象
@Cacheable(key = "T(java.lang.String).valueOf(#id)") // 静态方法调用
@Cacheable(key = "'user:' + #id") // 字符串拼接
18.5 分布式锁实现
package com.example.lock;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
public class RedisDistributedLock {
private final StringRedisTemplate redisTemplate;
private static final String LOCK_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT =
new DefaultRedisScript<>(LOCK_SCRIPT, Long.class);
public RedisDistributedLock(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 加锁
*/
public String tryLock(String lockKey, long timeout, TimeUnit unit) {
String lockValue = UUID.randomUUID().toString();
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, timeout, unit);
return Boolean.TRUE.equals(success) ? lockValue : null;
}
/**
* 解锁(Lua 脚本保证原子性)
*/
public boolean unlock(String lockKey, String lockValue) {
Long result = redisTemplate.execute(
UNLOCK_SCRIPT,
Collections.singletonList(lockKey),
lockValue
);
return result != null && result > 0;
}
}
// 使用示例
@Service
public class OrderService {
@Autowired
private RedisDistributedLock distributedLock;
public void createOrder(Long userId, Long productId) {
String lockKey = "lock:order:create:" + userId;
String lockValue = distributedLock.tryLock(lockKey, 30, TimeUnit.SECONDS);
if (lockValue == null) {
throw new RuntimeException("系统繁忙,请稍后重试");
}
try {
// 业务逻辑
processOrder(userId, productId);
} finally {
distributedLock.unlock(lockKey, lockValue);
}
}
}
📌 业务场景
场景一:用户信息缓存
@Cacheable(key = "'user:' + #userId", cacheNames = "user", unless = "#result == null")
public User getUserById(Long userId) {
return userRepository.findById(userId).orElse(null);
}
场景二:排行榜
public List<RankEntry> getTopRankings(String rankingKey, int topN) {
Set<ZSetOperations.TypedTuple<Object>> tuples =
redisTemplate.opsForZSet().reverseRangeWithScores(rankingKey, 0, topN - 1);
List<RankEntry> rankings = new ArrayList<>();
int rank = 1;
for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
rankings.add(new RankEntry(rank++, tuple.getValue(), tuple.getScore()));
}
return rankings;
}
场景三:秒杀库存扣减
private static final String STOCK_SCRIPT =
"local stock = tonumber(redis.call('GET', KEYS[1]) or '0') " +
"if stock > 0 then " +
" redis.call('DECR', KEYS[1]) " +
" return 1 " +
"end " +
"return 0";
public boolean deductStock(String skuId) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>(STOCK_SCRIPT, Long.class);
Long result = redisTemplate.execute(script,
Collections.singletonList("stock:" + skuId));
return result != null && result > 0;
}
🔗 扩展阅读