Spring으로 웹 애플리케이션, 특히 REST API를 개발하다 보면 DTO라는 용어를 반드시 마주치게 된다.
처음에는 "Entity가 있는데 굳이 왜 사용하는 걸까?" 라는 의문이 들었지만, DTO가 깔끔하고 안전한 애플리케이션을 만드는 데 핵심적인 역할을 한다는 걸 알았다.
이 글에서는 DTO의 정확한 개념과 Spring에서 DTO를 사용하는 이유를 정리해 보겠다.
1. 정의
DTO란 무엇인가요?
DTO는 Data Transfer Object의 약자로, 우리말로는 '데이터 전송 객체' 이다.
이름 그대로, 계층(Layer) 간 데이터 교환을 위해 사용하는 객체를 의미한다.
보통 클라이언트(브라우저, 앱)가 서버에 요청을 보낼 때 보내는 데이터를 담거나,
서버가 클라이언트에게 응답을 보낼 때 돌려줄 데이터를 담는 용도로 사용된다.
즉, 핵심은 DTO가 오직 데이터를 담는 그릇의 역할만 한다는 점이다.
어떤 비즈니스 로직도 포함하지 않은 순수한 데이터 객체(Plain Old Java Object, POJO) 라는 점.
쉽게 비유하자면...
데이터베이스의 Entity는 '주방의 원재료'와 같다. (ex. 생고기, 채소, 양념 등)
DTO는 손님에게 나가는 '완성된 요리' 라고 보면 될 것이다. (ex. 스테이크 플레이트)
셰프(서버 로직)는 주방의 다양한 원재료(Entity)를 사용해 요리하지만,
손님(클라이언트)에게는 보기 좋게 정제되고 필요한 것만 담긴 완성된 요리(DTO)를 접시에 담아 제공한다.
손님은 주방의 복잡한 상황이나 모든 원재료를 당연히 알 필요가 없다.
2. DTO 사용 이유

Client로부터 받은 요청은 전반적으로 위 그림과 같은 흐름을 통해 DB로 전달된다.
여기서 Entity는 DB와 직접 연결된 핵심 객체이다.
만약 Entity를 클라이언트와 직접 소통하는 데이터 객체로 사용한다면 여러 문제가 발생할 수 있다.
DTO는 바로 이 문제들을 해결하기 위해 사용된다.
1. 데이터 노출 방지 및 보안 강화
가장 중요하고 직관적인 이유다. Entity 클래스는 보통 DB 테이블의 모든 컬럼과 매핑된다.
여기에는 사용자의 비밀번호나 개인정보, 시스템 내부에서만 사용되는 데이터 등 민감한 정보가 포함될 수 있다.
만약 Entity를 그대로 클라이언트에게 반환하면, 이 모든 정보가 외부에 노출될 위험이 있다.
여기서 DTO를 사용하면 클라이언트에게 꼭 필요한 데이터만 선별해서 담아 보낼 수 있게 된다.
예시: User Entity vs. UserResponseDto
// Entity를 직접 반환하는 경우 (나쁜 예)
@Entity
public class User {
@Id
private Long id;
private String username;
private String password; // 🚨 외부에 노출되면 안 되는 민감 정보!
private String email;
private LocalDateTime createdAt;
}
// Controller에서 User Entity를 그대로 반환
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id); // password까지 JSON으로 변환되어 응답됨
}
// DTO를 사용하는 경우 (좋은 예)
// 클라이언트 응답용 DTO
public class UserResponseDto {
private Long id;
private String username;
private String email;
// Entity를 DTO로 변환하는 생성자
public UserResponseDto(User user) {
this.id = user.getId();
this.username = user.getUsername();
this.email = user.getEmail();
}
}
// Controller에서 DTO로 변환하여 반환
@GetMapping("/users/{id}")
public UserResponseDto getUser(@PathVariable Long id) {
User user = userService.findById(id);
return new UserResponseDto(user); // password가 제외된 안전한 데이터만 반환
}
2. API 스펙의 명확화 및 유연성 확보
클라이언트와 서버는 어떤 데이터를 주고받을지 약속(API 명세)을 한다.
DTO는 이 약속을 코드로 명확하게 표현하는 역할을 한다.
요청(Request)용 DTO와 응답(Response)용 DTO를 분리하면 그 역할이 더욱 명확해진다.
예를 들어, 회원가입 시에는 username, password, email을 받지만 (SignUpRequestDto),
사용자 정보를 조회할 때는 id, username, email을 돌려주는 (UserResponseDto) 것처럼 말이다.
3. 유효성 검사(Validation) 로직 분리
클라이언트로부터 들어오는 데이터는 항상 올바른 값이라고 보장할 수 없다.
따라서 데이터의 형식이 올바른지, 필수값이 누락되지 않았는지 등을 검증하는 과정이 필수적이다.
Spring에서는 @Valid 어노테이션과 함께 DTO에 @NotNull, @Size, @Email 등의 어노테이션을 붙여 유효성 검사 로직을 간편하게 추가할 수 있다.
이러한 유효성 검사 로직은 프레젠테이션 계층(Controller)의 책임이다. 데이터베이스 영속성(Persistence)을 책임지는 Entity에 유효성 검사 로직이 섞이면 객체의 책임이 불분명해진다.
따라서 DTO에 유효성 검사 로직을 위치시킴으로써 계층 간의 역할을 명확하게 분리할 수 있다.
예시
public class SignUpRequestDto {
@NotBlank(message = "사용자 이름은 필수입니다.")
@Size(min = 4, max = 20)
private String username;
@NotBlank(message = "비밀번호는 필수입니다.")
@Size(min = 8)
private String password;
@Email(message = "이메일 형식이 올바르지 않습니다.")
private String email;
}
// Controller
@PostMapping("/signup")
public ResponseEntity<String> signUp(@Valid @RequestBody SignUpRequestDto requestDto) {
// 유효성 검사를 통과한 경우에만 서비스 로직 실행
userService.register(requestDto);
return ResponseEntity.ok("회원가입 성공");
}
3. 정리
DTO는 단순히 코드를 늘리는 귀찮은 작업이 아니라, 안정적이고 확장 가능한 애플리케이션을 만들기 위한 필수 설계 패턴이다.
- 정의: 계층 간 데이터 전송을 목적으로 하는, 로직이 없는 순수한 데이터 객체
- 사용 이유:
- 보안: Entity의 민감한 정보 노출을 막는다.
- API 명세: API의 요청/응답 스펙을 명확하게 정의한다.
- 유연성: 화면(View)과 데이터베이스(Model) 사이의 의존성을 낮춰 유지보수를 쉽게 한다.
- 관심사 분리: 유효성 검사 로직을 프레젠테이션 계층으로 분리한다.
따라서 Spring 프로젝트를 진행할 때, Entity와 DTO의 역할을 명확히 구분하여 사용하는 습관을 들인다면 더욱 견고하고 전문적인 코드를 작성할 수 있을 것이다.
'개발 > Spring' 카테고리의 다른 글
| [Spring] Swagger UI와 OpenAPI 문서화 정리 (0) | 2026.07.31 |
|---|---|
| [Spring] Spring Boot JPA 동작 원리와 CRUD 정리 (0) | 2026.07.31 |
| [Spring] MVC 패턴과 Spring MVC 동작 원리 정리 (0) | 2026.07.28 |
| [Spring] Spring Boot란? (0) | 2025.07.07 |
| [Spring] Spring이란 (2) | 2025.06.23 |