-
-
안녕하세요? 오늘은 JPA에서 JPQL 패치(FETCH) 조인 에 대해 알아보도록 하겠습니다.
-
페치 조인 ★★★
(FETCH JOIN)
: SQL 조인 종류가 아니라, JPQL에서 성능최적화를 위해 제공
: 연관된 엔티티나 컬렉션을 SQL 한번에 함께 조회
JPQL | SELECT e FROM 엔티티 e JOIN FETCH e.연관관계객체 s |
SQL | SELECT t1.*, t2.* FROM 테이블1 as t1 INNER JOIN 테이블2 as t2 ON t1.key = t2.key |
+
예로 들어서, Member 엔티티와, Team 엔티티가 존재하고, Member는 Team과 다대일 [N:1] 연관관계라고 가정했을 때
멤버 엔티티 생성)
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "MEMBER_NAME")
private String name;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
...
}
팀 엔티티 생성)
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
@Column(name = "TEAM_NAME")
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
...
}
샘플 코드)
EntityManagerFactory emf = Persistence.createEntityManagerFactory("유닛명");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Team teamA = new Team();
team.setName("teamA");
em.persist(teamA);
Team teamB = new Team();
team.setName("teamB");
em.persist(teamB);
Member memberA = new Member();
memberA.setName("memberA");
memberA.setTeam(teamA);
em.persist(memberA);
Member memberB = new Member();
memberB.setName("memberB");
memberB.setTeam(teamA);
em.persist(memberB);
Member memberC = new Member();
memberC.setName("memberC");
memberC.setTeam(teamB);
em.persist(memberC);
em.flush();
em.clear();
String jpql = "SELECT m FROM Member m"
List<Member> result = em.createQuery(jpql, Member.class)
.getResultList();
for (Member member : result) {
System.out.println("member = " + member.getName()
+ ", " + member.getTeam().getName()); // 플래시 실행
}
// 결과 : Member 조회 쿼리 1번 + Team 조회 쿼리 2번
// member 데이터 조회 (SQL) : memberA, memberB, memberC
// memberA, teamA (SQL) : teamA 데이터 조회
// memberB, teamA (1차캐시) : teamA 1차캐시 데이터 조회
// memberC, teamB (SQL) : teamB 데이터 조회
jpql = "SELECT m FROM Member m JOIN FETCH m.team";
result = em.createQuery(jpql, Member.class)
.getResultList();
for (Member member : result) {
System.out.println("member = " + member.getName()
+ ", " + member.getTeam().getName());
}
// 결과 : JOIN으로 한방에 데이터 전체 조회
// member 데이터 조회 (SQL) : memberA, memberB, memberC
// memberA, teamA (1차캐시) : teamA 1차캐시 데이터 조회
// memberB, teamA (1차캐시) : teamA 1차캐시 데이터 조회
// memberC, teamB (1차캐시) : teamB 1차캐시 데이터 조회
tx.commit();
DB 결과) - 멤버 테이블
MEMBER_ID | MEMBER_NAME | TEAM_ID (FK) |
1 | memberA | 1 |
2 | memberB | 1 |
3 | memberC | 2 |
DB 결과) - 팀 테이블
TEAM_ID | TEAM_NAME |
1 | teamA |
2 | teamB |
※ 지연로딩을 해도, 패치 조인이 항상 우선
컬렉션 페치 조인
: 일대다 [1:N] 관계에서 사용
위의 예제에서, Team에서 Member의 데이터를 조회한다고 가정했을 때
샘플코드)
EntityManagerFactory emf = Persistence.createEntityManagerFactory("유닛명");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
String jpql = "SELECT t FROM Team t JOIN FETCH t.members"
List<Member> result = em.createQuery(jpql, Member.class)
.getResultList();
for (Team team : result) {
System.out.println("team = " + team.getName()
+ ", " + team.getMembers().getName()); // 플래시 실행
}
// 결과 : 중복 데이터 조회 -> N + 1 문제 발생 ★★
// teamA, memberA
// teamA, memberB
// teamB, memberC
tx.commit();
+
N+1 문제가 발생
> 중복 결과 데이터 발생
> DISTINCT 사용
>> SQL에 추가 + App에서 중복 제거
샘플코드)
String jpql = "SELECT DISTINCT t"
+ "FROM Team t"
+ "JOIN FETCH t.members"
+ "WHERE t.name = 'teamA'"
List<Member> result = em.createQuery(jpql, Member.class)
.getResultList();
for (Team team : result) {
System.out.println("team = " + team.getName()
+ ", " + team.getMembers().size());
}
// 결과 : 중복 제거 -> 1건 반환
// teamA, 2
+++
Fetch 조인과 일반 조인의 차이점
: JPQL은 결과를 반환할 때, 연관관계를 고려하지 않음
> 단지 SELECT 절에 지정한 엔티티만 조회
>> Team 엔티티만 조회하고, Member 엔티티는 조회X
: 페치 조인을 사용할 때만 연관된 엔티티도 함꼐 조회 (즉시로딩)
: 페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념
+++
페치 조인의 한계
: 페치 조인 대상에는 별칭 부여 불가
: 둘 이상의 컬렉션은 사용 불가
: 페이징 API 사용 불가
> 단일 값 연관 필드(일대일, 다대일)들은 가능
페이조인은 객체 그래프를 유지할 때 사용하면 효과적
여러 테이블을 조인해서, 엔티티가 가진 모양이 아닌, 전혀 다른 결과를 내야한다면, 페이조인 보다는, 일반 조인을 사용하고, 필요한 데이터만 조회해서, DTO로 반환하는 것이 효과적 |
※ JPA에서의 쿼리 방법에 대해서는, 아래 포스팅을 참조 부탁드리겠습니다.
https://logger-debug.tistory.com/entry/JPA-JPQL-%EA%B2%BD%EB%A1%9C-%ED%91%9C%ED%98%84%EC%8B%9D
오늘은 JPA에서 JPQL 패치 조인 에 대해서 알아보았습니다.
그럼 오늘도 즐거운 하루 되시길 바라겠습니다.
'프로그래밍 > Back-end' 카테고리의 다른 글
자바에서 경로 다루기: File, Request, Session을 활용한 경로 처리 방법 (0) | 2024.11.12 |
---|---|
자바 AWS SNS ( Amazon Simple Notification Service ) ! 아마존 문자 서비스 구현 ! (0) | 2024.10.25 |
자바 AWS SES ( Amazon Simple Email Service ) 아마존 이메일 서비스 구현 ! (0) | 2024.10.25 |
JPA 쿼리 JPQL 벌크 연산 (0) | 2024.10.24 |
JPA JPQL 경로 표현식 (0) | 2024.10.23 |
JPA JPQL 타입 표현식과 기타식 그리고 조건식과 기본 함수 (0) | 2024.10.18 |
JPA 쿼리 JPQL 페이징 조인 서브쿼리 (0) | 2024.10.18 |
JPA QueryDSL 네이티브SQL JDBC MyBatis (0) | 2024.10.18 |