반응형

순환 참조 란?

  • 1:1 연관관계 매핑 예제 참고
  • Author라는 객체와 Book이라는 객체가 있다고 생각해보자
    • 둘은 1:1 관계로 매핑되어 있음
    • Author은 name, age 정보 포함
    • Book은 name, price 정보 포함
    • Book을 저장할 때 책의 저자(Author)도 같이 저장
    • 이 상황에서 아래와 같이 Book을 조회한다면 아래와 같이 출력됨 (StackOverflow Error 발생)
@GetMapping("/book/{bookId}")
public Book showBook(@PathVariable Long bookId) {
    return bookRepository.findById(bookId).get();
}

  • 이렇게 출력되는 이유는 1번 Book 조회시 book1에 저장되어 있는 이름, 가격, author1을 출력
  • 이 때 author1의 정보를 출력하는데, author1의 이름, 나이, book1을 출력
  • book1이 출력한 author1이 출력한 book1을 출력 ....
  • 이와 같이 Book과 Author가 서로 참조하기 때문에 무한 루프(순환 참조 문제)가 발생함

해결방법 1 - @JsonIgnore 사용

  • @JsonIgnore 어노테이션을 통해 Json 타입으로 출력될 때 Author은 출력하지 않음
  • 간편하게 순환 참조 문제를 해결할 수 있지만, Author에 대한 정보를 출력할 수 없다는 단점 존재
@JsonIgnore
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;

결과

해결방법 2 - @JsonManagedReference, @JsonBackReference 사용

  • @JsonManagedReference는 부모 클래스에 @JsonBackReference는 자식 클래스에 적용
    • 이 예제에서는 Book = 자식, Author = 부모
  • 적용하는 위치와 결과는 @JsonIgnore과 같지만 @JsonIgnore은 JSON 타입 출력할 때 값을 nul는로 바꿔 출력해주는 반면 @JsonManagedReference, @JsonBackReference는 순환참조를 대비하기 위해 만들어진 어노테이션이라 생각하면 됨
// Book
@JsonBackReference
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;

// Author
@OneToOne(mappedBy = "author",fetch = FetchType.LAZY)
@JsonManagedReference
private Book book;

결과

해결방법 3 - DTO (Data Transfer Object) 사용 (추천)

  • 이 방법은 Book 객체 자체를 출력하는 대신 BookDto 클래스를 생성 후 원하는 데이터만 추출해서 넣어주고 BookDto를 출력하는 방법
  • BookDto를 활용해서 Author을 출력하는 대신 Author의 name만 출력해 보자
  • 이 방법이 가장 좋은 방법이라 생각함
// BookDto
@Getter
@AllArgsConstructor
public class BookDto {

    private Long id;
    private String name;
    private int price;
    private String authorName;

    public static BookDto of(Book book) {
        return new BookDto(book.getId(), book.getName(), book.getPrice(), book.getAuthor().getName());
    }
}
  • 위와 같이 BookDto를 생성하는 코드를 BookDto에 static으로 생성해주면 나중에 BookDto를 생성할 때 사용하기 편함
@GetMapping("/book/{bookId}")
public BookDto showBook(@PathVariable Long bookId) {
    Book book = bookRepository.findById(bookId).get();
    return BookDto.of(book);
}
  • 아까와는 달리 Book을 return하지 않고 BookDto를 return
  • BookDto의 of를 사용
    • bookId를 통해 찾은 Book을 넘겨주면 BookDto로 변환해서 return 해주는 메소드

결과

  • DTO를 활용하면 DTO 클래스를 만들어줘야 하는 귀찮음이 있을 수 있지만, 필요한 정보만 추출해 출력하는데 유리함
    • ex) Book에 대한 정보를 출력할 때 어떤 요청에서는 책의 이름과 가격만 출력해도 되는데 다른 요청에서는 책의 이름, 가격, 저자 이름, 저자 나이 까지 출력해야 된다면 DTO를 따로 만들어서 return 하는 방식 사용 가능

해결방법 4 - 양방향 매핑을 단방향 매핑으로 수정

  • 현재는 두 객체가 양방향으로 매핑되어 있음
  • 이 방법은 엔티티가 반대쪽 엔티티를 참조할 일이 없을 때 단방향으로 매핑을 하는 방법
  • 순환참조가 일어날 일은 없지만, 나중에 반대 방향의 참조가 일어날 것인지 잘 생각해보고 적용해야 함
반응형

↓ 클릭시 이동

복사했습니다!