반응형
JPA의 순환 참조란 객체 간의 상호 참조가 무한 반복되는 경우를 말한다.
객체 그래프에서 한 객체가 다른 객체를 참조하고, 그 다른 객체가 다시 처음 객체를 참조하는 상황이고 스택 오버 플로우가 발생할 수 있다.
사이드 프로젝트 진행 하던 도중 Service 단 개발을 끝내고 API를 만들다가 스택 오버 플로우가 발생 하였다.
순환 참조를 미쳐 신경쓰지 못하고 있었음.. 순환 참조를 잘 예방해야겠다고 다시 한 번 생각함
Service를 테스트 할 때 까지만 해도 발생하지 않았던 문제인데 REST API를 구현하면서 문제가 발생한 이유는
ResponseBody를 구현할 시 Object 형태의 값을 직렬화를 이용해 JSON 형태로 변환하는데 이 때 순환 참조가 발생하기 때문이다.
public class Orders {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Integer quantity;
private Integer totalPrice;
private OrderStatus orderStatus;
@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;
@OneToMany(mappedBy = "orders")
private List<OrderProduct> orderProducts = new ArrayList<>();
@OneToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "payment_id")
private Payment payment;
@OneToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "shipment_id")
private Shipment shipment;
}
public class Product {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productName;
private String description;
private Integer price;
private Integer quantity;
@ManyToOne
@JoinColumn(name = "category_id")
private Category category;
@OneToMany(mappedBy = "product")
private List<OrderProduct> orderProducts = new ArrayList<>();
}
public class OrderProduct {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "orders_id")
private Orders orders;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id")
private Product product;
private Integer quantity;
}
Orders와 Product를 다대다 관계로 매핑하기 위해 OrderProduct를 만들었고
Orders 조회 DTO인 OrderResponseDto를 만들었다.
* 문제가 되었던 DTO
순환 참조를 피하도록 DTO를 만들 수 있는데, DTO에서 객체를 그대로 반환하게 만들어서 문제가 발생했다.
OrderProduct, Customer, Payment와 Orders가 서로 참조 하고 있기 때문에..
@Getter
public class OrdersResponseDto {
private Integer quantity;
private Integer totalPrice;
private OrderStatus orderStatus;
private List<OrderProduct> orderProducts = new ArrayList<>();
private Customer customer;
private Payment payment;
private Shipment shipment;
public OrdersResponseDto(Orders orders) {
this.quantity = orders.getQuantity();
this.totalPrice = orders.getTotalPrice();
this.orderStatus = orders.getOrderStatus();
this.orderProducts = orders.getOrderProducts();
this.customer = orders.getCustomer();
this.payment = orders.getPayment();
this.shipment = orders.getShipment();
}
}
JPA는 순환 참조를 해결하기 위한 여러가지 방법을 제공 하는데, 대표적인 4가지 방법이다
- 단방향 관계 설정 : 한 엔티티만 다른 엔티티를 참조하도록 설계
- FetchType 설정 : FetchType.LAZY를 적용하여 연관된 엔티티를 실제로 사용할 때 까지 로딩을 지연시키기
- @JsonIgnore, @JsonMangedReference, @JsonBackReference 등의 어노테이션 사용 : JSON 직렬화 시 에 해당 필드를 무시하고나 참조를 관리
- 직렬화 및 역직렬화 커스터마이징 : 필요에 따라 엔티티의 직렬화 및 역직렬화 과정을 직접 커스터 마이징하여 순환 참조를 피함
- 순환 참조가 발생하지 않도록 별도의 DTO 사용
나는 그중 @JsonIgnore를 사용하여 JSON 직렬화 시 문제가 되는 필드를 무시하도록 수정 했다.
@Getter
public class OrdersResponseDto {
private Integer quantity;
private Integer totalPrice;
private OrderStatus orderStatus;
@JsonIgnore
private List<OrderProduct> orderProducts = new ArrayList<>();
@JsonIgnore
private Customer customer;
@JsonIgnore
private Payment payment;
private Shipment shipment;
public OrdersResponseDto(Orders orders) {
this.quantity = orders.getQuantity();
this.totalPrice = orders.getTotalPrice();
this.orderStatus = orders.getOrderStatus();
this.orderProducts = orders.getOrderProducts();
this.customer = orders.getCustomer();
this.payment = orders.getPayment();
this.shipment = orders.getShipment();
}
}
반응형
'프로그래밍 > Troubleshooting' 카테고리의 다른 글
[Spring] Service와 Repository 의존성: 좋은 패턴 고민하기 (Feat. Facade 패턴) (1) | 2025.07.16 |
---|---|
[Spring] @Transactional이 비동기(Async)를 만났을 때 벌어지는 일들 (2) | 2025.06.24 |
우연히 알게된 Mybatis의 캐시 Cache (0) | 2024.02.12 |
Spring Message, properties 한글 깨짐 해결 (0) | 2023.11.03 |
Nginx WebSocket 설정, Nginx WebSocket 안될 때 (0) | 2023.09.07 |