1. 경고 메시지 발생
(1) 개발환경
개발 환경 | 버전 |
Java | 17 |
OpenJDK | 23.0.2 |
Spring Boot | 3.4.3 |
(2) 경고 발견
3.4.3 스프링 부트를 사용하는 프로젝트에서 Page를 이용하여 페이지네이션 진행을 하였습니다.
서버를 작동하니 아래의 경고 문구가 발생하였습니다.
2025-03-01T20:06:40.421+09:00 WARN 7226 --- [Project] [nio-8080-exec-1] ration$PageModule$WarningLoggingModifier : 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 인스턴스를 있는 그대로 직렬화(Serialization)하는 것은 지원되지 않으며,
결과로 생성되는 JSON 구조의 안정성이 보장되지 않습니다!
즉, SpringBoot에서 `Page<T>`(예: PageImpl)을 API응답으로 직접 반환하면,
JSON 구조의 안정성이 보장되지 않아 발생하는 경고 입니다.
(3) 경고 원인
[참고]
page 관련 spring 공식 문서
공식 문서에 들어가 경고의 이유를 알아보았습니다.
Spring MVC 컨트롤러에서 Page를 반환하면 Jackson이 자동 직렬화될 수 있지만,
PageImpl이 도메인 타입이라 API 변경 시 Json 구조가 깨질 수 있어 권장하지 않는다고 합니다.
🔍 Jackson이란?
Jackson은 Java 객체를 JSON 형식으로 변화(직렬화, Serialization)하거나,
JSON을 Java 객체로 변환(역직렬화, Desiralization)하는 라이브러리이다.
SpringBoot 는 Jackson을 사용하여 컨트롤러 응답값을 Json응답으로 변환합니다.
🔍 도메인 타입이란?
도메인 타입은 비즈니스 로직을 표현하는 핵심 객체로,
애플리케이션에서 주요 데이터와 동작을 담고 있는 클래스를 의미합니다.
🔍 PageImpl이 도메인 타입인 이유
PageImpl은 Page<T>인터페이스를 구현한 Spring Data Jpa의 기본 페이지네이션 클래스입니다.
이는 비즈니스 데이터를 포함하고 있고, 페이징 정보를 가지고 있습니다.
또한 도메인 객체를 포함한 컬렉션을 다루는 Spring Data Jpa 구현체입니다.
즉, 단순한 데이터 모델이 아니라,
페이징과 관련된 비즈니스로직이 포함된 객체이므로 도메인 타입입니다.
🔍 도메인 타입이 API 변경 시 JSON 구조가 깨질 수 있는 이유
도메인 타입은 프레임워크 내부에서 비즈니스 로직을 수행하는 객체이기 때문에 내부적으로 관리됩니다.
따라서, 내부 필드나 메소드가 변경될 가능성이 큽니다.
이 변경이 발생하면, JSON 변환 시 필드구조가 달라져서 API 응답이 깨질 수 있습니다.
2. 해결 방법
위 문제를 해결하기 위해 Spring Data 3.1부터 경고 로그로 알리기 시작했습니다.
해결 방법은 아래의 2개의 방법 중 1가지를 선택하면 됩니다.
(1) @EnableSpringDataWebSupport 추가
`@SpringBootApplication`를 사용한 application 클래스에
`@EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO)` 를 추가합니다.
@SpringBootApplication
@EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO)
public class ProjectApplication {
public static void main(String[] args) {
SpringApplication.run(ProjectApplication.class, args);
}
}
(2) PagedModel<T> 사용
Page<T> 대신 PageModel<T> 를 이용합니다.
[변경 전 코드]
@GetMapping("/stores")
public ResponseEntity<Page<StoreResponse>> getStores(
@RequestParam(required = false) String StoreName,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size
) {
return ResponseEntity.ok(storeService.getStores(StoreName, page, size));
}
[변경 후 코드]
@GetMapping("/stores")
public ResponseEntity<PagedModel<StoreResponse>> getStores(
@RequestParam(required = false) String StoreName,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size
) {
return ResponseEntity.ok(new PagedModel<>(storeService.getStores(StoreName, page, size)));
}
3. 결과
[적용 전 페이지 반환 값]
[적용 후 페이지 반환 값]