작성
·
8.5K
42
안녕하세요 영한님
기다리던 2편도 어제부터 너무 즐겁게 보고 있습니다
항상 좋은 강의 감사드립니다
테스트 코드 작성 중 2가지 질문이 있어 질문 드립니다
기본편 패치조인 한계편 초반에 보면 fetch join 시 별칭을 줄 수 없다고 되어 있습니다
그 이유는 별칭을 준 이후 on 에서 별칭으로 조건을 주면 OneToMany 관계에서 Collection 형태로 조회되는 데이터가 전부 조회되지 않고 일부만 나오기 때문에 문제가 생길 수 있다는 설명도 이해를 했습니다
이 과정에서 2가지 질문이 있습니다
1. 테스트를 해보면 fetch join JPQL에 on 조건을 주면 무조건 에러가 발생합니다 (with-clause not allowed on fetched association)
fetch 조인 시 on 조건을 넣는 것 자체가 안된다고 생각해도 되는 것인가요?
Member -> Team @ManyToOne
Team -> Member @OneToMany
2. 1번 질문에서 만약 fetch 조인 시 on 조건 자체가 불가능하게 설계 되었다면 Collection의 값이 항상 보장되는 쿼리는 실행이 되어야 하지 않을까 해서 설계상의 원칙인지 제가 놓치고 있는 부분이 있는지 궁금합니다
ex) Select t from Team t join fetch t.member m on m.name=:memberName
(memeber 컬렉션이 전부 조회되지 않기 때문에 실행 불가능 한 것을 이해)
ex) Select t from Team t join fetch t.member m on t.name=:teamName
(Team을 기준으로 조건을 주었기 때문에 member Collection에는 영향이 없어 문제가 없지 않을까? 라고 생각합니다)
3. fetch 조인 시 where문 사용 의견
fetch 조인 시 where 조건은 동작하는 것을 테스트에서 확인했습니다
1) OneToMany 조건에서 where 조건을 Collection이 아닌 조건에만 사용 하는 것
ex) Select t from Team t join fetch t.member m where t.name=:teamName
2) ManyToOne, OneToOne에서 where 조건을 양쪽 모두 사용 하는 것
ex) Select m from Member m join fetch m.team t where m.name=:memberName
Select m from Member m join fetch m.team t where t.name=:teamName
위처럼 사용하는 것에 대한 의견 부탁드립니다
그리고 QueryDSL 강의 기다리고 있습니다 ^^
답변 4
37
그리고 추가 답변을 몇가지 드릴께요^^
생각해보니 이 부분을 말씀을 안드렸네요.
그러면 fetch join을 별칭으로 사용해도 되는 경우는 언제인가? 하이버네에트가 허용하는 이유가 있겠지요?
먼저 일관성이 문제가 없으면 사용해도 됩니다!
예를 들어서 문의주신 내용중에 다음 쿼리는 가능합니다.
Select m from Member m join fetch m.team t where t.name=:teamName
이 쿼리는 회원과 팀의 일관성을 해치지 않습니다. 그러니까 조회된 회원은 db와 동일한 일관성을 유지한 팀의 결과를 가지고 있습니다.
하지만 이 쿼리를 left join fetch로 변경하면 일관성이 깨질 수 있습니다.
추가로 일관성이 깨져도 엔티티를 변경하지 않고 딱! 조회용으로만 주의해서 사용하면 크게 문제는 없습니다. 하지만 이 경우 정말 조심해서 조회 용도로만 사용해야합니다. (여기서 2차 캐시 등등을 사용하면 또 문제가 될 수 있기 때문에 ㅠㅠ 여기까지 가면 너무 복잡해지네요)
저도 실무에서는 일관성을 해치지 않는 범위에서 성능 최적화를 위해 패치 조인 대상에 별칭을 종종 사용합니다^^
감사합니다. 또 궁금한 내용이 있으면 언제든지 편하게 질문주세요. (평일은 일하고, 주말에는 육아 때문에 답변이 좀 많이 늦을 수는 있습니다 ㅎㅎ)
34
안녕하세요 jhwoo님^^
좋은 질문입니다. JPA를 진지하게 깊이 공부하는게 느껴지네요.
먼저 정말 fetch join 대상에 별칭을 주면 안되는가! 라고하면, JPA 표준 스펙에는 fetch join 대상에 별칭이 없습니다. 그런데 하이버네이트는 허용합니다. 그 말은 결국 fetch join 대상에 별칭을 사용은 할 수 있지만 말씀드린 문제들이 발생할 수 있으니 주의해서 사용해야 합니다.
그 주의라는 것이 결국 적어주신 것 처럼 조인 대상을 필터를 하는 행위입니다.
이제 답변을 하나씩 드릴께요^^
1. 테스트를 해보면 fetch join JPQL에 on 조건을 주면 무조건 에러가 발생합니다 (with-clause not allowed on fetched association)
2번에서 같이 답변을 드릴께요.
2. 1번 질문에서 만약 fetch 조인 시 on 조건 자체가 불가능하게 설계 되었다면 Collection의 값이 항상 보장되는 쿼리는 실행이 되어야 하지 않을까 해서 설계상의 원칙인지 제가 놓치고 있는 부분이 있는지 궁금합니다
ex) Select t from Team t join fetch t.member m on m.name=:memberName
(memeber 컬렉션이 전부 조회되지 않기 때문에 실행 불가능 한 것을 이해)
-> 이건 이해하셨으니 패스할께요
ex) Select t from Team t join fetch t.member m on t.name=:teamName
(Team을 기준으로 조건을 주었기 때문에 member Collection에는 영향이 없어 문제가 없지 않을까? 라고 생각합니다)
-> 적어주신 쿼리의 의도는 사실 다음과 같은 쿼리입니다. 조인과는 무관하게 Team 자체의 데이터를 필터링 하는 것이기 때문에 on에 사용하는 것은 의도에 맞지 않습니다. 따라서 다음과 같이 where를 사용하는 것이 맞습니다.
Select t from Team t join fetch t.member m where t.name=:teamName
3. fetch 조인 시 where문 사용 의견
여기서 조심해야 할 점이 있습니다. 예시를 들어보겠습니다.
select t from Team t join fetch t.members m where m.username = ?
이 쿼리를 보면 Team은 fetch join 대상이 아니므로 where에서 마음껏 사용해도 됩니다!!
그런데 Member(t.members m)은 패치 조인 대상입니다! 따라서 where 같은 곳에서 사용하면 위험합니다. 왜 위험한지 다음 코드를 실행해보시면 이해할 수 있습니다^^!
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
package com.example.demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class DemoApplicationTests {
@Autowired
EntityManager em;
@Test
public void contextLoads() {
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member1 = new Member();
member1.setUsername("m1");
member1.setTeam(team);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("m2");
member2.setTeam(team);
em.persist(member2);
em.flush();
em.clear();
List<Team> result = em.createQuery("select t from Team t join fetch t.members m where m.username = 'm1'", Team.class)
.getResultList();
for (Team team1 : result) {
System.out.println("team1 = " + team1.getName());
List<Member> members = team1.getMembers();
for (Member member : members) {
System.out.println("member = " + member.getUsername());
}
}
}
}
실행결과
team1 = teamA
member = m1
실행결과를 보면 member가 하나만 존재합니다.
애플리케이션에서 fetch join의 결과는 연관된 모든 엔티티가 있을것이라 가정하고 사용해야 합니다. 이렇게 페치 조인에 별칭을 잘못 사용해서 컬렉션 결과를 필터링 해버리면, 객체의 상태와 DB의 상태 일관성이 깨지는 것이지요.
간단하게 한줄로 정리하면 다음과 같습니다^^
결론: fetch join의 대상은 on, where 등에서 필터링 조건으로 사용하면 안된다.
감사합니다^^
안녕하세요 영한님 3번 답변에 구체적인 사용하면 안되는 이유가 뭔지 몰라서 질문이 있습니다.
처음에는 쿼리자체가 사용자가 의도한대로 team= teamA, member = m1을 뽑아온거니까 맞는게 아닌가로 생각했습니다.
근데 JPA의 em.createQuery의 em.flush()가 동작해서 영속성 문제가 발생할 수 있기 때문에 사용하지 말라고 하는 구체적인 이유라고 생각하는데 제 생각이 맞나요?
17
저도 처음 JPA를 공부할 때 비슷한 고민을 했던 기억이 나네요 ㅎㅎ 이런 진지한 고민들이 저는 너무 좋아요. jhwoo님 덕분에 이런 내용이 궁금했던 다른 분들도 궁금증을 풀 수 있겠네요 ㅎㅎ 저도 감사합니다
13
혹시 left join fetch를 실행하였을때 어떤 방식으로 일관성이 깨질 수 있는지 알려주실수 있나요? 예전 글이지만 너무 궁금해져서 여쭙니다.
left join fetch 를 하면 team이 없는 멤버들이 추가로 조인이 될텐데, team의 members 리스트에 영향이 가는건가요?