SpringBoot+Jpa

양방향 연관관계 무한루프

lby132 2022. 11. 28. 00:50

양방향 연관관계에 있을때 엔티티를 @ResponseBody 그대로 내보냈을때 상황이다.

예를들어 Order엔티티는 이렇게 있다고 하자

@ManyToOne(fetch = LAZY)
@JoinColumn(name = "member_id")
private Member member;

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();

@OneToOne(fetch = LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "delivery_id")
private Delivery delivery;

private LocalDateTime orderDate;

여기서

@GetMapping("/api/v1/simple-orders")
public List<Order> ordersV1() {
    final List<Order> all = orderRepository.findAllByCriteria(new OrderSearch());
    return all;
}

Order의 데이터들을 그대로 바디로 반환해버리면 무한루프가 걸리면서 데이터가 끝없이 나오면서 에러가 발생한다. 

이유는 연관관계 때문인데 Order엔티티는 현재 orderItem과 delivery엔티티와 연관관계를 맺고있다.

public class OrderItem {
    
    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "order_id")
    private Order order;
public class Delivery {
    
    @OneToOne(mappedBy = "delivery", fetch = LAZY)
    private Order order;

이렇게 양쪽으로 연관관계가 되어있어서 Json라이브러리가 Order에 OrderItem이 있으니까 들어가봤는데 OrderItem에 또 Order가 있으니까 서로 계속 호출하게 되는것이다. 그래서 양방향관계에 있다면 한쪽을 @JsonIgnore로 끊어줘야한다.

@JsonIgnore
@OneToOne(mappedBy = "delivery", fetch = LAZY)
private Order order;

이런식으로. 그런데 이렇게 하고 실행을하면 무한루프에서는 벗어낫지만 다시 에러가 발생한다.

이 에러는 FetchType이 LAZY로 되어 있어서 난 에러인데

 

@ManyToOne(fetch = LAZY)
@JoinColumn(name = "member_id")
private Member member

이게 원래 소스이면

Jackson 라이브러리가 읽을때 FetchType이 LAZY로 되어있으면 아직 DB에서 불러오지 않았기 때문에 member는 값이 없고 

ByteBuddyInterceptor라는 프록시가 들어가 있기 때문에 Jackson 라이브러리가ByteBuddyInterceptor는 읽을수가 없어서 에러가 나는것이다.

private Member member = new ByteBuddyInterceptor();

뭐 이런식으로 되어있는것 같다. 그래서 컨트롤러를 조금 수정하면 

@GetMapping("/api/v1/simple-orders")
public List<Order> ordersV1() {
    final List<Order> all = orderRepository.findAllByCriteria(new OrderSearch());
    for (Order order : all) {
        order.getMember().getName();  //LAZY 강제 초기화
        order.getDelivery().getAddress();  //LAZY 강제 초기화
    }
    return all;
}

이렇게 order.getMember()를 하면 프록시객체이지만(DB에 쿼리가 아직 안날라간 상태) .getName()까지 호출하면 LAZY가 강제초기화가 되고 이때 Member쿼리를 날려서 DB에서 데이터를 다 끌고 온다. 이렇게 하면 오류 해결.

 

한가지 방법이 더 있는데

hibernate5module을 사용하는 방법이다.

implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-hibernate5', version: '2.14.1'

얘를 주입받고

스프링 시작시점에 

@Bean
Hibernate5Module hibernate5Module() {
   final Hibernate5Module hibernate5Module = new Hibernate5Module();
   final Hibernate5Module configure = hibernate5Module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, true);
   return hibernate5Module;
}

얘를 빈으로 등록해주면 되긴하는데 문제는 연관관계 걸려있는 FetchType이 LAZY인 애들을 모두 LAZY로딩으로 다 발라버려서 노출되면 안되는 API들까지 다 나오게 된다. 그래서 이것보단 DTO로 변환해서 사용하는게 좋다.

'SpringBoot+Jpa' 카테고리의 다른 글

casecade(영속성 전이)  (0) 2022.11.25
OSIV  (0) 2022.10.29
1대 N fetch join 위험성  (0) 2022.10.27
EntityManager의 @Autowired  (0) 2022.10.23
JPA사용시 @Transactional  (0) 2022.10.23