[SpringBoot] Entity를 DTO로 변환해야 하는 이유

2025. 11. 22. 19:30·Back-End/Spring

Spring Boot 프로젝트를 진행하다 보면 Entity, DTO, VO라는 용어를 자주 사용하게 된다.

처음에는 각각의 객체를 단순히 역할에 맞게 사용했지만, 프로젝트를 진행할수록 이 개념들을 한 번 정리할 필요가 있다고 느꼈다.

특히 API 응답을 만들 때 Entity를 그대로 반환하지 않고 DTO로 변환해서 내보내는 이유를 설명하지 못하고 있었다.

 

@GetMapping("/products/{id}")
public Product getProduct(@PathVariable Long id) {
    return productService.findById(id);
}

위 코드처럼 Entity를 그대로 반환하면 간단해 보인다.

하지만 실제 API에서는 Entity를 그대로 외부에 노출하기보다, 응답용 DTO로 변환해서 반환하는 경우가 많다.

 

그 이유를 이해하기 위해 먼저 Entity, DTO, VO의 차이를 정리해보았다.

Entity

Entity는 데이터베이스 테이블과 매핑되는 객체다.

JPA에서는 @Entity가 붙은 클래스가 여기에 해당한다.

@Entity
@Getter
@NoArgsConstructor
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private int price;

    private int stock;
}

Entity는 단순히 데이터를 담는 객체라기보다, 애플리케이션의 핵심 도메인 객체에 가깝다.

필요하다면 비즈니스 로직을 가질 수도 있다.

public void decreaseStock(int quantity) {
    if (this.stock < quantity) {
        throw new IllegalArgumentException("재고가 부족합니다.");
    }

    this.stock -= quantity;
}

즉, Entity는 DB와 매핑되면서 도메인의 상태와 행위를 표현하는 객체다.

DTO

DTO는 Data Transfer Object의 약자다.

이름 그대로 데이터를 전달하기 위한 객체다.

주로 API 요청과 응답, 또는 계층 간 데이터 전달에 사용된다.

public record ProductResponse(
    Long id,
    String name,
    int price
) {
    public static ProductResponse from(Product product) {
        return new ProductResponse(
            product.getId(),
            product.getName(),
            product.getPrice()
        );
    }
}

DTO는 Entity 전체가 아니라, 외부에 전달할 데이터만 선택해서 담는다.

따라서 API 응답 구조를 Entity와 분리할 수 있다.

VO

VO는 Value Object의 약자다.

값 자체를 표현하는 객체다.

예를 들어 금액, 주소, 좌표처럼 하나의 값 개념으로 묶을 수 있는 대상이 VO가 될 수 있다.

public record Money(int amount) {

    public Money {
        if (amount < 0) {
            throw new IllegalArgumentException("금액은 0보다 작을 수 없습니다.");
        }
    }
}

VO는 식별자보다 값 자체가 중요하다.

예를 들어 10000원이라는 값은 누가 만들었는지가 중요한 것이 아니라, 금액 값 자체가 의미를 가진다.

Entity, DTO, VO 차이 정리

구분 Entity DTO VO
목적 도메인 표현, DB 매핑 데이터 전달 값 표현
주요 위치 Domain, Entity 계층 Request, Response 계층 Domain 내부
특징 식별자를 가진다 필요한 데이터만 담는다 값 자체가 중요하다
예시 Product, Member, Order ProductResponse, LoginRequest Money, Address

 

간단히 정리하면 다음과 같다.

Entity는 도메인 객체
DTO는 전달 객체
VO는 값 객체

왜 Entity를 DTO로 변환해서 내보내야 할까?

Entity를 그대로 반환하면 코드가 짧아지고 구현도 단순해 보인다.

하지만 Entity를 API 응답으로 직접 노출하면 몇 가지 문제가 생길 수 있다.

1. 내부 필드가 노출될 수 있다

