API를 개발할 때 엔티티를 그대로 외부로 노출하는 방식으로 빠르게 결과를 낼 수 있다.
예를 들어 JPA 엔티티 객체를 바로 컨트롤러에서 JSON으로 반환하거나 요청 본문에서 그대로 받아오는 식이 있다.
하지만 엔티티의 변경이 API 스펙 변경으로 이어지는 구조적 결합 때문에 엔티티를 외부로 노출하면 안된다.
그리고 대충 생각해보기만 해도 불필요한 데이터가 과다 전송되면서 민감 정보도 포함될 수 있고 연관된 엔티티까지 LAZY 로딩이 강제로 일어나면서 불필요한 쿼리 폭증과 N+1 문제가 생길 수 있다.
밑에서 알아보자.
1. 엔티티 변경 = API 스펙 변경
엔티티는 비즈니스 로직이나 데이터베이스의 구조와 밀접하게 연관되어 있다.
따라서 프로젝트를 진행하면서 변경될 가능성이 매우매우 크다.
새로운 필드가 추가되거나 삭제될 수도 있고
필드 이름이 바뀔 수도 있고 연관관계 매핑( @ManyToOne, @OneToMany)이 추가될 수도 있다.
컬럼 제약 조건이 변경될 수도 있다(nullable = false 등)
이처럼 내부 구현에 따른 리팩토링이 자주 일어날 수 있는 엔티티를 API 응답/요청 모델로 사용하면 이 변경이 그대로 외부로 반영된다.
Member라는 엔티티가 있고 여기에 새로운 필드 email을 추가했다면 API 응답도 자동으로 바뀌게 된다.
기존 API 응답은 { “name”: “홍성민” } 인데
변경 후 API 응답은 { "fullName": "홍성민", "email": "hskhsmmm@gmail.com" }
→ 이런 변경은 클라이언트 입장에선 API가 바뀌었다는 것과 같다.
예고 없이 API 스펙이 깨져버린 것.
2. 불필요한 데이터 과다 전송
엔티티를 그대로 반환하면 클라이언트가 실제로 필요하지도 않은 수많은 필드들이 함께 전달된다.
@Entity
public class Member {
private String name;
private String email;
private String password;
private LocalDateTime createdAt;
private LocalDateTime lastLoginAt;
@OneToMany(mappedBy = "member")
private List<Order> orders;
}
이 엔티티를 그대로 API로 리턴하면
- password 같은 민감한 정보가 노출될 수 있고
- orders처럼 연관된 엔티티도 강제로 로딩되면서 N+1 문제 발생( 하나의 쿼리(N)로 특정 엔티티 목록을 조회한 후 그 엔티티들이 갖고 있는 연관 엔티티를 각각 1개씩 별도의 쿼리로 추가 조회함. 추후에 N+1 문제에 대해 포스팅 하겠다. 엄청 중요함)
- 모바일 환경에서는 대용량 JSON이 네트워크 성능에 큰 영향을 미침
클라이언트 입장에선 단순히 회원 이름 목록만 필요했을 뿐인데 너무 많은 정보가 함께 오게 된다.
DTO를 사용하지 않고 엔티티를 그대로 리턴하면 민감 정보가 실수로라도 외부에 노출될 수 있다.
- @JsonIgnore을 사용해서 개별적으로 막을 순 있는데 이건 실수 돌려막기이다.
- 엔티티 변경 → @JsonIgnore 하나 더 붙이기 → 또 변경 → 또 붙이기
정답은? DTO
가장 좋은 방법은 엔티티와 API 스펙을 분리하는 것이다.
DTO라는 별도의 객체를 만들어서 클라이언트가 요구하는 데이터만 선별적으로 전달하는 구조를 갖추는 것이 이상적이다.
// 엔티티 (내부 전용)
@Entity
public class Member {
private String name;
private String email;
private String password;
private LocalDateTime createdAt;
}
// DTO (API 응답 전용)
public class MemberDto {
private String name;
public MemberDto(Member member) {
this.name = member.getName();
}
}
이렇게 하면
- 엔티티가 어떻게 바뀌든 API 응답 형식은 그대로 유지된다.
- 프론트 개발자는 안정적인 JSON 응답을 받을 수 있다.
- 보안, 성능, 유지보수 측면에서 모두 장점이 된다.
참고: DTO를 쓴다고 무조건 다 좋은 건 아님
DTO를 stream으로 변환하는 경우에도 내부적으론 엔티티를 메모리에 올리기 위한 쿼리가 발생한다.
그래서 JPQL이나 QueryDSL을 사용하는 고급 최적화 방법도 고려해야 한다. 이건 추후에 더 공부하면서 정리하겠다.
정리
엔티티는 DB 구조와 비즈니스 로직에 따라서 자주 바뀌는 객체이다.
이걸 그대로 API에 노출하면 내부 변경이 외부 API 스펙 변경으로 이어져서 시스템 전체에 영향을 미치게 된다.
불필요한 데이터 전송, 보안 문제, 성능 저하까지 유발 가능하다.
-> API 응답/요청 전용 DTO를 사용해서 엔티티와 외부 스펙을 분리하자.
'Backend > JPA, QueryDSL' 카테고리의 다른 글
SQL 문법을 JPA로 (1) | 2025.05.26 |
---|---|
JPA 성능최적화의 핵심 Proxy를 알자(feat. N+1) (0) | 2025.05.24 |
[JPA] 다대일과 일대다 연관관계 매핑 이해하기 (0) | 2025.04.11 |
[JPA] 간단한 문제 해결(회원가입,조회) (0) | 2025.04.10 |
[JPA] 영속성 컨텍스트부터 엔티티 생명주기까지 확실히 알자! (0) | 2025.04.07 |