작성
·
80
0
첫번째 JPQL
Query("select m from Member m join m.team ")
List<Member> findInnerJoin();
@Test
public void test() {
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
teamRepository.save(teamA);
teamRepository.save(teamB);
Member member1 = new Member(10, "member1", teamA);
Member member2 = new Member(10, "member2", teamB);
memberRepository.save(member1);
memberRepository.save(member2);
em.flush();
em.clear();
List<Member> members = memberRepository.findInnerJoin();
for (Member member : members) {
System.out.println("member = " + member);
System.out.println("member.getTeam().getClass() = " + member.getTeam().getClass());
System.out.println("member.getTeam().getName() = " + member.getTeam().getName());
}
}
실행결과
Hibernate:
select
m1_0.member_id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
member m1_0
join
team t1_0
on t1_0.team_id=m1_0.team_id
member = Member(id=1, username=member1, age=10)
member.getTeam().getClass() = class study.data_jpa.entity.Team$HibernateProxy$w0d2fXxq
Hibernate:
select
t1_0.team_id,
t1_0.name
from
team t1_0
where
t1_0.team_id=?
member.getTeam().getName() = teamA
member = Member(id=2, username=member2, age=10)
member.getTeam().getClass() = class study.data_jpa.entity.Team$HibernateProxy$w0d2fXxq
Hibernate:
select
t1_0.team_id,
t1_0.name
from
team t1_0
where
t1_0.team_id=?
member.getTeam().getName() = teamB
첫번째 JPQL에서는 전형적인 N + 1 문제가 발생함을 알 수 있습니다. 그래서 첫번째 JPQL에서의 select문에 t만 추가를 해봤습니다.
두번째 JPQL
@Query("select m, t from Member m join m.team t")
List<Member> findInnerJoin();
실행결과
Hibernate:
select
m1_0.member_id,
m1_0.age,
m1_0.team_id,
m1_0.username,
t1_0.team_id,
t1_0.name
from
member m1_0
join
team t1_0
on t1_0.team_id=m1_0.team_id
member = Member(id=1, username=member1, age=10)
member.getTeam().getClass() = class study.data_jpa.entity.Team
member.getTeam().getName() = teamA
member = Member(id=2, username=member2, age=10)
member.getTeam().getClass() = class study.data_jpa.entity.Team
member.getTeam().getName() = teamB
위 실행 결과를 보고 페치 조인과 무슨 차이가 있을까 해서 페치 조인을 테스트 해봤습니다.
세번째 JPQL
@Query("select m from Member m join fetch m.team t")
List<Member> findMemberFetchJoinTeam();
@Test
public void ManyToOneFetchJoin() {
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
teamRepository.save(teamA);
teamRepository.save(teamB);
Member member1 = new Member(10, "member1", teamA);
Member member2 = new Member(10, "member2", teamB);
memberRepository.save(member1);
memberRepository.save(member2);
em.flush();
em.clear();
List<Member> members = memberRepository.findMemberFetchJoinTeam();
for (Member member : members) {
System.out.println("member = " + member);
System.out.println("member.getTeam().getClass() = " + member.getTeam().getClass());
System.out.println("member.getTeam().getName() = " + member.getTeam().getName());
}
}
실행결과
Hibernate:
select
m1_0.member_id,
m1_0.age,
t1_0.team_id,
t1_0.name,
m1_0.username
from
member m1_0
join
team t1_0
on t1_0.team_id=m1_0.team_id
member = Member(id=1, username=member1, age=10)
member.getTeam().getClass() = class study.data_jpa.entity.Team
member.getTeam().getName() = teamA
member = Member(id=2, username=member2, age=10)
member.getTeam().getClass() = class study.data_jpa.entity.Team
member.getTeam().getName() = teamB
두번째 JPQL과 세번째 JPQL의 차이를 보면 두번째 JPQL의 select문에서 m1_0.team_id 도 조회하고 세번째 JPQL의 select문에서는 m1_0.team_id 을 조회하지 않습니다.
두번째 JPQL과 세번째 JPQL이 정확히 어떤 차이가 있어 select문에서 조회하는 컬럼이 달라지는지 궁금합니다.
답변 2
0
0
안녕하세요, 인프런 AI 인턴입니다.
감바스님의 질문에 대한 답변입니다. 단순 조인(join
)과 페치 조인(join fetch
)의 차이는 두 방식이 엔티티와 연관된 엔티티를 어떻게 처리하는지에 있습니다.
단순 조인을 사용할 때, 연관된 엔티티는 처음에는 프록시 객체로 가져옵니다. 그 결과, 해당 연관 객체에 접근할 때마다 추가적인 쿼리(select 문)가 실행되어 성능상의 문제인 N + 1 문제가 발생합니다. 이는 첫 번째 JPQL의 실행 결과에서 확인할 수 있습니다.
두 번째 JPQL에서 볼 수 있듯이, select m, t from Member m join m.team t
쿼리를 사용하여 명시적으로 연관된 엔티티를 선택함으로써 연관된 엔티티를 한 번의 쿼리로 함께 가져옵니다. 하지만 이는 JPQL 쿼리의 결과로 Object[]
타입(여기서는 Member와 Team)을 반환해야 합니다(실제 사용 시에는 코드 예시에 따라 List
세 번째 JPQL에서 사용한 페치 조인은 연관된 엔티티를 즉시 로딩하여 성능 문제를 해결합니다. 페치 조인은 엔티티를 가져올 때 연관된 엔티티도 함께 조회하여 영속성 컨텍스트에 함께 저장하므로, 연관된 엔티티에 대한 추가적인 쿼리가 필요 없습니다. 이 방법은 결과적으로 연관된 엔티티를 한 번의 쿼리로 완벽하게 로딩합니다.
두 번째와 세 번째 JPQL의 차이점에 대해서, select m, t
방식은 페치 조인과 달리 모든 엔티티의 속성을 선택하기 위해 명시적으로 지정해야 합니다. 반면, 페치 조인은 JPQL 문에서 연관된 엔티티를 자동으로 포함하여, 최적화된 쿼리를 실행합니다. 이러한 차이점이 쿼리에서 조회하는 컬럼에 영향을 줍니다.
즉, 페치 조인은 엔티티와 그 엔티티와 연관된 모든 엔티티를 한 번의 쿼리로 조회하여 영속성 컨텍스트에 저장함으로써, 나중에 연관된 엔티티에 접근할 때 추가적인 쿼리 실행 없이 데이터에 접근할 수 있게 해줍니다. 이는 성능 최적화에 크게 기여합니다.