[개발 일기] 2025.03.04 - RequestDto에 Getter가 없으면 예외?

2025. 3. 4. 13:26·개발 일기

💡 개요

오늘은 자바에서 컨트롤러가 @RequestBody 를 전달받을 때 사용하는 ContollerRequestDto에서 기본생성자, Getter, Setter가 필요한 이유에 대해 정리해 보자.

 

 

 

📕 Jackson

Jackson은 자바 환경에서 직렬화와 역직렬화를 담당하는 라이브러리이다.

 

 

이를 통해 자바 애플리케이션 외부(React 같은 프론트엔드)에서 자바 애플리케이션 내부(Spring 서버)로 JSON 형태의 데이터를 역직렬화(자바 외부 → 자바 내부)할 수 있다.

 

 

Jackson이 @RequestBody 를 생성하는 방식은 다음과 같다.

 

 

  1. 기본 생성자를 사용해 비어있는 객체를 생성한다.

  2. JSON에 있는 값을 객체의 멤버 변수에 할당한다.

  3. RequestBody 객체가 완성된다.

 

 

 

🛠️ Getter가 없는 Dto

 

아래 코드는 기본 생성자를 사용했지만, Getter나 Setter가 없어서 MemberLoginRequest의 private 멤버 변수에 값을 할당할 수 없다.

 

@NoArgsConstructor
public class MemberLoginRequest {
    private String email;
    private String password;

    public MemberLoginServiceRequest toService() {
        return MemberLoginServiceRequest.builder()
                .email(email)
                .password(password)
                .build();
    }
}

 

 

이 DTO를 사용하는 로그인 기능에 값을 확실하게 넣어서 요청을 보내도 다음과 같은 예외가 발생한다.

 

{
    "code": "EMAIL_CANNOT_BE_NULL",
    "message": "이메일이 NULL일 수 없습니다."
}

 

 

아마 비어있는 MemberLoginRequest 객체는 생성했지만 값은 할당하지 못해서 NULL일 수 없다는 예외가 발생한 것 같다.

 

 

그렇기 때문에 값을 할당하기 위해선 Getter, Setter 를 사용해야 한다.

 

 

그런데 많은 개발자들은 알겠지만 Dto는 오로지 데이터를 전달하기 위한 목적으로 설계된 객체이기 때문에 웬만하면 불변으로 설계하는 편이다.

 

 

그렇기 때문에 이러한 불변 객체에 Setter 을 적용하는 건 올바르지 않다.

 

 

그래서 난 웬만하면 Getter만 사용해서 역직렬화를 진행한다.

 

 

 

🛠️ Getter만 사용해도 역직렬화가 동작하는 이유

 

하지만 Getter의 목적은 값을 할당하는 것이 아니라 값을 조회하는 것이다.

 

 

그런데 왜 Setter 없이 Getter 만으로도 값을 할당할 수 있을까?

 

 

이유는 Jackson의 공식 홈페이지에 그대로 나와있다.

 

‼️ 중요

To read Java objects from JSON with Jackson properly, it is important to know how Jackson maps the fields of a JSON object to the fields of a Java object, so I will explain how Jackson does that.

By default Jackson maps the fields of a JSON object to fields in a Java object by matching the names of the JSON field to the getter and setter methods in the Java object. Jackson removes the "get" and "set" part of the names of the getter and setter methods, and converts the first character of the remaining name to lowercase.

For instance, the JSON field named brand matches the Java getter and setter methods called getBrand() and setBrand(). The JSON field named engineNumber would match the getter and setter named getEngineNumber() and setEngineNumber().

 

Jackson removes the "get" and "set" part of the names of the getter and setter methods, and converts the first character of the remaining name to lowercase.

 

 

이 문장을 해석하면 Jackson은 get이나 set이 붙은 getter, setter 메서드가 있다면 get, set 을 지우고 지운 후 처음으로 오는 메서드명의 첫 글자를 소문자로 바꾸는 것이다.

 

 

