Serializers
redis에 데이터가 저장되는 방식 / spring에서 읽어오는 방식 등을 설정할 수 있다.
JdkSerializationRedisSerializer(Default)
- 기본 serializer
- 기본 java 직렬화 방식을 사용
- 기본 java 직렬화 방식은 취약한 원격 코드가 그대로 실행될 수 있다는 보안 취약점이 존재
- 신뢰할 수 없는 환경에서는 해당 직렬화 방식은 권장하지 않음
- serialVersionUID를 설정하지 않으면 클래스의 기본 해쉬값을 serialVersionUID로 사용하기 때문에 클래수 구조가 조금이라도 바뀌면 기존 데이터의 역직렬화가 실패
- serialVersionUID를 설정한다 해도 클래스에 필드가 제거되거나 수정되는 경우에는 역직렬화 예외가 발생
- 직렬화시 타입에 대한 정보 등의 메타 정보를 갖고 있기 때문에 다른 포맷에 비해 저장 용량이 크다.
따라서 자주 변경되는 클래스에서는 사용을 지양
GenericJackson2JsonRedisSerializer
- 별도 class type 지정 없이 자동으로 Object를 Json으로 직렬화
- object의 class Type을 포함한 데이터를 같이 저장 → 저장 용량 증가
- class의 package까지 같이 저장되고, 역직렬화 시 해당 경로의 클래스 정보를 읽어 역직렬화를 수행
- 따라서 직렬화 시의 class 위치가 달라지는 경우 에러 발생
Jackson2JsonRedisSerializer
- 단순히 Object를 Jackson으로 변환해 Json으로 저장
- ClassType을 Serializer에 지정해줘야 한다.
- 각 redisTemplate 마다 한 개의 클래스만 매핑될 수 있다.
@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.setKeySerializer(new Jackson2JsonRedisSerializer(String.class));
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(DTO.class));
return redisTemplate;
}
StringRedisSerializer
- String값을 그대로 저장
- 객체를 Json형태로 변환해 Redis에 저장하기 위해서는 application 단에서 Encoding/Decoding을 수행해야 한다.
- 클래스의 메타 정보를 저장할 필요가 없어 저장 용량이 적고 클래스의 위치에 의존성이 없다.
- 하나의 RedisTemplate에 여러 클래스가 같이 매핑될 수 있다.
@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
실습
실제 Serializer에 따라 값이 어떻게 달라지는지 확인
- StringSerializer, Jackson2JsonRedisSerializer, GenericJackson2JsonRedisSerializer에 따라 어떻게 값이 달라지는지 확인
- 테스트를 쉽게 하기 위해 모든 KeySerializer는 StringSerializer로 통일
Redis Config
@Configuration
public class RedisConfig {
@Bean
RedisConnectionFactory lettuceConnectionFactory() {
return new LettuceConnectionFactory(
new RedisStandaloneConfiguration("localhost", 6379)
);
}
@Bean(name = "RedisStringTemplate")
RedisTemplate<String, String> redisStringTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, String> template = new StringRedisTemplate();
template.setConnectionFactory(connectionFactory);
return template;
}
@Bean(name = "RedisGenericTemplate")
RedisTemplate<String, Object> redisGenericJackson2JsonRedisTemplate(
RedisConnectionFactory connectionFactory
) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
@Bean(name = "RedisJacksonTemplate")
RedisTemplate<String, RedisExampleEntity> redisJackson2JsonRedisTemplate(
RedisConnectionFactory connectionFactory
) {
RedisTemplate<String, RedisExampleEntity> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(RedisExampleEntity.class));
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RedisExampleEntity.class));
return redisTemplate;
}
}
RedisExampleEntity
@NoArgsConstructor
@AllArgsConstructor
public class RedisExampleEntity {
public Long id;
public Long callId;
public String status;
}
GenericJackson2JsonSerializer
@Test
void redisGenericJackson2JsonRedisTemplateTest() {
// given
RedisExampleEntity redisExampleEntity1 = new RedisExampleEntity(1L, 1L, "CALL");
RedisExampleEntity redisExampleEntity2 = new RedisExampleEntity(2L, 2L, "STOP");
ValueOperations<String, Object> valueOps = redisGenericJackson2JsonRedisTemplate.opsForValue();
valueOps.set("genericJackson2JsonSerializerTest", redisExampleEntity1);
HashOperations<String, Object, Object> hashOps = redisGenericJackson2JsonRedisTemplate.opsForHash();
hashOps.put("genericJackson2JsonSerializerTestHash", "entity1", redisExampleEntity1);
hashOps.put("genericJackson2JsonSerializerTestHash", "entity2", redisExampleEntity2);
// when
Object findEntity = valueOps.get("genericJackson2JsonSerializerTest");
Object findEntityHash1 = hashOps.get("genericJackson2JsonSerializerTestHash", "entity1");
Object findEntityHash2 = hashOps.get("genericJackson2JsonSerializerTestHash", "entity2");
// then
assertThat(findEntity).isNotNull();
assertThat(findEntity).isInstanceOf(RedisExampleEntity.class);
assertThat(((RedisExampleEntity) findEntity).id).isEqualTo(1L);
assertThat(findEntityHash1).isNotNull();
assertThat(findEntityHash1).isInstanceOf(RedisExampleEntity.class);
assertThat(((RedisExampleEntity) findEntityHash1).id).isEqualTo(1L);
assertThat(findEntityHash2).isNotNull();
assertThat(findEntityHash2).isInstanceOf(RedisExampleEntity.class);
assertThat(((RedisExampleEntity) findEntityHash2).id).isEqualTo(2L);
}
- 클래스와 RestTemplate 간의 N:1 관계이다.
값 확인

