Spring Data Redis 集成
1. 核心组件概览
Section titled “1. 核心组件概览”Spring Data Redis 是 Spring 对 Redis 客户端的统一封装,提供模板类操作 Redis,屏蔽底层客户端差异。
SpringBoot 应用 │ ▼Spring Data Redis │ ├── RedisTemplate<K, V> 通用模板,支持任意类型 └── StringRedisTemplate RedisTemplate<String, String> 的简化版 │ ▼ RedisConnectionFactory(连接工厂) │ ├── LettuceConnectionFactory 默认,基于 Netty,支持异步/响应式 └── JedisConnectionFactory 老牌同步客户端| 对比项 | Lettuce(默认) | Jedis |
|---|---|---|
| 线程模型 | 多线程共享单连接(Netty) | 每个线程独占连接 |
| 连接池 | 可选(推荐开启) | 必须配置连接池 |
| 异步支持 | ✅ 原生支持 | ❌ 不支持 |
| 响应式支持 | ✅ 支持 | ❌ 不支持 |
| 推荐程度 | ✅ 生产推荐 | 仅兼容旧项目 |
2. 依赖与配置
Section titled “2. 依赖与配置”2.1. 添加依赖
Section titled “2.1. 添加依赖”<!-- Spring Data Redis(内置 Lettuce 客户端) --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>
<!-- 连接池支持(Lettuce 需要 commons-pool2) --><dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId></dependency>2.2. application.yml 完整配置
Section titled “2.2. application.yml 完整配置”spring: data: redis: host: localhost port: 6379 password: 123456 database: 0 # 数据库编号,默认 0(共 16 个,0~15) connect-timeout: 3000ms # 连接超时 timeout: 3000ms # 命令执行超时
# Lettuce 连接池(推荐开启) lettuce: pool: max-active: 8 # 最大连接数(-1 表示无限制) max-idle: 8 # 最大空闲连接 min-idle: 1 # 最小空闲连接(保持预热) max-wait: 200ms # 获取连接的最大等待时间,超时抛出异常3. RedisTemplate vs StringRedisTemplate
Section titled “3. RedisTemplate vs StringRedisTemplate”3.1. 核心区别
Section titled “3.1. 核心区别”| 对比项 | RedisTemplate | StringRedisTemplate |
|---|---|---|
| 泛型 | <K, V> 可自定义 | <String, String> |
| 默认序列化 | JDK 序列化(❌ 不推荐) | String 序列化 |
| 适用场景 | 存储复杂 Java 对象 | 存储字符串(JSON 字符串) |
| 配置复杂度 | 需自定义序列化配置 | 开箱即用 |
3.2. RedisTemplate 默认序列化的问题
Section titled “3.2. RedisTemplate 默认序列化的问题”// 使用默认 RedisTemplate 存储对象redisTemplate.opsForValue().set("user:1", new User("Alice", 25));
// Redis 中实际存储的 key 和 value 都是 JDK 序列化的乱码:// Key: \xac\xed\x00\x05t\x00\x06user:1// Value: \xac\xed\x00\x05sr\x00\x04User...这导致:key 在 Redis 管理台中不可读;跨语言消费无法反序列化;存储体积大。
3.3. 推荐方案一:自定义 RedisTemplate(JSON 序列化)
Section titled “3.3. 推荐方案一:自定义 RedisTemplate(JSON 序列化)”@Configurationpublic class RedisConfig {
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory);
// Key 和 HashKey 使用 String 序列化(可读) StringRedisSerializer stringSerializer = new StringRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer);
// Value 和 HashValue 使用 JSON 序列化 Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet(); return template; }}// 使用自定义 RedisTemplate 存储对象redisTemplate.opsForValue().set("user:1", new User("Alice", 25));
// Redis 中实际存储:// Key: user:1// Value: {"name":"Alice","age":25}3.4. 推荐方案二:StringRedisTemplate + 手动 JSON(更直观)
Section titled “3.4. 推荐方案二:StringRedisTemplate + 手动 JSON(更直观)”@Service@RequiredArgsConstructorpublic class UserCacheService {
private final StringRedisTemplate redisTemplate; private final ObjectMapper objectMapper;
public void save(User user) throws JsonProcessingException { String key = "user:" + user.getId(); String value = objectMapper.writeValueAsString(user); // 手动序列化 redisTemplate.opsForValue().set(key, value, 1, TimeUnit.HOURS); }
public User get(Long userId) throws JsonProcessingException { String json = redisTemplate.opsForValue().get("user:" + userId); if (json == null) return null; return objectMapper.readValue(json, User.class); // 手动反序列化 }}4. 操作 API 速查
Section titled “4. 操作 API 速查”RedisTemplate 通过 opsForXxx() 方法获取各数据类型的操作对象:
// 五种基础类型操作入口ValueOperations<K, V> ops = template.opsForValue(); // StringHashOperations<K, F, V> ops = template.opsForHash(); // HashListOperations<K, V> ops = template.opsForList(); // ListSetOperations<K, V> ops = template.opsForSet(); // SetZSetOperations<K, V> ops = template.opsForZSet(); // ZSet
// key 级别操作(通用命令)template.delete(key); // DELtemplate.expire(key, 1, TimeUnit.HOURS); // EXPIREtemplate.getExpire(key, TimeUnit.SECONDS); // TTLtemplate.hasKey(key); // EXISTStemplate.type(key); // TYPE4.1. 批量操作(Pipeline)
Section titled “4.1. 批量操作(Pipeline)”// Pipeline:一次网络往返发送多条命令,减少 RTTList<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> { StringRedisConnection conn = (StringRedisConnection) connection; for (int i = 1; i <= 100; i++) { conn.set("key:" + i, "value:" + i); } return null;});4.2. Lua 脚本(原子操作)
Section titled “4.2. Lua 脚本(原子操作)”// 使用 Lua 脚本保证多命令原子性(如分布式锁释放)String script = """ if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end """;
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute( redisScript, Collections.singletonList("lock:order:1001"), "unique-value-12345");5. 常见踩坑
Section titled “5. 常见踩坑”| 问题 | 原因 | 解决方案 |
|---|---|---|
| key 出现乱码前缀 | 使用了默认 JDK 序列化 | 自定义 StringRedisSerializer |
| 存对象后无法反序列化 | 泛型擦除,Object.class 无法还原类型 | 指定具体类型或用 TypeReference |
@Autowired RedisTemplate 注入失败 | 存在多个 RedisTemplate Bean | 用 @Qualifier 指定 Bean 名称 |
| 连接池耗尽报错 | 高并发下连接不够用 | 调大 max-active,检查是否有连接泄露 |
| 命令超时 | 网络抖动或 Redis 执行大 key 命令 | 设置合理 timeout,排查大 key |