Entity에는 API 응답에 필요하지 않은 필드가 포함될 수 있다.

@Entity
@Getter
public class Member {

    @Id
    private Long id;

    private String email;

    private String password;

    private String name;

    private String role;
}

이 Entity를 그대로 반환하면 password, role 같은 민감하거나 내부 관리용 필드가 응답에 포함될 위험이 있다.

물론 @JsonIgnore로 특정 필드를 숨길 수도 있다.

 

하지만 API 응답 정책 때문에 Entity에 JSON 관련 설정이 늘어나면, Entity가 점점 API 응답 형식에 영향을 받게 된다.

Entity는 도메인을 표현하는 객체이고, API 응답은 외부에 보여줄 데이터 형식이다.
두 역할은 분리하는 것이 좋다.

2. API 응답이 Entity 구조에 종속된다

Entity를 그대로 반환하면 API 응답 구조가 Entity 필드 구조와 같아진다.

이 경우 Entity 필드명이나 연관관계가 변경되면 API 응답도 함께 바뀔 수 있다.

하지만 API 응답은 클라이언트와의 약속이다.

내부 Entity 구조가 바뀌었다고 해서 외부 응답 형식까지 함께 바뀌면 프론트엔드나 외부 클라이언트에 영향을 줄 수 있다.

DTO를 사용하면 Entity 구조가 변경되더라도 API 응답 형식은 유지할 수 있다.

public record ProductResponse(
    Long id,
    String name,
    int price
) {
    public static ProductResponse from(Product product) {
        return new ProductResponse(
            product.getId(),
            product.getName(),
            product.getPrice()
        );
    }
}

즉, DTO는 Entity와 API 응답 사이에서 완충 역할을 한다.

3. 연관관계로 인해 불필요한 데이터가 응답될 수 있다

JPA Entity는 다른 Entity와 연관관계를 가질 수 있다.

@Entity
@Getter
public class Product {

    @Id
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    private Member seller;
}

이 상태에서 Product Entity를 그대로 반환하면 seller 정보까지 응답에 포함될 수 있다.

더 복잡한 양방향 연관관계에서는 순환 참조 문제가 발생할 수도 있다.

Product → Member → Product 목록 → Member ...

DTO를 사용하면 응답에 필요한 값만 명확히 선택할 수 있다.

public record ProductResponse(
    Long id,
    String name,
    String sellerName
) {
    public static ProductResponse from(Product product) {
        return new ProductResponse(
            product.getId(),
            product.getName(),
            product.getSeller().getName()
        );
    }
}

이렇게 하면 Entity의 연관관계 전체를 노출하지 않고, 필요한 값만 응답으로 내려줄 수 있다.

4. 지연 로딩( Lazy Loading ) 문제가 발생할 수 있다

JPA에서는 연관관계를 LAZY로 설정하는 경우가 많다.

LAZY는 실제로 필요한 시점에 연관 데이터를 조회하는 방식이다.

하지만 Entity를 그대로 응답으로 반환하면 JSON으로 변환되는 과정에서 지연 로딩 필드에 접근할 수 있다.

이때 영속성 컨텍스트가 닫혀 있다면 LazyInitializationException이 발생할 수 있다.

또는 예상하지 못한 추가 쿼리가 발생할 수도 있다.

DTO로 변환하면 필요한 데이터를 서비스 계층이나 트랜잭션 범위 안에서 미리 선택해 응답 형태로 만들 수 있다.

DTO로 변환한 응답 구조

Entity를 그대로 반환하는 대신, 응답 DTO를 만들어 반환하면 다음과 같은 구조가 된다.

@GetMapping("/products/{id}")
public ProductResponse getProduct(@PathVariable Long id) {
    Product product = productService.findById(id);
    return ProductResponse.from(product);
}

이 구조에서는 Controller가 Entity를 그대로 외부에 노출하지 않는다.

대신 API 응답에 필요한 값만 담은 ProductResponse를 반환한다.