- 클래스 위치 정보가 자동으로 저장된다.
- 클래스의 위치가 달라지면 오류가 발생
Jackson2JsonSerializer
@Test
void redisJackson2JsonRedisTemplateTest() {
// given
RedisExampleEntity redisExampleEntity1 = new RedisExampleEntity(1L, 1L, "CALL");
RedisExampleEntity redisExampleEntity2 = new RedisExampleEntity(2L, 2L, "STOP");
ValueOperations<String, RedisExampleEntity> valueOps = redisJackson2JsonRedisTemplate.opsForValue();
valueOps.set("Jackson2JsonSerializerTest", redisExampleEntity1);
HashOperations<String, Object, RedisExampleEntity> hashOps = redisJackson2JsonRedisTemplate.opsForHash();
hashOps.put("Jackson2JsonSerializerTestHash", "entity1", redisExampleEntity1);
hashOps.put("Jackson2JsonSerializerTestHash", "entity2", redisExampleEntity2);
// when
RedisExampleEntity findEntity = valueOps.get("Jackson2JsonSerializerTest");
RedisExampleEntity findEntityHash1 = hashOps.get("Jackson2JsonSerializerTestHash", "entity1");
RedisExampleEntity findEntityHash2 = hashOps.get("Jackson2JsonSerializerTestHash", "entity2");
// then
assertThat(findEntity).isNotNull();
assertThat(findEntity).isInstanceOf(RedisExampleEntity.class);
assertThat(findEntity.id).isEqualTo(1L);
assertThat(findEntityHash1).isNotNull();
assertThat(findEntityHash1).isInstanceOf(RedisExampleEntity.class);
assertThat(findEntityHash1.id).isEqualTo(1L);
assertThat(findEntityHash2).isNotNull();
assertThat(findEntityHash2).isInstanceOf(RedisExampleEntity.class);
assertThat(findEntityHash2.id).isEqualTo(2L);
}
- class와 RedisTemplate이 1:1 관계이다.
값 확인

