본문 바로가기
JAVA/JPA

[JPA] 순환참조 문제해결 - 양방향 매핑 문제점

by 개미가되고싶은사람 2024. 12. 9.

 

목차

    순환참조 문제는 특정 조건에 만족할 때 발생합니다.

     

    바로 양방향 관계인 경우 순환 참조 문제가 발생합니다.


    그럼 이제 순환 참조 문제가 무엇인지 알아보겠습니다.

     

    순환참조란?

    순환참조는 두 개 이상의 객체가 서로를 참조하는 상황으로, 예를 들어 A 객체가 B 객체를 참조하고 B 객체가 다시 A 객체를 참조하는 경우에 해당합니다. 이러한 구조는 JPA 양방향 매핑과 유사하여 데이터베이스에서 객체를 직렬화할 때 문제가 발생할 수 있으며, 특히 JSON으로 변환할 때 무한 루프에 빠지거나 toString()을 사용할 때 무한 루프에 빠지게 됩니다.

    더보기

    toString() 무한 루프 예시

    @Entity
    @Table
    public class Order {
    
        @ManyToOne(fetch = LAZY)
        @JoinColumn(name = "member_id")
        private Member member;
    
        @Override
        public String toString() {
            return "Order{" +
                    ", member=" + member +
                    '}';
        }
    }
    
    @Entity
    public class Member {
    
        @OneToMany(mappedBy = "member", fetch = FetchType.LAZY)
        private List<Order> orders = new ArrayList<>();
    
        @Override
        public String toString() {
            return "Member{" +
                    ", orders=" + orders +
                    '}';
        }
    }

     

    Member member = new Member("회원1");
    member.setOrders(new Order("주문1"));
    member.toString();

    위에 코드를 보면 Order 클래스와 Member 클래스의 toString()를 보면 서로 참조하는 구조로 되어 있습니다. member.toString() 사용시 결과를 예상해보면 Member 클래스의 toString() 메서드는 orders 필드를 출력하고, Order 클래스의 toString() 메서드는 member 필드를 출력합니다. 이때 member는 Member 객체이므로 다시 member.toString()이 실행되어 무한 루프가 발생합니다. 따라서 toString()을 사용할 때는 서로 참조하는 객체 중 하나만 출력하거나 아예 사용하지 않아야 하며, 필드의 값만 출력해야 무한 루프가 발생하지 않습니다.

     

    순환참조의 문제점

    순환 참조로 인해 발생하는 문제점은 여러 가지가 있지만 가장 큰 문제점은 무한 루프에 빠질 수 있다는 것 입니다. 이로 인해 애플케이션이 강제 종료 될 수 있으며, 성능 저하가 발생할 수 있습니다.

    출처: https://velog.io/@minchae75/Spring-Boot-JPA-%EC%88%9C%ED%99%98-%EC%B0%B8%EC%A1%B0-%ED%95%B4%EA%B2%B0

    순환참조 문제 해결방법

    순환참조 문제를 해결하는 방법으로 @JsonIgnore, @JsonManagedReference, @JsonBackReference 등이 있지만 가장 좋은 방법은 DTO를 사용하는 것입니다. @JsonIgnore를 사용하면 해당 필드는 null로 들어가게 할 수 있지만, 엔티티 자체를 API 반환 값으로 그대로 사용하는 것은 좋지 않은 방법입니다. 이유는 한 엔티티에 각각의 API를 위한 모든 요청 요구사항을 담는 것이 거의 불가능하고, 엔티티가 변경되면 API 스펙도 변경되어 프론트 코드를 수정해야 할 수 있기 때문입니다. DTO는 특정 요청이나 응답에 필요한 데이터만 포함하므로 서로 참조하는 엔티티 간의 순환참조를 피할 수 있어 순환참조 문제를 사전에 예방할 수 있습니다.

     

    만약 로딩 전략이 지연 로딩(LAZY)인 경우에 DTO를 사용해도 당연히 N+1 문제가 발생합니다. 이런 경우에는 fetch join을 사용해 N+1 문제를 해결할 수 있습니다.

     

     

    참조

    https://medium.com/way-tech/%EC%96%91%EB%B0%A9%ED%96%A5-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84-%EB%A7%A4%ED%95%91-feat-%EC%88%9C%ED%99%98-%EC%B0%B8%EC%A1%B0-b5f55ff3c601

     

    [백엔드] 양방향 연관관계 매핑 (feat. 순환 참조)

    Jpa에서의 양방향 연관관계 매핑을 지양하라는 말을 많이 들었고 저희 프로젝트에서도 양방향 연관관계 매핑을 진행하지 않았습니다. 연관관계 매핑을 진행하지 않는다면, 1:N 관계를 많이 맺고

    medium.com