양방향 연관관계 매핑 시 @ToString

지찬우·2023년 1월 17일
0

Knowing

목록 보기
3/10
post-thumbnail

LazyInitializationException 발생

다음과 같은 테스트 코드를 작성하다가 오류가 발생했다. 카페의 이름에 검색어가 포함된 카페들을 조회하는 기능을 테스트하는 코드이다. 테스트 코드에서 값을 출력해서 확인하는 방법은 좋은 방법은 아니지만, 궁금해서 출력해 보고 싶었다.

@Test
void 카페_검색() {
    //get
    Member member = makeMember(1L, "woopaca");
    Cafe cafe1 = makeCafe(1L, "포롱");
    Cafe cafe2 = makeCafe(2L, "포포");

    //when
    memberRepository.save(member);

    cafe1.setOwner(member);
    cafe2.setOwner(member);
    cafeRepository.save(cafe1);
    cafeRepository.save(cafe2);

    List<Cafe> cafes = cafeRepository.findByCafeName("포");

    //then
    assertThat(cafes.size()).isEqualTo(2);
    System.out.println("cafes = " + cafes);
}

코드를 실행했는데, 이런. 오류가 발생했다. LazyInitializationException

// org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.woopaca.camo.entity.cafe.Cafe.menus, could not initialize proxy - no Session

출력할 때 Cafemenus가 문제인 것 같다. @OneToMany의 기본 fetch 전략이 FetchType.LAZY라고 알고 있다. lazy는 단어의 의미처럼 게으른 녀석인데, Cafe 객체를 조회할 때는 List<Menu>를 가져오지 않고, 이 조회한 Cafe 객체의 List에 접근(조회)하려 하는 경우에 그 시점에 불러오는 것으로 알고 있다. 그래서 출력할 때 문제가 발생한 것 같다.

@Entity
...
@ToString
...
public class Cafe {

		...

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member owner;

    @OneToMany(mappedBy = "cafe")
    private final List<Menu> menus = new ArrayList<>();

    @OneToMany(mappedBy = "cafe")
    private final List<Review> reviews = new ArrayList<>();

    @OneToMany(mappedBy = "cafe")
    private final List<Employee> employees = new ArrayList<>();

    @OneToMany(mappedBy = "cafe")
    private final List<Image> images = new ArrayList<>();

	  ...

}

@ToString.Exclude

그래서 처음에 무식하게 ‘그럼 FetchType.EAGER로 바꿔주면 되겠네~’하고 fetch 전략을 변경하는데, Cafe에는 List가 네 개나 있다. 근데 이걸 다 그렇게 바꿔주려니 뭔가 귀찮기도 하고, 무엇보다 만약 저게 다 출력되면 굉장히 출력하는 데 비용이 많이 들고 지저분해질 것 같았다. 그래서 그냥 출력을 하지 말아야겠다고 생각해서 @ToString에서 특정 필드를 제외할 수 있는 방법을 찾아보았다.

https://projectlombok.org/features/ToString

‘lombok @ToString’으로 검색했을 때 가장 상단에 뜬, 전에도 방문한 적 있는 Lombok 공식 문서이다.


크.. 이번에는 바로 찾았다.

If you want to skip some fields, you can annotate these fields with @ToString.Exclude.

만약 특정 필드를 출력에서 제외하고 싶으면 해당 필드에 @ToString.Exclude 애노테이션을 사용해라~ 라고 한다. 바로 적용했다.

@Entity
...
@ToString
...
public class Cafe {

		...

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member owner;

    @OneToMany(mappedBy = "cafe")
    @ToString.Exclude
    private final List<Menu> menus = new ArrayList<>();

    @OneToMany(mappedBy = "cafe")
    @ToString.Exclude
    private final List<Review> reviews = new ArrayList<>();

    @OneToMany(mappedBy = "cafe")
    @ToString.Exclude
    private final List<Employee> employees = new ArrayList<>();

    @OneToMany(mappedBy = "cafe")
    @ToString.Exclude
    private final List<Image> images = new ArrayList<>();

    ...

}

근데 CafeMember 객체 owner를 갖고 있기 때문에 Member 객체의 List도 마찬가지로 처리해 주어야 한다.

Member 객체의 List<Cafe>도 출력해 보고 싶어서 @ToString.Exclude를 사용하지 않고 @OneToMany(…, fetch = FetchType.EAGER)로 변경해서 실행해 봤었는데, StackOverFlowError가 발생했다. 당연한 결과이다. Cafeowner가 출력 되는데, ownerList<Cafe>가 출력되고, 또 List<Cafe>Cafe.owner가 출력될 것이고… 무한히 순환되는 결과이다.(이게 순환 참조인가..?)

참 부족한 점이 많지만, 이렇게 시행착오를 거치며 배우는 점이 많은 것 같아서 좋다!


정리!

양방향 연관관계 매핑 시 lombok의 @ToString 애노테이션을 사용해 객체의 값들을 출력할 때 LazyInitializationException이 발생할 수 있다. 따라서 특정 필드를 출력에서 제외하는 @ToString.Exclude를 활용하자.

profile
좋은 개발자가 되자.

0개의 댓글