엔티티를 노출한 API 생성하기
📍 @JsonIgnore 사용하기
- 양방향 연관관계의 엔티티에서 한 쪽에는 해당 어노테이션을 붙여줘야 무한 루프를 막을 수 있음
예시를 살펴보며 이해해보면,
Controller
@GetMapping("/api/v1/simple-orders")
public List<Order> ordersV1() {
List<Order> all = orderRepository.findAllByString(new OrderSearch());
return all;
}
Entity
@Entity
@Table(name = "orders")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {
@Id @GeneratedValue
@Column(name="order_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id") // foreign key가 무엇인지 명시
private Member member;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name ="delivery_id") //연관관계 주인. FK가짐
private Delivery delivery;
private LocalDateTime orderDate; // 주문시간
@Enumerated(EnumType.STRING)
private OrderStatus status; // 주문상태 [ORDER, CANCEL]
}
@Entity
@Getter
@Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@NotEmpty
private String name;
@Embedded
private Address address;
@JsonIgnore
@OneToMany(mappedBy = "member") // mappedBy를 통해 연관관계의 거울임을 명시. Order 테이블의 member 필드가 주인
private List<Order> orders = new ArrayList<>();
}
- 위의 Order 클래스는 Member를 필드로 갖고 있고, Member 클래스 또한 필드에 order를 가지고 있음
- 한 쪽에
@JsonIgnore
어노테이션을 붙이지 않으면, 위의 컨트롤러에서처럼 api에서 엔티티를 바로 사용하는 경우 무한 루프를 돌면서 계속 객체를 뽑아냄 - 즉, 양방향 연관관계에서는 한 쪽에
@JsonIgnore
어노테이션을 붙여 무한 루프가 돌지 않도록 해주어야 함
📍 Hibernate5Module 사용하기
위의 API를 포스트맨으로 호출 시, 아래와 같은 오류가 뜸
- 해당 오류는 LAZY fetch 때문에 뜨는 것
- Order class를 보면 member는 fetch가 LAZY로 되어있음 > 지연 로딩
- 지연 로딩의 경우, DB에서 데이터를 가지고 올 때 order에서만 데이터를 가지고 옴 (member X)
- 때문에 hibernate에서 아래와 같은 형식으로 프록시 객체를 가져옴 (bytebuddy) + member 값이 필요하게 되었을 때 DB에서 값 가져옴(프록시 초기화)
private Member member = new ByteBuddyInterceptor();
- 즉, member가 순수한 자바 객체가 아니라 발생하는 것!
해결 방법
- hibernate5module 사용 : 초기화 된 것은 값 내보내고, 그렇지 않은 경우 null값 반환하게 해줌
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5-jakarta'
를 build.gradle에 추가- main application 코드에 스프링 빈으로 등록해준 후 로딩 어떻게 할 지 설정
@Bean Hibernate5JakartaModule hibernate5Module(){ Hibernate5JakartaModule hibernate5Module = new Hibernate5JakartaModule(); hibernate5Module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, true); return hibernate5Module; }
- 위의 코드 상으로는 FORCE_LAZY_LOADING으로 설정했기 때문에, jpa 실행 시점에 강제로 로딩을 해옴
- FORCE_LAZY_LOADING으로 설정하지 않는 경우에는 강제로 컨트롤러에서 로딩하는 방법이 있음
📍 엔티티를 직접 노출해 API를 불러오는 방법의 문제점
- API 스펙 상 필요 없는 정보를 받아오게 될 수 있음
- 성능 상 이슈 : 필요 없는 정보를 받아오므로
- 위의 예시를 보면 hibernate5module를 통해 지연 로딩인 필드를 다 강제로 가져오게끔 함 > 그만큼 쿼리나감
- hibernate5module을 사용하는 것보다는 dto로 변환해서 반환하는 것이 더 좋은 방법!
- 강제 초기화를 막기 위해 필드를 EAGER fetch로 바꾸는 건 절대 해서는 안됨 > 성능 이슈
** '실전! 스프링 부트와 JPA 활용2' 강의를 듣고 작성했습니다.
'Spring' 카테고리의 다른 글
[Spring] API 개발 컬렉션 조회 최적화 (0) | 2023.04.24 |
---|---|
[Spring] 엔티티를 DTO로 변환한 API 설계 (0) | 2023.04.23 |
[Spring] JPA Auditing - BaseTimeEntity (0) | 2023.04.21 |
[Spring] 변경 감지와 병합(merge) (1) | 2023.04.19 |
[Spring] AWS + Docker 이용해 서버 배포하기 (0) | 2023.04.18 |