- 값은 String 형태로 들어가 있다.
- 실제 application에서 불러올 때 class로 deserialize해준 뒤 반환
StringSerializer
@Test
void redisStringTemplateTest() throws JsonProcessingException {
// given
ObjectMapper objectMapper = new ObjectMapper();
RedisExampleEntity redisExampleEntity1 = new RedisExampleEntity(1L, 1L, "CALL");
String serializedRedisExampleEntity1 = objectMapper.writeValueAsString(redisExampleEntity1);
RedisExampleEntity redisExampleEntity2 = new RedisExampleEntity(2L, 2L, "STOP");
String serializedRedisExampleEntity2 = objectMapper.writeValueAsString(redisExampleEntity2);
ValueOperations<String, String> valueOps = redisStringTemplate.opsForValue();
valueOps.set("stringSerializerTest", serializedRedisExampleEntity1);
HashOperations<String, String, String> hashOps = redisStringTemplate.opsForHash();
hashOps.put("stringSerializerTestHash", "entity1", serializedRedisExampleEntity1);
hashOps.put("stringSerializerTestHash", "entity2", serializedRedisExampleEntity2);
// when
String findEntityString = valueOps.get("stringSerializerTest");
RedisExampleEntity findEntity = objectMapper.readValue(findEntityString, RedisExampleEntity.class);
String findEntityHash1String = hashOps.get("stringSerializerTestHash", "entity1");
RedisExampleEntity findEntityHash1 = objectMapper.readValue(findEntityHash1String, RedisExampleEntity.class);
String findEntityHash2String = hashOps.get("stringSerializerTestHash", "entity2");
RedisExampleEntity findEntityHash2 = objectMapper.readValue(findEntityHash2String, RedisExampleEntity.class);
// then
assertThat(findEntity).isNotNull();
assertThat(findEntity).isInstanceOf(RedisExampleEntity.class);
assertThat(findEntity.id).isEqualTo(1L);
assertThat(findEntityHash1).isNotNull();
assertThat(findEntityHash1).isInstanceOf(RedisExampleEntity.class);
assertThat(findEntityHash1.id).isEqualTo(1L);
assertThat(findEntityHash2).isNotNull();
assertThat(findEntityHash2).isInstanceOf(RedisExampleEntity.class);
assertThat(findEntityHash2.id).isEqualTo(2L);
}
- put/get 전에 직접 serialize, deserialize를 수행해줘야 한다.
값 확인

