BE전문가 프로젝트

JPQL - 페치 조인(fetch join) 본문

JPA

JPQL - 페치 조인(fetch join)

원호보고서 2022. 11. 8. 21:58
  • SQL 조인 종류X
  • JPQL에서 성능 최적화를 위해 제공하는 기능
  • 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능
  • join fetch 명령어 사용
  • 페치ㅣ 조인 ::=[LEFT[OUTER] | INNER] JOIN FETCH

엔티티 페치 조인

  • 회원을 조회하면서 연관된 팀도 함께 조회(SQL 한 번에)
  • SQL을 보면 회원 뿐만 아니라 팀(T.*)도 함께 SELECT
  • [JPQL] - select m from Member m join fetch m.team
  • [SQL] - select M.*, T.* FROM MEMBER M INNER JOIN TEAM T ON M.TEAM_ID = T.ID

즉시로딩과 같지만 쿼리로 원하는 데로 객체 그래프를 한번에 조회할 것이라는 것을 명시적으로 동적인 타이밍에 정할 수 있다는 것이 차이점이다.

@Entity
@Getter
@Setter
public class Member {

    @Id @GeneratedValue
    private Long id;
    private String username;
    private int age;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

Member에 fetch를 지연 로딩으로 설정해준다.

Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);

Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);

Member member1 = new Member();
member1.setUsername("member1");
member1.setTeam(teamA);
em.persist(member1);

Member member2 = new Member();
member2.setUsername("member2");
member2.setTeam(teamA);
em.persist(member2);

Member member3 = new Member();
member3.setUsername("member3");
member3.setTeam(teamB);
em.persist(member3);

String query = "select m from Member m";
List<Member> result = em.createQuery(query, Member.class)
                    .getResultList();

for (Member member :result) {
	System.out.println("member = " + member.getUsername() + ", " + member.getTeam());
    //회원1, 팀A(SQL)
    //회원2, 팀A(1차캐시)
    //회원3, 팀B(SQL)
}

tx.commit();

실행을 하면 조회하는 쿼리는 총 3번나간다(Member조회,  회원1의 team조회, 회원3의 Team조회)

만약에 100명을 조회하게 되면 쿼리가 101번 나가는 경우가 생길 수도 있다. 이것을 n+1 문제라고 한다.

따라서 이것을 해결하는 경우는 join fetch를 통해 해결해야한다.

 

String query = "select m from Member m join fetch m.team";
List<Member> result = em.createQuery(query, Member.class)
		.getResultList();

위에 있는 코드를 실행하면 이런 쿼리가 나오는데 Pintln으로 getTeam을 가져올 때 나오는 team은 프록시가 아닌 영속성컨텍스트에 있는 데이터이다.

지연로딩을 설정해도 fetch join이 우선으로 실행된다.

 

컬렉션 페치 조인

  • 일대다 관계, 컬렉션 페치 조인
-- JPQL
select t
from Team t join fetch t.members
where t.name = '팀A'
		↓
--SQL
select T.*, M.*
from TEAM T INNER JOIN MEMBER M ON T.ID = M.TEAM_ID
WHERE T.NAME = '팀A'

 

쿼리 실행

String query = "select t from Team t join fetch t.memberList";
List<Team> result = em.createQuery(query, Team.class)
    .getResultList();

for (Team team :result) {
	System.out.println("member = " + team.getName() + ", " + team.getMemberList().size());

	for (Member member : team.getMemberList()) {
		System.out.println("-> member = " + member);
	}
}

위에 있는 코드를 실행해보면 중복으로 팀A가 2번 나오는 것을 확인할 수 있다.

컬렉션일 때, 즉 일대다 join일 경우 일 때는 데이터가 뻥튀기가 된다.

 JPA는 데이터를 전해주는 만큼 그래도 전해주기 때문에 팀A가 2개가 나온 것이다.

 

페치 조인과 DISTINCT

SQL의 DISTINCT는 중복된 결과를 제거하는 명령

- JPQL의 DISTINCT 2가지 기능 제공

  • 1. SQL에 DISTINCT를 추가
  • 2. 애플리케이션에서 엔티티 중복 제거

- DISTINCT가 추가로 애플리케이션에서 중복 제거 시도

- 같은 식별자를 가진 Team 엔티티 제거

 

페치 조인과 일반 조인의 차이

  • 일반 조인 실행시 연관된 엔티티를 함께 조회하지 않음
  • JPQL은 결과를 반환할 때 연관관계 고려X
  • 단진 SELECT 절에 지정한 엔티티만 조회할 뿐
  • 여기서는 팀 엔티티만 조회하고, 회원 엔티티는 조회 X
  • 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩)
  • 페치 조인은 연관된 엔티티를 함께 조회함
--JPQL
select t
from Team t join t.members m
where t.name = '팀A'

--SQL
select T.*
from TEAM T INNER JOIN MEMBER M ON T.ID = M.TEAM_ID
where T.NAME = '팀A'

Team의 데이터만 가져오고 Member에 관련된 데이터는 가져오지 않는다.

 

'JPA' 카테고리의 다른 글

다형성 쿼리  (0) 2022.11.10
페치조인의 한계  (0) 2022.11.09
경로 표현식  (0) 2022.11.08
JPQL 함수  (0) 2022.11.08
조건식(Case 식 등)  (0) 2022.11.07
Comments