Backend/spring

DTO 작성 방법 - Array 및 Object 타입

dddzr 2024. 11. 18. 23:14

DTO(Data Transfer Object)는 애플리케이션의 계층 간 데이터를 간단히 전달하기 위해 사용되는 객체입니다. 특히 DTO는 데이터를 캡슐화하여 객체로 전달함으로써 데이터 일관성을 유지하고, 불필요한 비즈니스 로직을 생략함으로써 성능을 높일 수 있습니다. 이 글에서는 DTO의 작성 방법을 키워드 및 예제와 함께 설명하며, Array나 Object 타입의 필드가 포함된 경우의 작성법도 함께 다루겠습니다.


1. DTO 작성의 기본 구성

DTO는 주로 다음 요소로 구성됩니다.

  • 필드: DTO가 전달할 데이터. 주로 private 접근 제어자로 선언하여 외부 접근을 제한합니다.
  • 기본 생성자: 객체 생성 시 기본 상태로 생성할 수 있도록 빈 생성자를 추가합니다.
  • 게터/세터: 각 필드에 접근할 수 있도록 gettersetter 메서드를 제공합니다.

1.1 private 필드 접근 제어자

DTO의 모든 필드는 private 접근 제어자를 붙여 외부 클래스에서 직접 접근할 수 없도록 합니다. 이를 통해 캡슐화가 이루어지며, 데이터 보호와 무결성을 유지할 수 있습니다. 외부에서는 게터와 세터를 통해 필드에 접근하고 값을 수정할 수 있습니다.

private String username;


1.2 기본 생성자

기본 생성자는 클래스에 매개변수가 없는 생성자를 정의하는 것입니다. Jackson과 같은 JSON 직렬화 라이브러리나 JPA와 같은 프레임워크는 객체를 생성할 때 기본 생성자가 필요합니다. 기본 생성자는 코드 안에 아무것도 작성하지 않아도 무방합니다.

public UserDTO() {}

 

2. Array 및 Object 타입 필드의 작성 방법

DTO에서 Array 또는 Object 형태의 필드를 가질 때는 다음과 같은 방법을 사용합니다.

 

*타입 정의 잘 못 했을 때 발생하는 에러

더보기
더보기

{ "timestamp": "2024-10-28T00:54:49.227+00:00", "status": 415, "error": "Unsupported Media Type", "trace": "org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'application/json;charset=UTF-8' is not supported\r\n\tat org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:236)\r\n\tat …, "message": "Content-Type 'application/json;charset=UTF-8' is not supported.", "path": "/addUser" }

 

List<>로 작성 전 요구되는 타입 그대로인 Array라고 임시로 써 뒀을 때 에러 발생.

Jackson 라이브러리는 JSON과 Java 객체 간의 변환을 담당하는데, Array 타입을 사용할 경우 변환 과정에서 문제가 발생할 수 있다.

 

2.1 Array 타입 필드 (List 사용)

Java에서 Array 타입을 다룰 때는 List 인터페이스를 사용하는 것이 일반적입니다. 이는 데이터 구조를 유연하게 사용할 수 있도록 하고, JSON 직렬화 시 배열 형식으로 변환이 가능하다는 장점이 있습니다.

import java.util.List;

public class UserDTO { 
    private String username; // 사용자명
    private List<String> hobbies; // 사용자의 취미 목록 (Array 형식)

    // 기본 생성자
    public UserDTO() {}

    // 게터/세터 메서드
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }
}

 

2.2 Object 타입 필드

Object 타입의 필드는 DTO 내에 중첩 클래스로 정의할 수도 있고, 외부에 독립적인 클래스로 정의할 수도 있습니다. 

2.2.1 중첩 클래스로 정의

만약 중첩 클래스가 외부 클래스와 독립적으로 사용될 가능성이 있다면 public static으로 정의하는 것이 좋습니다. 예를 들어, Address 클래스는 독립적으로 사용할 수 있기 때문에 public static으로 선언되었습니다.

@Data //Data 어노테이션 사용으로 생성자, getter, setter 생략
public class UserDTO { 
    private String username; // 사용자명
    private Address address; // 사용자 주소 (Object 형식)
    
    /* Address 클래스는 UserDTO와 밀접한 관계를 가지면서도
      독립적으로 사용할 수 있도록 public static으로 정의 */
    //@Data 여기도 쓸 수 있다.
    public static class Address {
        private String city; // 도시명
        private String street; // 거리명

        public Address() {}

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }

        public String getStreet() {
            return street;
        }

        public void setStreet(String street) {
            this.street = street;
        }
    }
}

2.2.2 중첩 클래스 선언 시 에러( public static )

"Type definition error: [simple type, class com.naverapicalltest.apicalltest.dto.User$Address ]"

더보기
더보기

Static 안 붙였을 때 발생하는 에러.

 

  • Address 클래스의 타입이 잘못 정의되어 있거나, JSON 데이터를 처리하는 과정에서 Jackson 라이브러리(ObjectMapper, annotation.JsonInclude 등 사용)가 해당 타입을 해석하는 데 문제가 생겼다는 의미.
  •  Jackson이 비정적(non-static) 중첩 클래스를 인스턴스화하려고 할 때, 외부 클래스의 인스턴스를 먼저 알아야 하는데, Jackson은 이를 처리할 방법이 없어 오류가 발생.
  • static 키워드를 사용하면, 그 클래스가 외부 클래스의 인스턴스와 관계없이 독립적으로 존재할 수 있게 된다.

 

2.2.2 외부 클래스가 더 적합한 경우의 예시

UserDTO와는 독립적으로 여러 객체에서 사용할 수 있는 정보라면 중첩 클래스 대신 외부에 독립적인 클래스로 정의하는 것이 더 좋습니다. 예를 들어, Adress가 유저 정보, 배송지 정보 등 여러군데서 이용될 경우 외부 클래스가 적합합니다.

외부 클래스는 하나의 dto파일에서 class 외부에 작성해도 외부 클래스 이지만,

보통 각각의 파일로 관리합니다. (UserDTO.java, ShopingDTO.java, Address.java)

@Data
public class UserDTO { 
private String name; 
private Address address; }

@Data
public class ShippingDTO { 
private String trackingNumber; 
private Address destination; }

@Data 
public class Address { 
private String street; 
private String city; }