최종 프로젝트

TIL 20240316 최종 프로젝트(작심백일) - (12) 직렬화, 역직렬화 문제

개발자 백구 블로그 2024. 3. 16. 20:06

목표 검색을 페이지네이션으로  만들고, 

 

레디스를 이용해서 최근 검색어를 만들려고 했다.

 

최근 검색 기록이 꽉 찼을 경우 새로 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으로 적용해줬더니,,  아래처럼 직렬화, 역직렬화 에러가 발생하였다.

 

 

https://brunch.co.kr/@oemilk/179

 

 

[]가 조회가 되어야 하는데 {} 가 조회되는 일이 발생하였는데

 

씁,, 생성하는 것부터 문제가 발생했던건가? 싶었다.

 

 

 

우선적으로 했던 방법은 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를 일일이 받았었는데,, 

팀원분이 레디스를 사용하면 디비를 최대한 접근을 안하는 로직을 짜는 것이 좋다!! 라는 말씀을 주셔서 관련된 로직은 다 정리하여 지워주었다.