[하이버네이트] OneToMany에 FetchType.EAGER 사용시 어떤 일이 생길까?
Plan -> PlanDetail 관계에서 Plan 쪽에서 PlanDetail로 OneToMany 관계를 설정하고, fetch 모드를 EAGER로 설정하면,, 엄청난 문제가 생길 수 있습니다. @_@
P 1 <-- PD 1
P 1 <-- PD 2
P 2 <-- PD 3
P 3
이렇게 PD 두 개가 같은 P에 속해있을 경우, P 목록을 뿌리고자, createQuery("from P").list(); HQL로 이렇게 작성하면.. 쿼리는 다음과 같이 날아가게 됩니다.
(양방향 관계에서 mappedby 설정했다고 가정하면..)
~~ from P p left outer join PD pd on p.id = pd.p_id ~~
(양방향 관계에서 mappedby를 설정하지 않았다고 가정하면.. 이건 거의 최악)
~~ from P p left outer join P_PD p_pd on p.id = p_pd.p_id ~~
LEFT OUTER JOIN 인거죠...ㄷㄷㄷ..
결과는 아래와 같은 모습일 겁니다.
P 1 - PD 1
P 1 - PD 2
P 2 - PD 3
P 3 - null
그래서 DB에서는 레코드가 한 줄인데, 화면에는 두 줄이 나타납니다. 크하하하.. 그런데.. 이게 .. 이상한 일일까요? 글쎄요. 그런 것 같진 않습니다. P가 가지고 있는 컬렉션을 EAGER 패치로 가져오란 얘기가 곧 DB 관점에서는 P를 왼쪽에 두고 LEFT OUTER JOIN해서 P와 연관 맺고 있는 PD도 가져오란 얘기가 될 테니.. 하이버는 그저 시킨대로 한 죄 밖에는 없습니다. 결국 자연스러운 일입니다.
그렇다면 애초에 원했던 결과는 무엇이었을까요? 바로 P 목록만 가져오는 것이었습니다. 그러려면 P를 가져올 때 PD는 내비두고 오로지 P만 가져오게 해야겠죠. 어차피 P 목록을 보려고 하는데 PD 까지 가져올 필요는 없자나요. 패치모드를 LAZY로 바꾸면 from Plan 같은 HQL을 보내면 아예 join을 하지 않습니다.
~~ from P ~~
아마도 이런 SQL을 보시게 될 겁니다.
논외로 하이버 HQL, Criteria로 발생하는 SQL 쿼리를 이해하는 개발자가 되는 길은 멀고도 험한듯 합니다.
예를 들어 이번 이슈(P->PD)에서 패치모드, 방향성, mappedby의 변화로 생기는 쿼리 형태를 조사하려면 몇 가지 경우의 수를 고려해야 할까요?
- 방향성: 총 2가지(P->PD, P<->PD)
- 패치모드: 총 2가지(P->PD Lazy, P->PD Eager)
- mappedby: 총 2가지(P의 pd에 붙인 @OneToMany에서 mappedby="p")
정답은 그렇다면 2 * 2 * 2 = 8 가지? 글쎄요.
몇 번 해보시면 MappedBy 설정은 거의 영향이 없다는 걸 아실 수 있습니다. mappedby를 하면 좋은 점은 연관 테이블 수를 줄일 수 있다는 것. 하지만 결과에 영향이 없는 이유는 연관 테이블(P_PD)과 PD의 row 수가 같기 때문이죠. P가 PD와 left outer join을 하나, P가 P_PD와 left outer join을 하나 결과는 같으니까요.
따라서 2 * 2 = 4 가지 일까요? 그런데 만약 전제로 했던 P -> PD로의 방향성이 PD -> P 방향성 이라면?? ManyToOne이 되는데, 이때는 어떤 변화가 있을까요? @OneToMany의 fetch 속성 기본값은 LAZY 입니다. 별다른 설정을 하지 않으면 위와 같이 원하지 않았던 결과는 발생하지 않겠죠. 하지만 @ManyToOne의 fetch 속성 기본값은 EAGER입니다. 어떻게 될까요? 무슨 일이라도 생길까요? 앞선 경우처럼 DB에 들어있는 PD의 갯수보다 더 많은 PD의 갯수가 출력될까요?
그렇진 않습니다. ManyToOne 관계니까 그럴리는 없습니다.
PD 1 <- P 1
PD 2 <- P 1
PD 3 <- P 2
P 3
PD를 왼쪽에 두고 left outer join을 해봤자. 이런 관계라고 할 때 결과는 다음과 같겠죠.
PD 1 - P 1
PD 2 - P 1
PD 3 - P 2
결과 row과 PD row와 동일한 상태가 됩니다. 따라서 ManyToOne에서도 fetch 모드를 별도로 설정하지 않더라도 HQL로 from PD를 날리면 예상하던(?) 결과를 얻을 수 있습니다. ㅎㅎ 재밌지요.