DTO (Data Transfer Object) 란 ?
DTO는 계층 간 데이터 전달을 위한 객체이다. 주로 Controller ↔ Service ↔ Repository 사이에서 데이터를 주고 받기 위해 사용한다.
핵심 특징
- 비즈니스 로직을 포함하지 않는다.
- 필요한 데이터만 담아 전송한다.
- 엔티티와 목적이 다르다.
- 입력/출력에 따라 DTO를 구분한다.
왜 DTO를 사용할까 ?
DTO는 단순한 데이터 묶음이지만, 엔티티를 직접 노출하지 않기 위한 중요한 보호 장치이다.
1. 엔티티 그대로 요청과 응답에 사용하다 보면 데이터 변경의 위험이 있다.
2. Controller에서 엔티티를 직접 다루면 비즈니스 계층 구조가 무너질 수 있다. DTO를 사용하면 계층 역할이 명확해진다.
3. .엔티티의 모든 필드가 필요한 것은 아니다. DTO로 필요한 필드만 선택하여 전달할 수 있다.
4. 여러 엔티티를 조합하거나, 외부 API 데이터를 묶어서 내보내는 등 가공에 유리하다.
DTO 활용 패턴
Spring에서 DTO를 사용할 때 가장 많이 쓰는 패턴은 2가지이다.
1) toEntity() 메서드
DTO 내부에 `toEntity()` 인스턴스 메서드를 두어, DTO에서 곧바로 엔티티로 변환하는 패턴이다. 주로 Request DTO를 엔티티로 변환할 때 사용된다.
public record UserCreateRequest(String username, String email) {
public User toEntity() {
return new User(this.username, this.email);
}
}
- 난 주로 DTO를 `record` 형태로 작성하여 관리하는데, `record` 는 불변 객체에 가깝게 설계되어 자동으로 생성자와 접근자(getter)가 생긴다.
- `toEntity()` 는 인스턴스 메서드이므로 `UserCreateRequest`의 값을 사용해 User 객체를 생성한다.
2) from() 메서드
DTO 내부에 `from()` 같은 정적 팩토리 메서드를 두어 엔티티에서 DTO 변환을 수행한다. 특히 Reseponse DTO를 만들 때, 엔티티에서 DTO로 변환할 때 사용한다.
public record UserResponse(Long id, String username) {
public static UserResponse from(User user) {
return new UserResponse(user.getId(), user.getUsername());
}
}
- `from(User)` 은 정적 팩토리로, 엔티티에서 필요한 필드만 골라서 DTO로 변환한다.
서비스/컨트롤러에서 사용 예시
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
// 생성
public Long createUser(UserCreateRequest req) {
User user = req.toEntity();
userRepository.save(user);
return user.getId();
}
// 조회
public UserResponse getUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("not found"));
return UserResponse.from(user);
}
}
정적 팩토리 메서드 (Static Factory Method)
생성자를 직접 호출하지 않고, 생성 역할을 하는 static 메서드를 통해 객체를 만든다. new로 직접 만들지 않고, 대신 이름이 있는 메서드를 통해 객체를 만드는 것이다.
UserResponse.from(user) // 객체 생성
여기서 `from()` 이 바로 정적 팩토리 메서드에 해당한다.
왜 사용하냐 ?
1. 메서드 이름으로 무슨 객체인지 명확하게 표현이 가능하다.
생성자 `new UserResponse(...)` 는 의미가 없지만, `from(User)` 는 바로 user로부터 DTO를 만든다가 보인다.
2. 새 객체를 만들 필요가 없다.
3. 여러 버전의 생성 로직을 제공할 수 있다.
'Spring' 카테고리의 다른 글
| [Spring] Lombok 이해하기 - @NoArgsConstructor, @AllArgsConstructor, @RequiredArgsConstructor (0) | 2025.11.04 |
|---|---|
| [Spring] 데이터 유효성 검증 (Validation) 이해하기 (0) | 2025.11.04 |
| [Spring] 외부 API 연동하기 (RestTemplate vs. WebClient vs. OpenFeign) (0) | 2025.10.05 |
| [Spring] N+1 문제 (0) | 2025.10.03 |
| [Spring] 객체지향 쿼리 언어 (0) | 2025.10.02 |