전체코드
@SpringBootTest
class RedisExampleApplicationTests {
@Autowired
@Qualifier(value = "RedisStringTemplate")
RedisTemplate<String, String> redisStringTemplate;
@Autowired
@Qualifier(value = "RedisGenericTemplate")
RedisTemplate<String, Object> redisGenericJackson2JsonRedisTemplate;
@Autowired
@Qualifier(value = "RedisJacksonTemplate")
RedisTemplate<String, RedisExampleEntity> redisJackson2JsonRedisTemplate;
@Test
void contextLoads() {
}
@Test
void redisStringTemplateTest() throws JsonProcessingException {
// given
ObjectMapper objectMapper = new ObjectMapper();
RedisExampleEntity redisExampleEntity1 = new RedisExampleEntity(1L, 1L, "CALL");
String serializedRedisExampleEntity1 = objectMapper.writeValueAsString(redisExampleEntity1);
RedisExampleEntity redisExampleEntity2 = new RedisExampleEntity(2L, 2L, "STOP");
String serializedRedisExampleEntity2 = objectMapper.writeValueAsString(redisExampleEntity2);
ValueOperations<String, String> valueOps = redisStringTemplate.opsForValue();
valueOps.set("stringSerializerTest", serializedRedisExampleEntity1);
HashOperations<String, String, String> hashOps = redisStringTemplate.opsForHash();
hashOps.put("stringSerializerTestHash", "entity1", serializedRedisExampleEntity1);
hashOps.put("stringSerializerTestHash", "entity2", serializedRedisExampleEntity2);
// when
String findEntityString = valueOps.get("stringSerializerTest");
RedisExampleEntity findEntity = objectMapper.readValue(findEntityString, RedisExampleEntity.class);
String findEntityHash1String = hashOps.get("stringSerializerTestHash", "entity1");
RedisExampleEntity findEntityHash1 = objectMapper.readValue(findEntityHash1String, RedisExampleEntity.class);
String findEntityHash2String = hashOps.get("stringSerializerTestHash", "entity2");
RedisExampleEntity findEntityHash2 = objectMapper.readValue(findEntityHash2String, RedisExampleEntity.class);
// then
assertThat(findEntity).isNotNull();
assertThat(findEntity).isInstanceOf(RedisExampleEntity.class);
assertThat(findEntity.id).isEqualTo(1L);
assertThat(findEntityHash1).isNotNull();
assertThat(findEntityHash1).isInstanceOf(RedisExampleEntity.class);
assertThat(findEntityHash1.id).isEqualTo(1L);
assertThat(findEntityHash2).isNotNull();
assertThat(findEntityHash2).isInstanceOf(RedisExampleEntity.class);
assertThat(findEntityHash2.id).isEqualTo(2L);
}
@Test
void redisGenericJackson2JsonRedisTemplateTest() {
// given
RedisExampleEntity redisExampleEntity1 = new RedisExampleEntity(1L, 1L, "CALL");
RedisExampleEntity redisExampleEntity2 = new RedisExampleEntity(2L, 2L, "STOP");
ValueOperations<String, Object> valueOps = redisGenericJackson2JsonRedisTemplate.opsForValue();
valueOps.set("genericJackson2JsonSerializerTest", redisExampleEntity1);
HashOperations<String, Object, Object> hashOps = redisGenericJackson2JsonRedisTemplate.opsForHash();
hashOps.put("genericJackson2JsonSerializerTestHash", "entity1", redisExampleEntity1);
hashOps.put("genericJackson2JsonSerializerTestHash", "entity2", redisExampleEntity2);
// when
Object findEntity = valueOps.get("genericJackson2JsonSerializerTest");
Object findEntityHash1 = hashOps.get("genericJackson2JsonSerializerTestHash", "entity1");
Object findEntityHash2 = hashOps.get("genericJackson2JsonSerializerTestHash", "entity2");
// then
assertThat(findEntity).isNotNull();
assertThat(findEntity).isInstanceOf(RedisExampleEntity.class);
assertThat(((RedisExampleEntity) findEntity).id).isEqualTo(1L);
assertThat(findEntityHash1).isNotNull();
assertThat(findEntityHash1).isInstanceOf(RedisExampleEntity.class);
assertThat(((RedisExampleEntity) findEntityHash1).id).isEqualTo(1L);
assertThat(findEntityHash2).isNotNull();
assertThat(findEntityHash2).isInstanceOf(RedisExampleEntity.class);
assertThat(((RedisExampleEntity) findEntityHash2).id).isEqualTo(2L);
}
@Test
void redisJackson2JsonRedisTemplateTest() {
// given
RedisExampleEntity redisExampleEntity1 = new RedisExampleEntity(1L, 1L, "CALL");
RedisExampleEntity redisExampleEntity2 = new RedisExampleEntity(2L, 2L, "STOP");
ValueOperations<String, RedisExampleEntity> valueOps = redisJackson2JsonRedisTemplate.opsForValue();
valueOps.set("Jackson2JsonSerializerTest", redisExampleEntity1);
HashOperations<String, Object, RedisExampleEntity> hashOps = redisJackson2JsonRedisTemplate.opsForHash();
hashOps.put("Jackson2JsonSerializerTestHash", "entity1", redisExampleEntity1);
hashOps.put("Jackson2JsonSerializerTestHash", "entity2", redisExampleEntity2);
// when
RedisExampleEntity findEntity = valueOps.get("Jackson2JsonSerializerTest");
RedisExampleEntity findEntityHash1 = hashOps.get("Jackson2JsonSerializerTestHash", "entity1");
RedisExampleEntity findEntityHash2 = hashOps.get("Jackson2JsonSerializerTestHash", "entity2");
// then
assertThat(findEntity).isNotNull();
assertThat(findEntity).isInstanceOf(RedisExampleEntity.class);
assertThat(findEntity.id).isEqualTo(1L);
assertThat(findEntityHash1).isNotNull();
assertThat(findEntityHash1).isInstanceOf(RedisExampleEntity.class);
assertThat(findEntityHash1.id).isEqualTo(1L);
assertThat(findEntityHash2).isNotNull();
assertThat(findEntityHash2).isInstanceOf(RedisExampleEntity.class);
assertThat(findEntityHash2.id).isEqualTo(2L);
}
}
참고
Working with Objects through RedisTemplate :: Spring Data Redis
Spring RedisTemplate Serializer 설정
'Spring Data Redis(스프링 데이터 레디스)' 카테고리의 다른 글
| Redis Pub/Sub (0) | 2023.12.29 |
|---|---|
| Redis Cache (0) | 2023.12.29 |
| RedisTemplate (0) | 2023.12.29 |
| Redis Connection Modes (0) | 2023.12.29 |
| Redis Drivers (0) | 2023.12.29 |