문제
RedisTemplate
를 통해 데이터를 저장하기에는 성공했으나, 읽어오는 과정에서 역직렬화에 실패했다.
Redis에 저장할dto
에 존재하지 않는 필드가 함께 저장되어 역직렬화에 실패한 것인데 이유를 알아보자.
Redis에 String 타입의 Json 데이터를 저장했고 직/역직렬화에는 Jackson 2.18.1 라이브러리를 사용했다.
Redis
에 StringType으로 저정할 Dto는 아래와 같다.
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ProductCache implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private static final String KEY_FORMAT = "product:%d";
private Long id;
private Long hubId;
private Long companyId;
private Integer quantity;
private String name;
// ...
public String getKey() {
return String.format(KEY_FORMAT, id);
}
}
Key 포맷을 Dto에 두었고, getKey()
를 통해 외부에서 키를 얻을 수 있도록 했다.
정상적으로 동작하는지 테스트를 해보았다.
@Test
@DisplayName("캐시 생성 테스트")
public void saveProductCache_successTest() {
// Given
Product save = productCacheAdapter.save(TEST_PRODUCT);
// When
Optional<Product> productOptional = productCacheAdapter.findOne(save);
// Then
Assertions.assertAll(
() -> Assertions.assertTrue(productOptional.isPresent()),
() -> Assertions.assertEquals(TEST_PRODUCT.getId(), productOptional.get().getId())
);
}
결과는?
org.springframework.data.redis.serializer.SerializationException:
Could not read JSON:Unrecognized field "key"
(class com.sparta.logistics.product.infrastructure.cache.dto.ProductCache),
not marked as ignorable
(5 known properties: "hubId", "id", "companyId", "quantity", "name"])
직렬화 관련 예외가 발생하며 key
필드를 발견하지 못하여 JSON을 읽어오지 못한다고 한다. 발견된 프로퍼티는 hubId
, id
, companyId
, quantity
, name
인데 key
라는 필드가 없어서 역직렬화에 실패한 듯 했다.
캐시의 데이터를 확인해보자.
{
"@class":"com.sparta.logistics.product.infrastructure.cache.dto.ProductCache",
"id":1,
"hubId":1,
"companyId":1,
"quantity":1,
"name":"test",
"key":"product:1"
}
나는 캐시에 넣을 데이터에 key라는 필드를 추가한 적이 없다.
하지만 캐시 데이터에는 key라는 데이터가 저장되어있다. 놀라워
그러다 문득 Jackson
의 역직렬화 방식에 대해 탐구해봤던 내용이 떠올랐다.
Jackson
라이브러리가 JSON
포맷문자열을 객체로 역직렬화할 때, 아래의 순서를 따른다.
- 빈 생성자를 호출한다.
- 리플렉션으로
getXxx()
메서드를 찾아 Xxx에 해당하는 필드에 값을 매핑한다.
자세하게 정리한 블로그 포스팅이 있어 첨부한다.
ObjectMapper
따라서 Jackson
라이브러리를 사용하면 역직렬화를 위해 빈생성자와 모든 필드의Getter
가 필요한 것이다.
@NoArgsConstructor
@AllArgsConstructor
public class ProductCache implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private static final String KEY_FORMAT = "product:%d";
private Long id;
private Long hubId;
private Long companyId;
private Integer quantity;
private String name;
// ...
public String getKey() {
return String.format(KEY_FORMAT, id);
}
}
class
→ JSON
으로 직렬화 할 때에도 getter
가 필요한 것일까?
dto 에서 @Getter
어노테이션을 제거하고 데이터를 저장해봤다.
{
"@class":"com.sparta.logistics.product.infrastructure.cache.dto.ProductCache",
"key":"product:1"
}
놀랍게도 key만 저장되었다.
해결 방법
@Getter
어노테이션을 선언하여 모든 필드에 대해 getter 메서드를 생성했다.
getKey()
메서드명을 key()
로 변경하여 직렬화 시 key라는 필드 생성을 방지했다.
결론
jackson
라이브러리는 직렬화/역직렬화 시 getXxx()
메서드를 통해 필드를 파악한다.
직렬화 시에 getXxx()
를 통해 반환받는 값을 xxx
필드에 매핑시켜 JSON
으로 파싱한다.
getKey() 라는 메서드가 key:1 을 반환한다면 “key” : “key:1” 으로 직렬화 된다.
즉, jackson을 통한 직렬화 / 역직렬화를 위해선 빈생성자와 getter가 강제된다.
이는 객체의 캡슐화를 해칠 수 있는 우려가 있기에
도메인 객체를 직렬화하는 경우 캡슐화를 고려하거나다른 라이브러리 사용을 고려해야할 것 같다.
jackson을 사용하여 직렬화 / 역직렬화를 수행한다면 도메인 객체가 아닌 dto를 통해 동작하도록 하자.
'오류 해결기' 카테고리의 다른 글
이벤트 기반 비동기 통신 구현 및 Kafka를 사용한 이유 (feat. RabbitMQ) (2) | 2024.12.20 |
---|---|
[Spring Security + JWT] 크롬과 Postman의 인증 결과가 다른현상 (0) | 2023.07.14 |
[Git] 작업이 종료 직전 브랜치를 잘못 생성한 걸 깨달았다,, (0) | 2023.06.30 |
[Git] PR을 메인 브랜치로 잘못 머지한 상황 (0) | 2023.06.30 |
[DB] JPA 무한참조 문제 (0) | 2023.03.18 |