inblog logo
|
DailyCoding
    Spring Boot

    10-4. DTO와 객체 복사

    박은서's avatar
    박은서
    Feb 06, 2026
    10-4. DTO와 객체 복사
    Contents
    1. DTO와 객체 복사1️⃣ SSR과 API 통신의 차이2️⃣ DTO(Data Transfer Object)가 필요한 이유3️⃣ 객체 복사(매핑)4️⃣ DTO 매핑 책임은 어디에 두는가?5️⃣ 연관 관계 데이터와 DTO6️⃣ DTO는 “보안 + 정책”의 역할도 함7️⃣ 이름 있는 생성자 (Static Factory Method)의 의미8️⃣ 전체 핵심 요약9️⃣ 참고 코드🔟 실습

    1. DTO와 객체 복사

    1️⃣ SSR과 API 통신의 차이

    1) SSR (Server Side Rendering)

    • 서버가 HTML을 직접 만들어서 클라이언트(브라우저)에 전달
    • Mustache 같은 템플릿 엔진에서
      • 객체 전체를 전달하더라도
      • 화면에 필요한 필드만 선택적으로 렌더링 가능
    • 즉, 객체에 password가 있어도 템플릿에서 쓰지 않으면 노출되지 않음
    ➡️ 렌더링 제어가 서버 템플릿 레벨에서 가능

    2) 모바일 앱 / SPA (JSON 기반 통신)

    • 브라우저가 아니라 앱이 서버와 데이터(JSON) 로 통신
    • 서버에서 객체를 그대로 반환하면
      • 객체의 모든 필드가 JSON으로 직렬화되어 전송될 수 있음
      • 불필요하거나 민감한 정보(password 등)가 네트워크를 통해 전달됨
    ➡️ 문제는 “화면에 보이느냐”가 아니라 “전송되느냐”임

    2️⃣ DTO(Data Transfer Object)가 필요한 이유

    1) 핵심 이유

    • 클라이언트에 필요한 데이터만 보내기 위해
    • 보내면 안 되는 데이터를 구조적으로 차단하기 위해

    2) 엔티티(Entity) vs DTO

    • 엔티티
      • 도메인/DB 중심
      • 내부 로직, 연관관계, 민감 정보 포함 가능
    • DTO
      • 화면/API 응답 중심
      • 필요한 데이터만 포함
      • 구조가 엔티티와 달라도 됨
    ➡️ 엔티티 ≠ 응답 모델

    3️⃣ 객체 복사(매핑)

    1) 개념

    • DTO는 보통 기존 객체(엔티티)를 그대로 쓰지 않고
    • 필요한 정보만 새 객체로 옮겨 담는 과정을 거침
    • 이 과정을 흔히 아래와 같이 부름
      • 객체 복사
      • 매핑(mapping)
      • 변환(transform)

    2) 중요한 점

    • 단순 복사만 하는 게 아니라 아래 기능 모두 가능
      • 필드 선택
      • 구조 변경
      • 값 가공
      • 조건 필터링

    4️⃣ DTO 매핑 책임은 어디에 두는가?

    1) 좋은 방향

    • “이 DTO는 이 엔티티로부터 만들어진다”는 책임을
      • DTO 내부에 두는 것이 좋음

    2) 이유

    • 매핑 로직이 여기저기 흩어지지 않음
    • 수정 시 영향 범위가 줄어듦
    • 의도가 명확해짐

    5️⃣ 연관 관계 데이터와 DTO

    1) 엔티티 구조

    • 보통 DB 중심:
      • 1:N, N:1, N:M 관계
      • 객체 그래프가 깊고 복잡함

    2) DTO 구조

    • 화면/API 중심
      • 단순화된 구조
      • 필요한 데이터만 추출
      • 연관 객체 전체를 보내지 않아도 됨
    • DTO에서는 연관 객체를
      • 일부 필드만 뽑거나
      • 다른 타입으로 변환하거나
      • 개수를 제한하거나
      • 조건에 따라 필터링 가능

    6️⃣ DTO는 “보안 + 정책”의 역할도 함

    1) 보안 + 정책의 역할

    • DTO는 단순한 데이터 운반 객체가 아니라
      • 외부에 공개할 정보의 경계선
      • API 응답 정책의 구현체

    2) 예시

    • 비밀번호, 내부 플래그 제거
    • 관리자 전용 정보 제외
    • 특정 조건에 맞는 데이터만 응답
    ➡️ “DTO를 잘 설계하면 실수로 정보가 새는 구조가 안 나옴”

    7️⃣ 이름 있는 생성자 (Static Factory Method)의 의미

    1) 문제

    • 기본 생성자를 열어두면
      • 잘못된 조합의 필드
      • 의미 없는 상태의 객체가 쉽게 만들어짐

    2) 해결

    • 기본 생성자를 private로 막고
    • 목적에 맞는 이름 있는 생성자만 제공

    3) 효과

    • 객체 생성 의도가 명확해짐
    • 유효하지 않은 상태의 객체 생성을 원천 차단
    • 도메인 규칙을 코드 레벨에서 강제
    ➡️ new는 자유롭지만 위험
    ➡️ 이름 있는 생성자는 제한적이지만 안전

    8️⃣ 전체 핵심 요약

    • SSR에서는 템플릿이 출력 제어를 하지만
      • API 통신에서는 “전송 데이터 자체”를 제어해야 함
    • 엔티티를 그대로 반환하지 말고 DTO로 변환해서 응답
    • DTO는
      • 필요한 데이터만 담고
      • 구조를 단순화하고
      • 보안과 정책을 담당
    • 객체 생성/복사 방식은
      • 코드 품질, 유지보수성, 안정성에 직결됨

    9️⃣ 참고 코드

    DTO를 사용하는 이유

    🔟 실습

    0) User 클래스 (기본)

    C:\workspace\spring_lab\skillapp\src\main\java\ex01\User.java
    package ex01; import lombok.Data; @Data // Getter, Setter, toString public class User { private int id; private String username; private String email; }

    1) Setter 사용

    C:\workspace\spring_lab\skillapp\src\main\java\ex01\CopyEx01.java
    package ex01; import lombok.Data; @Data class UserDTO { private int id; private String username; private String email; } public class CopyEx01 { public static void main(String[] args) { // 1. Setter (가장 안 좋은 방법, 그래도 알고 있어야 함) User user = new User(); user.setId(1); user.setUsername("ssar"); user.setEmail("ssar@nate.com"); UserDTO dto = new UserDTO(); dto.setId(user.getId()); dto.setUsername(user.getUsername()); dto.setEmail(user.getEmail()); System.out.println(dto); } }
    notion image

    2) 생성자 사용

    C:\workspace\spring_lab\skillapp\src\main\java\ex01\CopyEx02.java
    package ex01; import lombok.Data; @Data class UserDTO2 { private int id; private String username; private String email; public UserDTO2(int id, String username, String email) { this.id = id; this.username = username; this.email = email; } } public class CopyEx02 { public static void main(String[] args) { // 2. 생성자 User user = new User(); user.setId(1); user.setUsername("ssar"); user.setEmail("ssar@nate.com"); UserDTO2 dto2 = new UserDTO2(user.getId(), user.getUsername(), user.getEmail()); System.out.println(dto2); } }
    notion image

    3) 생성자 매개변수(User 클래스 넘기기)

    C:\workspace\spring_lab\skillapp\src\main\java\ex01\CopyEx03.java
    package ex01; import lombok.Data; @Data class UserDTO3 { private int id; private String username; private String email; public UserDTO3(User user) { this.id = user.getId(); this.username = user.getUsername(); this.email = user.getEmail(); } } public class CopyEx03 { public static void main(String[] args) { // 3. 생성자의 매개변수에 User 클래스 넘기기 User user = new User(); user.setId(1); user.setUsername("ssar"); user.setEmail("ssar@nate.com"); UserDTO3 dto3 = new UserDTO3(user); System.out.println(dto3); } }
    notion image

    4) 객체 2개 → DTO 옮기기 (생성자 매개변수로 객체 넘기기)

    C:\workspace\spring_lab\skillapp\src\main\java\ex01\CopyEx04.java
    package ex01; import lombok.Data; import java.util.ArrayList; import java.util.List; @Data class Board { // 1:N의 관계에서 Board가 1 private int id; private String title; private String content; private List<Reply> replies = new ArrayList<>(); } @Data class Reply { // 1:N의 관계에서 Reply는 N private int id; private String comment; } @Data class DetailDTO { private int id; private String title; private String content; private List<String> comments = new ArrayList<>(); public DetailDTO(Board board) { this.id = board.getId(); this.title = board.getTitle(); this.content = board.getTitle(); // for (int i = 0; i < board.getReplies().size(); i++) { // this.comments.add(board.getReplies().get(i).getComment()); // } // for문은 복잡! stream이 보기 편함! -> 그래서 stream 배울 것 for (Reply reply : board.getReplies()) { this.comments.add(reply.getComment()); } } } public class CopyEx04 { public static void main(String[] args) { // 3. 클래스로 넘기기 Reply r1 = new Reply(); r1.setId(1); r1.setComment("댓글1"); Reply r2 = new Reply(); r2.setId(2); r2.setComment("댓글2"); Reply r3 = new Reply(); r3.setId(3); r3.setComment("댓글3"); Board board = new Board(); board.setId(1); board.setTitle("제목1"); board.setContent("내용1"); board.setReplies(List.of(r1, r2, r3)); DetailDTO detailDTO = new DetailDTO(board); System.out.println(detailDTO); } }
    notion image
    package ex01; import lombok.Data; import java.util.ArrayList; import java.util.List; @Data class Board { // 1:N의 관계에서 Board가 1 private int id; private String title; private String content; private List<Reply> replies = new ArrayList<>(); } @Data class Reply { // 1:N의 관계에서 Reply는 N private int id; private String comment; } @Data class DetailDTO { private int id; private String title; private String content; private List<String> comments = new ArrayList<>(); public DetailDTO(Board board) { this.id = board.getId(); this.title = board.getTitle(); this.content = board.getTitle(); // for (int i = 0; i < board.getReplies().size(); i++) { // this.comments.add(board.getReplies().get(i).getComment()); // } // for문은 복잡! stream이 보기 편함! -> 그래서 stream 배울 것 for (Reply reply : board.getReplies()) { // if (id < 3) if (reply.getId() < 3) { this.comments.add(reply.getComment()); } } } } public class CopyEx04 { public static void main(String[] args) { // 3. 클래스로 넘기기 Reply r1 = new Reply(); r1.setId(1); r1.setComment("댓글1"); Reply r2 = new Reply(); r2.setId(2); r2.setComment("댓글2"); Reply r3 = new Reply(); r3.setId(3); r3.setComment("댓글3"); Board board = new Board(); board.setId(1); board.setTitle("제목1"); board.setContent("내용1"); board.setReplies(List.of(r1, r2, r3)); DetailDTO detailDTO = new DetailDTO(board); System.out.println(detailDTO); } }
    조건 추가
    notion image

    5) 이름이 있는 생성자

    C:\workspace\spring_lab\skillapp\src\main\java\ex02\NamedEx01.java
    객체 복사 X 객체 만드는 것 O
    package ex02; import lombok.Data; @Data class User { // 공통 private int id; private String username; private String password; private String type; // 학생, 선생 구분 // 학생 private String classRoom; // 교실 private String classYear; // 학년 // 선생 private String subject; // 담당 과목 private String teacherName; // 이름 private User() {} // 외부에서 절대 new 못함! 이름 있는 생성자에서 new 해줘야 함! // 이름 있는 생성자 (목적에 맞게 new 가능 / 대신 기본생성자 접근 못하게 private로 막아야 함) public static User createTeacher(int id, String username, String password, String subject, String teacherName) { User user = new User(); user.id = id; user.username = username; user.password = password; user.type = "선생"; user.subject = subject; user.teacherName = teacherName; return user; } public static User createStudent(int id, String username, String password, String classRoom, String classYear) { User user = new User(); user.id = id; user.username = username; user.password = password; user.type = "학생"; user.classRoom = classRoom; user.classYear = classYear; return user; } } public class NamedEx01 { public static void main(String[] args) { User student = User.createStudent(1,"ssar","1234","101", "3"); User teacher = User.createTeacher(2,"cos","1234","수학", "cos"); System.out.println(student); System.out.println(teacher); } }
    notion image
    Share article
    Contents
    1. DTO와 객체 복사1️⃣ SSR과 API 통신의 차이2️⃣ DTO(Data Transfer Object)가 필요한 이유3️⃣ 객체 복사(매핑)4️⃣ DTO 매핑 책임은 어디에 두는가?5️⃣ 연관 관계 데이터와 DTO6️⃣ DTO는 “보안 + 정책”의 역할도 함7️⃣ 이름 있는 생성자 (Static Factory Method)의 의미8️⃣ 전체 핵심 요약9️⃣ 참고 코드🔟 실습

    DailyCoding

    RSS·Powered by Inblog