본문 바로가기
개발진행목록/셀프 세차장 예약 서비스

예약 리스트 조회 API 순환참조 해결

by o3oppp 2024. 10. 29.
순환참조
  • 참조하는 대상이 서로 물려 있어 참조할 수 없게 되는 현상
  • JPA에서 양방향으로 연결된 엔티티를 그대로 조회하는 경우, 서로의 정보를 순환하면서 조회하다가 stackoverflow 발생

엔티티 직접 노출 AS-IS
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ReserveShop {

    @Id
    @GeneratedValue
    @Column(name = "reserve_shop_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "reserve_id") // FK 설정, 연관관계 주인
    private Reserve reserve;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "shop_id")
	...
}
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Reserve {

    @Id
    @GeneratedValue
    @Column(name = "reserve_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id") // FK 설정, 연관관계 주인
    private Member member;

    @OneToMany(mappedBy = "reserve", cascade = CascadeType.ALL)
    private List<ReserveShop> reserveShops = new ArrayList<>();
	...
}
  • ReserveShop 클래스와 Reserve는 양방향 관계(N:1)
import detailing.reservation.domain.*;
import detailing.reservation.repository.ReserveRepository;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequiredArgsConstructor
public class ReserveApiController {

    private final ReserveRepository reserveRepository;

    @GetMapping("/api/v1/reserves")
    public List<Reserve> reserveV1(){
        List<Reserve> all = reserveRepository.findAllByString(new ReserveSearch());
        for (Reserve reserve : all) {
            reserve.getMember().getName();
            List<ReserveShop> reserveShops = reserve.getReserveShops();
            reserveShops.stream().forEach(r -> r.getShop().getName()); // Lazy 강제 초기화
        }
        return all;
    }
	...
}
  • 순환 참조 방지를 위해 Lazy 강제 초기화 진행

API 호출 결과

  • Lazy 강제 초기화를 진행하였지만, API 호출 시 reserveShop -> reserve -> reserveShop -> reserve와 같이 순환 참조 발생

엔티티 직접 노출 TO-BE
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ReserveShop {

    @Id
    @GeneratedValue
    @Column(name = "reserve_shop_id")
    private Long id;
	
    @JsonIgnore // 추가
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "reserve_id") // FK 설정, 연관관계 주인
    private Reserve reserve;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "shop_id")
	...
}
  • 순환 참조가 발생하는 엔티티 @JsonIgnore 추가

API 호출 결과

  • @JsonIgnore를 추가함으로써 순환 참조 문제 해결
  • 해당 어노테이션 사용 시 JSON 데이터에 해당 프로퍼티는 null로 들어감. 즉 데이터에 아예 포함시키지 않음

다른 해결법

1. @JsonManagedReference, @JsonBackReference 사용

  • 순환참조를 방어하기 위한 어노테이션
  • @JsonManagedReference를 연관 관계 주인의 반대편에 선언
  • @JsonBackReference를 연관  관계 주인(외래키가 있는곳)에 선언

2. DTO 사용

  • 필요한 데이터만 옮겨담아 return 하여 순환참조 방어 가능
  • 다음 게시글에 작성 예정

'개발진행목록 > 셀프 세차장 예약 서비스' 카테고리의 다른 글

예약 조회 API 개발  (0) 2024.10.24
회원 관련 API 개발  (0) 2024.10.17
설계  (0) 2024.09.20