Serializing PageImpl instances as-is is not supported 경고 발생
2024. 7. 20. 10:14ㆍTIL
❎ 문제발생
프로젝트 진행중 와인 프로모션 DB를 조회할때 FETCH JOIN을 사용하여 N+1 문제를 해결하려고 시도하였는데, 직렬화 관련 문제가 발생하였다.
PageModule$PlainPageSerializationWarning : Serializing PageImpl instances as-is is not supported, meaning that there is no guarantee about the stability of the resulting JSON structure!
For a stable JSON structure, please use Spring Data's PagedModel (globally via @EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO))
or Spring HATEOAS and Spring Data's PagedResourcesAssembler as documented in https://docs.spring.io/spring-data/commons/reference/repositories/core-extensions.html#core.web.pageables.
🆘 해결 과정
해당 경고 메시지는 PageImpl 인스턴스를 직렬화할 때 JSON 구조의 안정성을 보장하지 못한다는 내용이며 Spring Data의 PageModel을 사용하는것을 권고하고 있다. 즉 페이지네이션을 적용할때 발생하는 문제이다.
의존성 추가
Spring HATEOAS와 Spring Data의 PagedResourcesAssembler를 사용하여 Page를 PagedModel로 변환할 수 있기 때문에 hateoas 의존성을 추가해줘야 한다.
dependencies {
implementation("org.springframework.boot:spring-boot-starter-hateoas")
}
PagedResourcesAssembler를 빈으로 등록
package sparta.nbcamp.wachu.infra.hateoas
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.web.PagedResourcesAssembler
import org.springframework.data.web.config.PageableHandlerMethodArgumentResolverCustomizer
import org.springframework.hateoas.config.EnableHypermediaSupport
@Configuration
@EnableHypermediaSupport(type = [EnableHypermediaSupport.HypermediaType.HAL])
class HateoasConfig {
@Bean
fun pagedResourcesAssembler(): PagedResourcesAssembler<*> {
return PagedResourcesAssembler<Any>(null, null)
}
@Bean
fun pageableCustomizer(): PageableHandlerMethodArgumentResolverCustomizer {
return PageableHandlerMethodArgumentResolverCustomizer {
it.setOneIndexedParameters(true)
}
}
}
WinePromotion을 감싸는 모델 클래스를 생성
ATEOAS (Hypermedia as the Engine of Application State) 원칙에 따라, 리소스를 감싸는 래퍼를 사용하여 각 리소스에 대한 링크를 포함시키기 위해서 모델 클래스를 생성한다.
package sparta.nbcamp.wachu.domain.wine.entity
import org.springframework.hateoas.EntityModel
import org.springframework.hateoas.server.core.Relation
@Relation(itemRelation = "winePromotion", collectionRelation = "winePromotions")
class WinePromotionModel(winePromotion: WinePromotion) : EntityModel<WinePromotion>(winePromotion)
WinePromotion을 WinePromotionModel로 감싸는 어셈블러 생성
WinePromotion 엔티티를 WinePromotionModel로 변환하는 어셈블러
package sparta.nbcamp.wachu.domain.wine.entity
import org.springframework.hateoas.server.RepresentationModelAssembler
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder
import org.springframework.stereotype.Component
import sparta.nbcamp.wachu.domain.wine.controller.WineController
@Component
class WinePromotionAssembler : RepresentationModelAssembler<WinePromotion, WinePromotionModel> {
override fun toModel(entity: WinePromotion): WinePromotionModel {
val model = WinePromotionModel(entity)
model.add(
WebMvcLinkBuilder.linkTo(
WebMvcLinkBuilder.methodOn(WineController::class.java)
.getPromotionWineList(direction = "asc", sortBy = "price", page = 0, size = 10)
).withSelfRel()
)
return model
}
}
Service 에서 PagedModel 적용
override fun getPromotionWineList(
page: Int,
size: Int,
sortBy: String,
direction: String
): PagedModel<WinePromotionModel> {
val pageable: Pageable = PageRequest.of(page, size, getDirection(direction), sortBy)
val promotionsPage: Page<WinePromotion> = winePromotionRepository.findPromotionWineList(pageable)
return pagedResourcesAssembler.toModel(promotionsPage, winePromotionAssembler)
}
Controller 반환값 PagedModel로 수정
@RequestMapping("/api/v1/wines")
@RestController
class WineController(
private val wineService: WineService,
){
@GetMapping("/promotion")
fun getPromotionWineList(
@RequestParam(value = "page", defaultValue = "0") page: Int,
@RequestParam(value = "size", defaultValue = "10") size: Int,
@RequestParam(value = "sort_by", defaultValue = "createdAt") sortBy: String,
@RequestParam(value = "sort_direction", defaultValue = "asc") direction: String,
): ResponseEntity<PagedModel<WinePromotionModel>> {
return ResponseEntity.status(HttpStatus.OK).body(
wineService.getPromotionWineList(
page = page,
size = size,
sortBy = sortBy,
direction = direction
)
)
}
}
✔ 해결
이제는 쿼리를 날려도 해당 경고창이 발생하지 않는 것을 확인할 수 있다.
'TIL' 카테고리의 다른 글
pagedModel fetchJoin 적용후 redis Cache 적용시 발생하는 오류 (0) | 2024.07.22 |
---|---|
QueryDslSupport (0) | 2024.07.21 |
Fetch Join (0) | 2024.07.19 |
최종 프로젝트 (7일차) (0) | 2024.07.18 |
@AfterEach 그리고 @DirtiesContext (테스트 코드) (0) | 2024.07.17 |