Spring 5.2.X 버전에서 @RequestParam required = true 옵션은 null safety 하지 않다.
kindof
·2024. 1. 28. 16:26
2024년 1월 현재 기준으로 개인 프로젝트나 가까운 시일 내에 시작한 프로젝트라면 Springboot 3.X 버전, Spring 6.X 버전을 사용하게 됩니다.
하지만 프로젝트 개발을 한 지 오래된 서비스라면 Spring, Springboot 버전이 꽤 낮을 수 있는데요.
예를 들어, 3년 전인 2021년에 개발을 시작한 프로젝트만 하더라도 당시 Springboot의 최신 버전은 2.3.1.RELEASE 였으며 이 때 호환되는 Spring 버전은 5.2.X 이었습니다.
이번 글에서는 이런 점을 심각하게 고려하지 않고 개발을 하다가 마주친 문제에 대해 정리해보려고 합니다.
바로 예제 코드를 보겠습니다.
@GetMapping("/v1/book")
public Book getBook(@RequestParam(value = "bookId", required = true) Long bookId) {
log.info("bookId = {}", bookId);
Book book = bookRepository.findById(bookId).orElse(null);
if (book == null) {
return null;
}
// 비즈니스 로직 ...
이 코드에서 @RequestParam으로 들어오는 bookId는 null이 될 수 있을까요?
아마 최근에 이런 코드를 작성해보신 분이라면 bookId에 대한 null 체크를 따로 하지 않아도 된다는 것을 알고 계실 것입니다.
하지만 Spring 5.2.X 버전까지는 required = true 옵션이 Empty, Null 파라미터에 대한 체크를 하지 않았는데요.
실제로 테스트를 해보기 위해 Springboot 2.3.12.RELEASE 버전(Spring 5.2.15.RELEASE Compile dependency) 환경을 만들어보았습니다.
아래와 같이 build.gradle을 수정하고 jdk = 1.8, 해당 버전과 호환되는 Import 문을 작성해주시면 됩니다.
plugins {
id 'java'
id 'org.springframework.boot' version '2.3.12.RELEASE'
id 'io.spring.dependency-management' version '1.1.0'
}
그리고 위에서 작성한 API 에 대한 호출을 해봤는데요.
실제로 bookId에 대한 값을 입력하지 않고 API 호출을 하면 bookId 에는 null 값이 들어가게 되고, JpaRepository에서 null 을 통해 조회를 하는 순간 IllegalArgumentException이 발생하는 것을 볼 수 있습니다.
required = true 옵션이 null 요청을 EntryPoint에서 자르지 못하기 때문인데요. 이를 간과하고 코드를 작성하게 되면 위와 같이 예상치 못한 예외를 발생시켜 장애를 발생시킬 수 있다는 것이 포인트입니다.
그렇다면 최신 버전의 Spring에서는 required = ture 옵션이 null을 방지할까요? 아래와 같이 최신 버전의 Springboot 환경에서 다시 테스트를 진행해보겠습니다. 마찬가지로 jdk는 17로 올려주셔야 합니다.
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.0'
id 'io.spring.dependency-management' version '1.1.0'
}
똑같은 API 호출을 하면서 bookId 파라미터에 값을 할당하지 않았습니다.
그 결과, 이번에는 400 Bad Request 응답을 리턴하며 MissingServletRequestParameterException이 발생합니다.
즉, 최신 버전의 스프링에서는 required = true 옵션이 파라미터의 Null 체크를 한다는 것입니다.
왜 이런 차이가 있을까요? 오랜 시간 구글링을 하다가 spring 공식 프로젝트의 한 이슈를 발견했습니다.
해당 이슈에서 언급된 것처럼 Spring 5.3 버전 이후부터에서야 required=true 옵션이 non-null 값에 대한 Conversion을 지원하게 되었다는 것입니다.
따라서, 그 이전에는 null이 될 수 있는 파라미터에 대해서는 required=false 혹은 @Nullable 어노테이션을 사용하라고 가이드하고 있는 것이죠.
처음 이 문제를 겪었을 때는 'required = true 옵션이 null 체크를 해주지 않는구나'라고 생각했지만, 집에 와서 거의 비슷한 코드로 재현을 해보니 실제로는 null 체크가 되었습니다.
그래서 오랜 시간을 다른 원인이 있을까해서 많은 코드 베이스를 뒤져보았지만 왜 다른 결과가 나오는지 찾지 못했습니다.
그래서 혹여 Spring 버전에 따라 다르게 동작할까? 라는 생각으로 공식 문서를 뒤져서 원하는 답을 찾을 수 있었는데요.
이번 글에서 정리한 것처럼 스프링 프로젝트는 계속해서 버전업을 하고 있고, 그 전의 사용 패턴과 달라지는 부분이 반드시 존재하기 때문에 이런 점들을 한 번쯤은 생각해보고 개발한다면 앞으로도 도움이 될 것 같습니다.
'Spring & Springboot' 카테고리의 다른 글
jackson-databind, AUTO_DETECT_IS_GETTERS 옵션(is getter) (1) | 2024.02.14 |
---|---|
Spring RestTemplate 까보기 (1) | 2024.01.11 |
Spring READ 관련 API에서 @Transactional(readOnly = true)는 필수인가? (0) | 2023.11.10 |
장애 전파 대응을 위한 Resilience4j Circuit Breaker 실습 (0) | 2023.11.06 |
JPA 트랜잭션 격리 수준과 낙관적 락 동작을 테스트해보자. (0) | 2023.10.15 |