보통 우리가 아는 Getter와 Setter는 다음과 같은 형태이다.

 

@NoArgsConstructor
public class MemberLoginRequest {
    private String email;
    private String password;

    public String getEmail() {
        return email;
    }
    
    public String getPassword() {
        return password;
    }
    
    
    public void setEmail(String email) {
        this.email = email;
    }
    
    public void setPassword(String password) {
        this.password = password;
    }
}

 

 

Jackson은 이 클래스에서 getEmail() 메서드를 찾으면, get을 제거한 후 Email의 첫 글자를 소문자로 바꾼다.

 

getEmail → Email → email

 

 

즉, email이라는 JSON 필드와 매칭되는 방식이다.

 

 

같은 원리로, getPassword()는 password 필드와 연결된다.

 

 

결국 Jackson은 Setter와 Getter를 사용할 때 개념적 차이는 없다.

 

 

그냥 메서드 명을 보고 어떤 값이 바인딩되어 있는지 확인하는 목적으로 사용하는 것이다.

 

 

만약 아래와 같은 코드를 사용한다면 어떻게 될까?

 

 

일단 email과 password라는 멤버변수의 값을 반환하기 때문에 Getter의 목적은 정상적으로 수행할 것이다.

 

public String getEemail() {
    return email;
}

public String getPpassword() {
    return password;
}

 

 

하지만 Jackson에선 해당 메서드가 어떤 값을 바인딩하고 있는지 알 수 없다. 그렇기 때문에 역직렬화도 정상적으로 동작하지 않는다.

 

getEemail → Eemail → eemail

 

 

Jackson을 사용해 값을 할당할 땐 Getter, Setter 메서드 명도 반드시 위 형태를 맞춰야 한다.

 

 

 

🛠️ Getter가 있는 Dto

 

그렇기 때문에 아래 방식을 사용하면 불변도 지키면서 안전하게 값을 바인딩할 수 있다.

 

@Getter
@NoArgsConstructor
public class MemberLoginRequest {
    private String email;
    private String password;

    public MemberLoginServiceRequest toService() {
        return MemberLoginServiceRequest.builder()
                .email(email)
                .password(password)
                .build();
    }
}

'개발 일기' 카테고리의 다른 글

[개발 일기] 2025.03.06 - JPQL vs QueryDsl  (0) 2025.03.06
[개발 일기] 2025.03.05 - 디스크, 파티션, 볼륨 (Docker)  (0) 2025.03.05
[개발 일기] 2025.03.03 - Thread 조정 (동기화)  (1) 2025.03.03
[개발 일기] 2025.03.02 - Thread 조정 (종료)  (1) 2025.03.02
[개발 일기] 2025.03.01 - Redis 직렬화  (1) 2025.03.01
'개발 일기' 카테고리의 다른 글
  • [개발 일기] 2025.03.06 - JPQL vs QueryDsl
  • [개발 일기] 2025.03.05 - 디스크, 파티션, 볼륨 (Docker)
  • [개발 일기] 2025.03.03 - Thread 조정 (동기화)
  • [개발 일기] 2025.03.02 - Thread 조정 (종료)
오도형석
오도형석
  • 오도형석
    형석이의 성장일기
    오도형석
  • 전체
    오늘
    어제
    • 분류 전체보기 N
      • MSA 모니터링 서비스
        • DB
      • 스파르타 코딩클럽
        • SQL
        • Spring
      • 백엔드
        • Internet
        • Java
        • DB
      • 캡스톤
        • Django
        • 자연어처리
      • Spring
        • JPA
        • MSA
      • ETC
        • ERROR
      • 개발 일기 N
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 인기 글

  • 태그

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
오도형석
[개발 일기] 2025.03.04 - RequestDto에 Getter가 없으면 예외?
상단으로

티스토리툴바