Fetch Join이랑 친해지는 시간을 갖도록 하겠습니다.
fetch Join은 실무에서 엄청 중요하다.
- 김영한님 -
🔎 Fetch Join이 뭘까?
1. JPQL에서 성능 최적화를 위해 제공하는 기능
2. 연관된 엔티티 및 컬렉션을 한번의 쿼리로 조회하는 기능
🔎 위에 나온 두 기능이 뭔데?
우선, 연관된 엔티티 및 컬렉션을 한번의 쿼리로 조회 하는 기능을 먼저 알아보겠습니다.
현재, 회원 엔티티와 팀 엔티티가 팀을 기준으로 1:N 관계를 맺고 있습니다.
1:N 관계는 한개의 팀에 여러명의 회원이 오는 관계입니다.
코드는 다음과 같습니다.
회원 엔티티
package com.example.spring_jpa_basic.페치조인;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@Entity
@NoArgsConstructor
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name ="team_id")
private Team team;
public User(String name, Team team) {
this.name = name;
this.team = team;
team.getUsers().add(this);
}
}
Team 필드에 FetchType.LAZY를 준 이유는 다음과 같습니다.
@ManyToOne 어노테이션은 아래 사진과 같이 FetchType이 EAGER로 설정되어 있습니다.
User 엔티티를 조회하게 되면, Team 엔티티도 추가 쿼리를 날려 데이터를 가져오게 됩니다.
만약, User 엔티티에 대한 데이터만을 가져오길 원하는 상황이라면, 불필요한 쿼리가 날라가는 N+1 문제가 발생하게 됩니다.
팀 엔티티
package com.example.spring_jpa_basic.페치조인;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@Entity
@NoArgsConstructor
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<User> users = new ArrayList<>();
public Team(String name) {
this.name = name;
}
}
Fetch Join의 기능을 알아보기 위해 다음과 같은 상황을 가정하도록 하겠습니다.
개발자A씨는 현재 회원에 대한 데이터와 그 회원이 속한 팀의 이름을 조회해야 하는 상황에 놓여있습니다.
(회원은 총 3명이 있고, 각 회원은 서로 다른 팀에 속해있습니다.)
📍 Fetch Join을 사용하지 않은 경우
먼저, Fetch Join을 사용하지 않은 상황에 대한 쿼리문을 확인해 보겠습니다.
테스트 하고자 하는 코드는 다음과 같습니다.
@Test
@Transactional
@Rollback(value = false)
void 페치조인() throws Exception {
// 팀 저장
Team teamA = new Team("A");
Team teamB = new Team("B");
Team teamC = new Team("C");
em.persist(teamA);
em.persist(teamB);
em.persist(teamC);
// 회원 저장
User user1 = new User("user1", teamA);
User user2 = new User("user2", teamB);
User user3 = new User("user3", teamC);
em.persist(user1);
em.persist(user2);
em.persist(user3);
em.flush();
em.clear();
String query = "select u from User u";
List<User> resultList = em.createQuery(query, User.class).getResultList();
for(User user: resultList) {
System.out.println("팀 이름 : " + user.getTeam().getName());
}
}
테스트 코드를 돌린 후, 쿼리문이 어떻게 나가는지 확인해보도록 하겠습니다.
1. 회원을 조회하는 쿼리가 1번 나갑니다.
2. 각 회원의 팀 이름을 조회하는 쿼리를 3번 날립니다.
총 4번의 쿼리가 날라가게 됩니다.
📍 Fetch Join을 사용한 경우
Query문만 변경해줍니다.
String query = "select u from User u join fetch u.team";
총 1번의 쿼리가 날라가게 됩니다.
📍결과 비교
Fetch Join을 사용하지 않은 경우 총 쿼리 4번 날라간다.
Fetch Join을 사용하는 경우 총 쿼리 1번 날라간다.
📍결론
Fetch Join은 한번의 쿼리로 조회를 가능하게 해주어 쿼리의 횟수를 확연히 줄여준다.
'JPA' 카테고리의 다른 글
동시성 문제 해결방법(Update Query편) (0) | 2025.01.29 |
---|---|
save()와 SELECT의 관계 (0) | 2025.01.26 |
JPA와 DB는 어떻게 동기화가 되는걸까? (0) | 2025.01.26 |
🙆🏻♂️ 너, Fetch Join 만능이야? (0) | 2024.09.24 |
@DataJapTest를 사용하는 이유 (0) | 2024.07.17 |