JPA와 API
- 결론부터 말하면 API에서는 엔티티 그대로 반환하면 안 된다.
- 엔티티는 스펙이 변경될 가능성이 크기 때문이다.
- 또한 애플리케이션 내부 로직이 노출될 위험성도 존재하기 떄문이라는 이유도 있다.
v1 - 엔티티를 직접 노출
- 엔티티를 그대로 반환하기 때문에 API에서 사용하기에는 좋지 않은 방법
- 지연 로딩에 의해 프록시로 존재하는 부분을 강제로 초기화해줘야 한다.
Hibernate5Module
- JPA 사용 시 일반적으로 fetch 전략을 LAZY로 잡기 때문에 실제 엔티티 객체 대신에 프록시 객체를 갖고 있다.
- jackson 라이브러리는 기본적으로 이 프록시 객체를 json으로 어떻게 생성해야 하는지 모르기 때문에 예외가 발생한다.
- 이를 해결하기 위해 Hibernate5Module 또는 Hibernate5JakartaModule를 스프링 빈으로 등록해준다.
스프링 부트 2.X를 사용 중인 경우
스프링 부트 3.X를 사용 중인 경우
@JsonIgnore
- 엔티티를 직접 반환할 때 해당 엔티티에 양방향 연관관계가 존재한다면 양측 엔티티가 서로 호출하면서 무한 루프가 발생한다.
@JsonIgnore
를 추가해서 순환 참조를 막는다.@JsonIgnore
는 주로 @ManyToOne 어노테이션이 있는 필드에 추가하면 된다.
v2 - 엔티티를 DTO로 변환
- 엔티티를 DTO로 변환 후 반환하는 방법
- 조회한 엔티티를 DTO의 생성자를 통해 DTO로 변환한다.
- 장점
- 해당 API를 위한 어느정도 고정된 스펙의 DTO를 반환하기 때문에 API의 스펙이 변경될 일이 적다.
- 단점
- 지연 로딩이기 때문에 N + 1의 문제가 존재한다.
- 발생하는 문제의 유형을 부르는 이름이 N + 1인거지 실제로는 더 많은 쿼리가 발생할 수도 있다.
- 만약에 조회한 엔티티 내부에 프록시로 존재하는 필드가 M개 있다면 실행되는 쿼리의 개수는 1 + N * M이 될 수도 있다.
- 팁
map(o -> new SimpleOrderDto(o))
는 람다 레퍼런스 방식으로 map(SimpleOrderDto::new)
처럼 표현할 수 있다.
v3 - 페치 조인 최적화
- 페치 조인(fetch join)으로 데이터를 한꺼번에 가져온다.
- 엔티티로 조회했기 때문에 DB와의 작업이 수월하다. (ex : 변경 감지)
- 유연도가 높은 방식이다. (v4에 비해서 비교적 높은 편)
v4 - JPA에서 DTO로 바로 조회
- DB에서 조회한 데이터를 엔티티가 아닌 DTO로 바로 받는 방식
- DTO로 조회했기 때문에 DB와의 작업이 수월하지 못 하다. (ex : 변경 감지)
- 유연도가 낮은 방식이다. (v3에 비해서 비교적 낮은 편)
- 화면에 최적화되 있는 방식
- JPQL 작성 방법이 좀 번거롭다. (패키지명 직접 명시)
출처