[Spring] DTO에 기본 생성자 & getter가 필요한 이유

박성우·2023년 7월 13일
3

Spring

목록 보기
7/10

DTO를 항상 사용할 때마다 @NoArgsContructor와 @Getter 어노테이션을 붙여주지 않으면 에러가 뜨는 이유가 궁금해서 간단하게 알아보고 정리하게 되었다.

우선, HTTP 요청으로부터 JSON 형식의 데이터를 받을 때, RestController에서 @RequestBody를 통해 DTO로 바인딩을 해주는 역할을 ObjectMapper가 해준다.

ObjectMapper

Jackson 라이브러리에서 제공하는 클래스로써, Java Object ↔ JSON 파싱
직렬화 (Serialize) : Java Object → JSON
역직렬화 (Deserialize) : JSON → Java Object

이때, JSON으로부터 받아온 데이터를 가지고 DTO로 변환할 때 DTO의 기본 생성자를 이용하여 껍데기를 먼저 만드는 것이다.

그 다음으로, JSON의 필드와 DTO의 필드를 매칭시켜야하는데 이때는 Java 객체의 getter 및 setter 메소드의 이름을 파싱해서 매칭시킨다고 한다.

getter와 setter 메소드는 get 또는 set 뒤에 필드명이 붙기 때문에 이렇게 필드명을 읽어와서 변수를 매칭하는 것이다.

마지막으로, 생성된 DTO 객체의 매칭된 필드에 값을 넣어주기만 하면되는데, 이때 값을 넣어주는 과정에서 Reflection이라는 API를 이용해서 값을 넣어준다.

Reflection

  • Runtime에 동적으로 특정 Class의 정보를 추출할 수 있는 프로그래밍 기법
  • 구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 Java API

일반적으로 객체의 값을 넣어줄 때는 setter를 떠올리겠지만 Reflection을 이용하면 단순히 필드명만 알아도 해당 필드를 조작할 수 있기 때문에 setter가 없어도 된다.

간단하게 Reflection을 이용하여 필드를 조작하는 예제를 보면,

public class Main {
    public static void main(String[] args) throws Exception {
        UserDTO userDTO = new UserDTO();

        Class<?> clazz = UserDTO.class;
        Field field = clazz.getDeclaredField("username");
        field.setAccessible(true);
        field.set(userDTO, "myName");

        System.out.println(userDTO.getUsername()); // 출력: myName
    }
}

class UserDTO {
    private String username;

    public String getUsername() {
        return username;
    }
}

이런 식으로 필드명을 통해 필드를 읽어오며(getDeclaredField), setAccessible() 메소드를 이용하면 해당 필드가 private으로 선언이 되어있든, final로 선언이 되어있든 상관없이 접근 가능하게 해준다.

getter가 필수인 것이 아니다. getter 또는 setter 둘 중 하나는 존재해야 ObjectMapper가 바인딩 시 필드 매칭이 가능하고 그 후로는 Reflection을 이용해서 값을 할당하기 때문에 둘 중 하나만 있어도 되지만 굳이 그 둘 중 setter를 쓸 이유가 없을 뿐이다.


Entity에도 기본 생성자가 필요하다

DTO 뿐만 아니라, JPA의 Entity 또한 기본 생성자가 없으면 에러가 발생한다.

그 이유는 Entity 생성에도 Reflection API가 사용되기 때문이다.

위에서도 나왔지만, Reflection은 동적 바인딩을 제공한다.

애초에 Entity의 필드 값은 컴파일 시점에 생성자의 인자로 넣어줄 수 있는 것이 아니기 때문에, 기본 생성자를 통해 객체를 생성해놓고 Reflection을 통해 동적으로 필드값을 매핑한다.

❗단, Entity의 기본 생성자는 public 또는 protected로 선언해야한다. private으로 선언 시 Lazy Loading을 통한 객체 참조가 일어날 때, 실제 객체를 상속받는 Proxy 객체가 필요한데 Proxy 객체를 생성할 때 super를 호출할 수 없게 될 것이고, 결국 객체 생성이 되지 않을 것이기 때문이다.


Reference

profile
Backend Developer

0개의 댓글