上一小节我们提到了RedisTemplate<Stinrg,Object>使用SpringDataRedis 默认的 JDK 序列化方式时,Redis 的key/value 可读性比较差。
因此课程里通常会自定义 RedisTemplate:
目的就是:
plaintext
默认 JDK 序列化
↓
数据不直观、不方便阅读
改成
Key → String 序列化
Value → JSON 序列化
↓
Redis 中的数据更容易阅读
但这里又产生了新的问题:
假设保存:
java
User user = new User("张三", 20);
redisTemplate.opsForValue().set("user:1", user);
为了以后能够把 JSON 反序列化回 User 对象,某些 JSON 序列化方案会把 Java 类型信息一起保存进去。
Redis 中可能类似:
js
{
"@class": "com.example.pojo.User",
"name": "张三",
"age": 20
}
问题就在这里:
text
真正业务数据
name
age
+
额外类型信息
com.example.pojo.User
也就是说:
为了自动反序列化对象,需要保存额外的 Java 类型信息,增加了 Redis 的存储开销,而且 Redis 数据和 Java 类结构耦合得更紧。
为了解决这个问题我们提供了另一种思路:StringRedisTemplate
它不帮你处理复杂 Java 对象,而是明确规定:
text
Key → String
Value → String
因此:
java
@Autowired
private StringRedisTemplate stringRedisTemplate;
存普通字符串:
java
stringRedisTemplate.opsForValue()
.set("name", "Jack");
如果要存 User:
java
User user = new User("张三", 20);
先由我们自己:
text
User 对象
↓
ObjectMapper
↓
JSON 字符串
↓
StringRedisTemplate
↓
Redis
Redis 里只需要
js
{
"name": "张三",
"age": 20
}
读取的时候反过来:
text
Redis
↓
JSON String
↓
ObjectMapper
↓
User
也就是:
先通过ObjectMapper将 user 对象转成 JSON 格式的字符串:
java
String json = objectMapper.writeValueAsString(user);
然后调用 Spring 给我们的提供的 API将 String 类型的 key/value 写入 redis:
java
redisTemplate.opsForvalue().set("user",json);
读取的时候同样需要调用ObjectMapper 手动将读取到的JSON 字符串通过字节码对象转成 User 对象:
java
String json = stringRedisTemplate.opsForValue()
.get("user");
User user = objectMapper.readValue(json, User.class);
虽然看起来多写了一步:
java
对象 → JSON
JSON → 对象
但是好处是Redis 只负责存字符串,Java 程序自己决定这个字符串应该转换成什么对象。
总结:
为了节省内存空间,我们并不会使用 JSON 序列化器来处理 value,而是统一使用 String 序列化器,要求只能存储 String 类型的 key 和 value,这两者的区别在于:JSON 序列化器会手动帮我们把Java 对象序列化成 String 数据,但为了实现自动反序列化需要保存额外的 Java 类型信息,导致Redis 的存储开销变大并且增加了Java 类与 Redis 数据的耦合度,而后者则在需要存储 Java 对象时,通过
ObjectMapper的writeValueAsString(user)和readValue(json,User.class)手动完成对象的序列化和反序列化。
补充:
-
ObjectMapper 是一个 JSON 工具
需要我们手动的创建对象:
private static final ObjectMapper mapper = new ObjectMapper(); -
Spring 默认提供了一个 StringRedisTemplate 类,它的 key 和 value 默认就是 String 方式,省去了我们自定义 RedisTemplate 的过程.
-
User.class得到的是User类对应的Class对象,可以暂时把它理解为"字节码对象",但更严谨的叫法是:
User.class是User类在 JVM 中对应的Class<User>对象。
text
User.java
↓ 编译
User.class 字节码文件
↓ JVM 加载
JVM 中的 User 类
↓
对应一个 Class<User> 对象
↑
User.class 可以拿到它
相当于拿到这个对象的信息:
text
json → 数据是什么
User.class → 告诉 Jackson 要转换成什么类型
↓
User 对象