다음과 같은 테스트 코드를 작성하다가 오류가 발생했다. 카페의 이름에 검색어가 포함된 카페들을 조회하는 기능을 테스트하는 코드이다. 테스트 코드에서 값을 출력해서 확인하는 방법은 좋은 방법은 아니지만, 궁금해서 출력해 보고 싶었다.
@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
출력할 때 Cafe
의 menus
가 문제인 것 같다. @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<>();
...
}
그래서 처음에 무식하게 ‘그럼 FetchType.EAGER
로 바꿔주면 되겠네~’하고 fetch 전략을 변경하는데, Cafe
에는 List
가 네 개나 있다. 근데 이걸 다 그렇게 바꿔주려니 뭔가 귀찮기도 하고, 무엇보다 만약 저게 다 출력되면 굉장히 출력하는 데 비용이 많이 들고 지저분해질 것 같았다. 그래서 그냥 출력을 하지 말아야겠다고 생각해서 @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<>();
...
}
근데 Cafe
가 Member
객체 owner
를 갖고 있기 때문에 Member
객체의 List
도 마찬가지로 처리해 주어야 한다.
Member
객체의List<Cafe>
도 출력해 보고 싶어서@ToString.Exclude
를 사용하지 않고@OneToMany(…, fetch = FetchType.EAGER)
로 변경해서 실행해 봤었는데,StackOverFlowError
가 발생했다. 당연한 결과이다.Cafe
의owner
가 출력 되는데,owner
의List<Cafe>
가 출력되고, 또List<Cafe>
의Cafe.owner
가 출력될 것이고… 무한히 순환되는 결과이다.(이게 순환 참조인가..?)
참 부족한 점이 많지만, 이렇게 시행착오를 거치며 배우는 점이 많은 것 같아서 좋다!
양방향 연관관계 매핑 시 lombok의 @ToString
애노테이션을 사용해 객체의 값들을 출력할 때 LazyInitializationException
이 발생할 수 있다. 따라서 특정 필드를 출력에서 제외하는 @ToString.Exclude
를 활용하자.