결과적으로 Entity는 도메인 역할에 집중하고, DTO는 외부 전달 역할에 집중할 수 있다.

정리

Entity, DTO, VO는 모두 데이터를 다루는 객체처럼 보이지만 역할이 다르다.

  1. Entity는 DB와 매핑되는 도메인 객체다.
    식별자를 가지고, 도메인의 상태와 행위를 표현한다.
  2. DTO는 데이터를 전달하기 위한 객체다.
    API 요청과 응답에 필요한 값만 담는다.
  3. VO는 값 자체를 표현하는 객체다.
    금액, 주소처럼 하나의 값 개념을 안전하게 다룰 때 사용한다.

Entity를 API 응답으로 그대로 반환하면 내부 필드 노출, API 응답 구조 종속, 연관관계 순환 참조, 지연 로딩 문제 등이 발생할 수 있다.

따라서 Entity를 직접 노출하기보다 DTO로 변환해서 반환하면, 내부 도메인 구조와 외부 API 응답을 분리할 수 있다.


한 줄 정리

Entity는 도메인을 표현하는 내부 객체

DTO는 외부에 데이터를 전달하기 위한 객체

 

따라서 API 응답에서는 Entity를 그대로 노출하지 않고 DTO로 변환해 반환하는 것이 안전하다.

 

Entity를 직접 반환하지 않고 DTO를 사용하는 이유는 크게 네 가지다.

1. 비밀번호와 같은 중요 내부 필드 노출 방지
2. Entity 구조 변경이 API 응답 변경으로 이어져 발생하는 클라이언트와의 소통 오류 방지
3. JPA 연관관계를 가진 Entity의 불필요한 데이터 노출이나 순환 참조 문제를 방지
4. 영속성 컨텍스트가 닫힘으로 인해 발생하는 지연 로딩 문제를 방지하기 위함

'Back-End > Spring' 카테고리의 다른 글

@RequestParam, @ModelAttribute, @RequestBody는 언제 사용할까?  (0) 2025.11.24
[JPA] @ManyToOne, @OneToMany 연관관계 정리  (0) 2025.11.22
ID값을 UUID v4에서 UUID v7로 변경한 이유  (0) 2025.11.21
UUID만으로 구분하기 어려운 데이터를 @PrePersist로 개선해보기  (0) 2025.11.11
[JWT] Bearer는 왜 붙을까? Basic 인증과 Bearer 인증 비교하기  (0) 2025.10.29
'Back-End/Spring' 카테고리의 다른 글
  • @RequestParam, @ModelAttribute, @RequestBody는 언제 사용할까?
  • [JPA] @ManyToOne, @OneToMany 연관관계 정리
  • ID값을 UUID v4에서 UUID v7로 변경한 이유
  • UUID만으로 구분하기 어려운 데이터를 @PrePersist로 개선해보기
devoks
devoks
느려도 꾸준히
  • devoks
    ok's 개발일지
    devoks
  • 전체
    오늘
    어제
    • 분류 전체보기
      • Front-End
      • Back-End
        • Spring
        • Infra
        • AI
      • Computer Science
        • Cs
      • 언어
        • Java
        • SQL
      • 코테
        • Java
        • MySQL
      • Etc.
  • 블로그 메뉴

    • 홈
  • 링크

    • My GitHub
  • 공지사항

  • 인기 글

  • 태그

    springboot
    switch
    java
    programmers
    Container
    compare
    replace
    stack
    codingtest
    최대공배수
    PrePersist
    StringTokenizer
    effectivejava
    BFS
    replaceAll
    IaaS
    BufferedReader
    PaaS
    최대공약수
    docker
    역직렬화
    유클리드호제법
    json
    CI/CD
    persist
    BufferedWriter
    Regex
    정규표현식
    dfs
    CS
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
devoks
[SpringBoot] Entity를 DTO로 변환해야 하는 이유
상단으로

티스토리툴바