SpringBoot18-redis的配置
一、Spring Boot 中使用 Redis
我来详细介绍如何在 Spring Boot 项目中集成和使用 Redis。
1. 添加依赖
在 pom.xml 中添加 Spring Data Redis 依赖:
<dependencies><!-- Spring Boot Redis 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Lettuce 连接池(可选,推荐) --><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>
2. 配置文件
在 application.yml 或 application.properties 中配置 Redis:
application.yml:
spring:redis:host: localhostport: 6379password: # 如果有密码就填写database: 0 # Redis 数据库索引(默认为0)timeout: 3000ms # 连接超时时间lettuce:pool:max-active: 8 # 连接池最大连接数max-idle: 8 # 连接池最大空闲连接数min-idle: 0 # 连接池最小空闲连接数max-wait: -1ms # 连接池最大阻塞等待时间
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 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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 valueJackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);// 使用 StringRedisSerializer 来序列化和反序列化 keyStringRedisSerializer stringRedisSerializer = new StringRedisSerializer();// key 采用 String 的序列化方式template.setKeySerializer(stringRedisSerializer);// hash 的 key 也采用 String 的序列化方式template.setHashKeySerializer(stringRedisSerializer);// value 序列化方式采用 jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);// hash 的 value 序列化方式采用 jacksontemplate.setHashValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}
}
4. 使用方式
方式一:直接注入 RedisTemplate
package com.example.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
public class UserService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// 设置值public void setUser(String key, User user) {redisTemplate.opsForValue().set(key, user);}// 设置值并设置过期时间public void setUserWithExpire(String key, User user, long timeout) {redisTemplate.opsForValue().set(key, user, timeout, TimeUnit.SECONDS);}// 获取值public User getUser(String key) {return (User) redisTemplate.opsForValue().get(key);}// 删除public void deleteUser(String key) {redisTemplate.delete(key);}// 判断 key 是否存在public boolean hasKey(String key) {return Boolean.TRUE.equals(redisTemplate.hasKey(key));}// 设置过期时间public void expire(String key, long timeout) {redisTemplate.expire(key, timeout, TimeUnit.SECONDS);}
}
方式二:封装 Redis 工具类(推荐)
package com.example.util;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.util.Collection;
import java.util.concurrent.TimeUnit;@Component
public class RedisUtil {@Autowiredprivate 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 Object get(String key) {return redisTemplate.opsForValue().get(key);}/*** 删除缓存*/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);}/*** 判断 key 是否存在*/public Boolean hasKey(String key) {return redisTemplate.hasKey(key);}/*** 递增*/public Long increment(String key, long delta) {return redisTemplate.opsForValue().increment(key, delta);}/*** 递减*/public Long decrement(String key, long delta) {return redisTemplate.opsForValue().decrement(key, delta);}// ========== Hash 操作 ==========/*** Hash 设置*/public void hSet(String key, String hashKey, Object value) {redisTemplate.opsForHash().put(key, hashKey, value);}/*** Hash 获取*/public Object hGet(String key, String hashKey) {return redisTemplate.opsForHash().get(key, hashKey);}/*** Hash 删除*/public Long hDelete(String key, Object... hashKeys) {return redisTemplate.opsForHash().delete(key, hashKeys);}// ========== List 操作 ==========/*** List 右侧推入*/public Long lPush(String key, Object value) {return redisTemplate.opsForList().rightPush(key, value);}/*** List 左侧弹出*/public Object lPop(String key) {return redisTemplate.opsForList().leftPop(key);}// ========== Set 操作 ==========/*** Set 添加*/public Long sAdd(String key, Object... values) {return redisTemplate.opsForSet().add(key, values);}/*** Set 移除*/public Long sRemove(String key, Object... values) {return redisTemplate.opsForSet().remove(key, values);}// ========== Sorted Set 操作 ==========/*** ZSet 添加*/public Boolean zAdd(String key, Object value, double score) {return redisTemplate.opsForZSet().add(key, value, score);}
}
5. 实际使用示例
@RestController
@RequestMapping("/api/user")
public class UserController {@Autowiredprivate RedisUtil redisUtil;// 缓存用户信息@GetMapping("/{id}")public User getUser(@PathVariable Long id) {String key = "user:" + id;// 先从缓存中获取User user = (User) redisUtil.get(key);if (user == null) {// 缓存未命中,从数据库查询user = userService.getUserById(id);if (user != null) {// 存入缓存,过期时间 30 分钟redisUtil.set(key, user, 30, TimeUnit.MINUTES);}}return user;}// 更新用户时删除缓存@PutMapping("/{id}")public void updateUser(@PathVariable Long id, @RequestBody User user) {userService.updateUser(user);// 删除缓存redisUtil.delete("user:" + id);}
}
6. 常见操作总结
| 数据类型 | RedisTemplate 方法 | 说明 |
|---|---|---|
| String | opsForValue() | 字符串操作 |
| Hash | opsForHash() | 哈希表操作 |
| List | opsForList() | 列表操作 |
| Set | opsForSet() | 集合操作 |
| Sorted Set | opsForZSet() | 有序集合操作 |
7. 注意事项
- 序列化问题:默认使用 JDK 序列化,建议使用 JSON 序列化(如上面配置类所示)
- 缓存穿透:查询不存在的数据,建议缓存空值或使用布隆过滤器
- 缓存雪崩:大量缓存同时失效,建议设置随机过期时间
- 缓存击穿:热点数据过期,建议使用互斥锁或永不过期
- 连接池配置:根据实际并发量调整连接池参数
二、为什么要编写redis配置类呢
1. 可以直接使用,但有问题
引入依赖后可以直接 @Autowired 注入使用,Spring Boot 会自动配置一个默认的 RedisTemplate。
@Autowired
private RedisTemplate redisTemplate; // 可以直接用
但是,默认的 RedisTemplate 有一个严重问题:序列化方式!
2. 默认序列化的问题
Spring Boot 默认的 RedisTemplate 使用 JDK 序列化(JdkSerializationRedisSerializer),会导致:
问题演示:
// 存入数据
redisTemplate.opsForValue().set("user:1", user);
在 Redis 中看到的数据是这样的:
Key: "\xac\xed\x00\x05t\x00\x06user:1"
Value: "\xac\xed\x00\x05sr\x00\x1ccom.example.entity.User..."
问题有哪些?
- 不可读:存储的是二进制数据,无法直接在 Redis 客户端查看
- 占用空间大:JDK 序列化后的数据比 JSON 大很多
- 跨语言不兼容:其他语言(如 Python、Go)无法读取 Java 序列化的数据
- 安全风险:JDK 序列化存在已知的安全漏洞
对比:使用 JSON 序列化
Key: "user:1"
Value: {"id":1,"name":"张三","age":25}
这样就:
- ✅ 可读性强
- ✅ 体积更小
- ✅ 跨语言兼容
- ✅ 更安全
3. 为什么是 RedisTemplate<String, Object>?
泛型说明
RedisTemplate<K, V>
// K: Key 的类型
// V: Value 的类型
为什么用 <String, Object>?
Key 使用 String:
// Redis 的 key 通常都是字符串
"user:1"
"product:100"
"cache:article:20"
Value 使用 Object:
// 可以存储各种类型的对象
redisTemplate.opsForValue().set("user:1", userObject); // User 对象
redisTemplate.opsForValue().set("count", 100); // Integer
redisTemplate.opsForValue().set("list", Arrays.asList(1,2,3)); // List
使用 Object 类型最灵活,可以存储任何对象。
4. 配置类做了什么?
让我简化说明配置类的作用:
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);// 关键:修改序列化方式// 1. Key 使用 String 序列化template.setKeySerializer(new StringRedisSerializer());// 2. Value 使用 JSON 序列化template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));// 3. Hash 的 Key 和 Value 也设置序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));return template;
}
核心作用就是:把默认的 JDK 序列化改成 JSON 序列化!
5. 不配置 vs 配置的对比
场景:存储用户对象
User user = new User(1L, "张三", 25);
redisTemplate.opsForValue().set("user:1", user);
不配置(使用默认)
Redis 中存储的内容:
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x06user:1"127.0.0.1:6379> get "\xac\xed\x00\x05t\x00\x06user:1"
"\xac\xed\x00\x05sr\x00\x1c..." # 一堆乱码
配置后(使用 JSON)
Redis 中存储的内容:
127.0.0.1:6379> keys *
1) "user:1"127.0.0.1:6379> get user:1
"{\"id\":1,\"name\":\"张三\",\"age\":25}"
是不是清晰多了?
总结
- 可以不配置,但会用 JDK 序列化,导致数据不可读
- 配置的目的:改用 JSON 序列化,让数据可读、节省空间、跨语言兼容
<String, Object>:Key 用字符串,Value 用 Object 更灵活- 推荐做法:写配置类,统一使用 JSON 序列化
三、配置类中的ObjectMapper的相关操作
redis配置类中有一段代码:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL );
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);让我逐行解释这段代码的作用。
这段代码在解决什么问题?
核心问题:反序列化时的类型丢失
当你从 Redis 取出数据时,Jackson 不知道原始对象是什么类型,只能反序列化成 LinkedHashMap 或其他通用类型。
问题演示
// 存入 User 对象
User user = new User(1L, "张三", 25);
redisTemplate.opsForValue().set("user:1", user);// 取出时
Object obj = redisTemplate.opsForValue().get("user:1");
System.out.println(obj.getClass());
// 输出:class java.util.LinkedHashMap(不是 User!)// 无法直接使用
User user = (User) obj; // 报错:ClassCastException
逐行解释
1. 创建 ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
这是 Jackson 的核心类,负责 Java 对象和 JSON 之间的转换。
2. 设置可见性
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
作用:告诉 Jackson 可以访问对象的所有属性
PropertyAccessor.ALL 包括:
FIELD(字段)GETTER(get 方法)SETTER(set 方法)CREATOR(构造方法)IS_GETTER(is 方法)
JsonAutoDetect.Visibility.ANY 表示:
public可以访问protected可以访问private也可以访问 ⬅️ 关键
示例:
public class User {private Long id; // private 字段private String name; // private 字段// 没有 getter/setter 也能序列化!
}
不设置这个配置的话:
// 默认只能访问 public 字段或有 getter/setter 的字段
// private 字段没有 getter 就无法序列化
3. 激活默认类型信息(重点!)
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL
);
这是最关键的配置!作用:在 JSON 中存储类型信息
不配置时的问题:
存入 Redis:
{"id": 1,"name": "张三","age": 25
}
从 Redis 取出:
Object obj = redisTemplate.opsForValue().get("user:1");
// obj 是 LinkedHashMap,不是 User!
// 因为 Jackson 不知道原始类型是什么
配置后的效果:
存入 Redis(包含类型信息):
["com.example.entity.User",{"id": 1,"name": "张三","age": 25}
]
从 Redis 取出:
Object obj = redisTemplate.opsForValue().get("user:1");
// obj 就是 User 类型!可以直接转换
User user = (User) obj; // ✅ 成功
参数说明:
LaissezFaireSubTypeValidator.instance
- 一个宽松的类型验证器
- 允许反序列化几乎所有类型
LaissezFaire是法语,意思是"放任自由"
ObjectMapper.DefaultTyping.NON_FINAL
- 为非 final 类添加类型信息
- 选项包括:
JAVA_LANG_OBJECT:只对 Object 类型OBJECT_AND_NON_CONCRETE:Object 和抽象类/接口NON_CONCRETE_AND_ARRAYS:抽象类、接口和数组NON_FINAL:所有非 final 类 ⬅️ 最常用EVERYTHING:所有类型
4. 设置到序列化器
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
把配置好的 ObjectMapper 设置给 Jackson 序列化器,让它使用我们的配置。
完整效果对比
配置前(类型丢失)
// 存入
User user = new User(1L, "张三", 25);
redisTemplate.opsForValue().set("user:1", user);// Redis 中存储:
{"id": 1,"name": "张三","age": 25
}// 取出
Object obj = redisTemplate.opsForValue().get("user:1");
System.out.println(obj.getClass());
// 输出:class java.util.LinkedHashMap ❌User user = (User) obj; // 报错!ClassCastException
配置后(保留类型)
// 存入
User user = new User(1L, "张三", 25);
redisTemplate.opsForValue().set("user:1", user);// Redis 中存储:
["com.example.entity.User",{"id": 1,"name": "张三","age": 25}
]// 取出
Object obj = redisTemplate.opsForValue().get("user:1");
System.out.println(obj.getClass());
// 输出:class com.example.entity.User ✅User user = (User) obj; // 成功!
代码简化版(帮助理解)
ObjectMapper objectMapper = new ObjectMapper();// 1. 让 Jackson 能访问 private 字段
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);// 2. 在 JSON 中保存类型信息(最重要!)
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, // 验证器:允许所有类型ObjectMapper.DefaultTyping.NON_FINAL // 为非 final 类添加类型信息
);// 3. 应用这些配置
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
注意事项
1. 安全风险
// 使用 LaissezFaireSubTypeValidator 可能有安全风险
// 因为它允许反序列化任何类型
// 生产环境可以考虑更严格的验证器
2. 性能影响
类型信息会增加存储空间
// 原始:{"id":1,"name":"张三"}
// 带类型:["com.example.User",{"id":1,"name":"张三"}]
3. 替代方案
如果不想使用类型信息,可以手动指定类型:
// 存入时就明确类型
ValueOperations<String, User> ops = redisTemplate.opsForValue();
ops.set("user:1", user);// 取出时也明确类型
User user = ops.get("user:1");
总结
这段代码的核心作用:
- setVisibility:让 Jackson 能访问 private 字段
- activateDefaultTyping:在 JSON 中存储类型信息(最重要!)
- 目的:解决反序列化时类型丢失的问题
不配置:取出来是 LinkedHashMap
配置后:取出来是原始的 User 对象
