Files
spring-boot-demo/redis/docs/02-default-serialization.md

70 lines
3.6 KiB
Markdown

[Home](../README.md) | Prev: [What Boot gives you](01-what-boot-gives-you.md) | Next: [String template and two keyspaces](03-string-template-and-two-keyspaces.md)
# 2. Default serialization: why redis-cli shows garbage
Redis stores bytes. A `RedisTemplate` is a translator between Java objects and those bytes, and the
translator is a `RedisSerializer`. The template Boot registers, `RedisTemplate<Object, Object>`,
uses `JdkSerializationRedisSerializer` for keys **and** values
([01-default-template.txt](output/01-default-template.txt)):
```
key serializer: JdkSerializationRedisSerializer
value serializer: JdkSerializationRedisSerializer
```
## What lands in Redis
`redisTemplate.opsForValue().set("user:1", "Ankur")` stores this key, which is not `user:1`:
```
$ redis-cli -p 6390 --no-raw KEYS '*'
1) "\xac\xed\x00\x05t\x00\x06user:1"
$ redis-cli -p 6390 --no-raw GET user:1
(nil)
```
Reading the bytes: `\xac\xed\x00\x05` is the header of every Java serialization stream (magic
number `0xACED`, stream version 5). `t` is the type code for a string, `\x00\x06` its length, then
the six characters. Java wrote a *serialised String object*, not the characters. `GET user:1`
therefore misses: a different key.
The value is the same story (`"\xac\xed\x00\x05t\x00\x05Ankur"`), and Java reading it back through the same template
works, which is why the problem hides: every test that goes through the template passes.
## What it does to objects
[02-default-template-not-serializable.txt](output/02-default-template-not-serializable.txt):
- A record that is not `java.io.Serializable` is rejected with `SerializationException: Cannot serialize`. The root cause message is Spring's `DefaultSerializer requires a Serializable payload`. Nothing is written.
- The same record implementing `Serializable` is accepted, and the stored bytes contain the **fully qualified class name** (`com.ankurm.redis.model.SerializableUser`) and field descriptors. Rename or move the class and existing entries stop deserializing.
## The counter trap
`opsForValue().increment("hits")` sends `INCR`, and Redis stores the text `1`. The template wrote the
key with JDK serialization, but the counter's value is plain text, so the next `get` fails
([03-default-template-increment.txt](output/03-default-template-increment.txt)):
```
org.springframework.data.redis.serializer.SerializationException
message: Cannot deserialize
root: java.io.EOFException: null
```
Any command where Redis itself interprets the value (`INCR`, `INCRBYFLOAT`, `HINCRBY`) needs a
serializer that writes plain text.
## Fixes
1. Use `StringRedisTemplate` when everything is text ([chapter 3](03-string-template-and-two-keyspaces.md)).
2. Configure String keys and a JSON value serializer ([chapter 4](04-json-serializers.md)).
3. If data written with the JDK serializer is already in production, **changing the serializer orphans it**: new keys have different bytes. Plan a migration or a new key prefix. This repository does not exercise a migration.
## Going deeper
- [User](../src/main/java/com/ankurm/redis/model/User.java) and [SerializableUser](../src/main/java/com/ankurm/redis/model/SerializableUser.java)
- [DefaultTemplateTest](../src/test/java/com/ankurm/redis/DefaultTemplateTest.java)
- [Java Object Serialization Specification: stream grammar and type codes](https://docs.oracle.com/en/java/javase/25/docs/specs/serialization/protocol.html)
- [Spring Data Redis: serializers](https://docs.spring.io/spring-data/redis/reference/redis/template.html#redis:serializer)
[Home](../README.md) | Prev: [What Boot gives you](01-what-boot-gives-you.md) | Next: [String template and two keyspaces](03-string-template-and-two-keyspaces.md)