TIL 20240316 최종 프로젝트(작심백일) - (12) 직렬화, 역직렬화 문제
목표 검색을 페이지네이션으로 만들고,
레디스를 이용해서 최근 검색어를 만들려고 했다.
최근 검색 기록이 꽉 찼을 경우 새로 insert 하기 위해선 매번 가장 오래된 기록을 제거 해야 하므로 정렬 후 delete 하는 과정이 따로 필요했다!
이러한 부분을 효율적으로 처리하기 위해 처리 속도가 빠른 인메모리 db인 redis를 사용하게 되었다.
dto 에 serializable를 추가해주고,
data class SearchResponse(
val id: Long,
val title: String
):Serializable
@Operation(summary = "목표 검색")
@GetMapping("/search")
fun searchByResolution(
@RequestParam title: String,
@PageableDefault(size = 10, sort = ["title"]) pageable: Pageable
): ResponseEntity<Page<SearchResponse>> {
return ResponseEntity.status(HttpStatus.CREATED).body(resolutionSearchService.searchByResolution(title, pageable))
}
@Cacheable(key = "#title", cacheNames = ["title"], condition = "#title != null")
override fun searchByResolution(title: String, pageable: Pageable): Page<SearchResponse> {
return resolutionRepository.searchByTitle(title, pageable)
}
여기까지는 cache를 사용해서 목표검색을 별 문제없이 구현 할 수 있었다.
우선, LocalDateTime은 변환이 안되는 에러가 발생했으므로
찾아보니 여러 해결 방법이 있었고
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
의존성 추가해준 다음!
val createdAt: String,
나는 createdAt를 String으로 적용해줬더니,, 아래처럼 직렬화, 역직렬화 에러가 발생하였다.
[]가 조회가 되어야 하는데 {} 가 조회되는 일이 발생하였는데
씁,, 생성하는 것부터 문제가 발생했던건가? 싶었다.
우선적으로 했던 방법은 obejectMapper를 template에 적용하고 남발했는데,, 전혀 소용이 없었다.
안되는 이유를 찾아서 서치를 엄청 많이 했었다,,,
val javaTimeModule = JavaTimeModule()
// LocalDateTime을 원하는 포맷으로 직렬화하기 위한 설정
javaTimeModule.addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
objectMapper.registerModule(javaTimeModule)
그래서 template에서 이러한 많은 것들을 추가했었다.
template에 있는 것을 지우고 서비스에 objectMapper를 부르고 objectMapper.writeValueAsString(Response) return logs.map { it }으로 작성해주었다. 이미 dto에서 Serializable을 해줬기 때문에 꼬여서 발생한 것으로 추정된다.
생성자가 없다고 떠서 주 생성자, 부 생성자 순으로 해봤는데,
주 생성자로는 안됐는데!!
부 생성자(constructor)로 아래처럼 해보니까 제대로 작동되었다.
@Bean
fun redisTemplate(): RedisTemplate<*,*>{
val genericJackson2JsonRedisSerializer = GenericJackson2JsonRedisSerializer()
return RedisTemplate<Any, Any>().apply{
this.connectionFactory = lettuceConnectionFactory()
this.keySerializer = StringRedisSerializer()
this.valueSerializer = genericJackson2JsonRedisSerializer
this.hashKeySerializer = StringRedisSerializer()
this.hashValueSerializer = genericJackson2JsonRedisSerializer
}
}
objectMapper 관련 로직들을 다 정리해주었다.
override fun saveRecentSearchLog(title: String, userId: Long?){
val now = LocalDateTime.now()
userId?.let {
val key: String = "SearchLog${userId}"
val value: SearchLogSearchResponse = SearchLogSearchResponse(title = title, createdAt = now.toString())
val size: Long? = redisTemplate.opsForList().size(key)
if (size == 10L) {
redisTemplate.opsForList().rightPop(key)
}
redisTemplate.opsForList().leftPush(key, value)
}
}
override fun findRecentSearchLog(userId: Long): List<SearchLogSearchResponse> {
val key = "SearchLog${userId}"
val logs: List<SearchLogSearchResponse> = redisTemplate.opsForList().range(key, 0, -1) ?: emptyList()
val objectMapper = ObjectMapper()
objectMapper.writeValueAsString(SearchLogSearchResponse)
return logs.map { it }
}
이렇게 최종적으로 코드를 수정해주었더니 문제없이 작동하였다!!!
원래는 디비를 접근하여 userId를 일일이 받았었는데,,
팀원분이 레디스를 사용하면 디비를 최대한 접근을 안하는 로직을 짜는 것이 좋다!! 라는 말씀을 주셔서 관련된 로직은 다 정리하여 지워주었다.