JAVA ORM - JPA(Fetch Join)
페치조인(Fetch Join)
연관된 엔티티나 컬렉션을 함께 로드하는데 사용되는 기술로, 기본적으로 JPA에서 연관된 엔티티는 지연로딩으로 설정된경우에, 해당 엔티티에 접근할 때마다 별도의 쿼리가 발생하는데 이를 N+1문제라고 함. 페치 조인을 사용해 메인 엔티티와 연관된 엔티티를 한 번의 쿼리로 함께 로드해 성능을 최적화 할 수 있다.
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "product", fetch = FetchType.LAZY)
private List<Order> orders;
...
}
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "product_id")
private Product product;
...
}
위와같은 Product 엔티티와 Order엔티티를 페치조인 하려면,
EntityManager em = entityManagerFactory.createEntityManager();
String jpql = "SELECT p FROM Product p JOIN FETCH p.orders WHERE p.id = :productId";
TypedQuery<Product> query = em.createQuery(jpql, Product.class);
query.setParameter("productId", productId);
Product product = query.getSingleResult();
em.close();
Product 엔티티와 연관된 Order 엔티티를 한번에 로드한다.
위의 JOIN FETCH 구문을 사용하면 연관된 엔티티를 즉시로딩(EAGER)으로 처리해서 별도의 쿼리가 발생하지 않고 한번에 데이터를 가져오게 된다.
SELECT p FROM Product p JOIN FETCH p.orders WHERE p.id = :productId
이 JPQL구문은
SELECT p.id AS p_id, p.name AS p_name, o.id AS o_id, o.product_id AS o_product_id FROM Product p JOIN Order o ON p.id = o.product_id WHERE p.id = ?
이렇게 SQL쿼리로 날아간다.
장점
N+1문제를 해결해 성능향상, 필요한 모든 데이터를 한번의 쿼리로 가져올 수 있다.
단점
Product가 여러개의 Order과 연관된 경우, Product의 데이터가 중복 될 수 있다.(하지만 Jpa는 중복을 제거해서 반환한다.)
조인의 수가 많아지면, 쿼리가 복잡해져 성능저하를 일으킬 수 있다.
JPQL에선 한 번의 쿼리에서 여러개의 컬렉션에 대해 페치조인을 쓸 수 없다.(단일 컬렉션에 대해서만 페치조인을 사용.)
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.getName()
+ ", " + member.getTeam().getName());
}
위와같은 코드에서 쿼리가 몇번날까?
Hibernate:
/* select
m
From
Member m */ select
m1_0.MEMBER_ID,
m1_0.createdBy,
m1_0.createdDate,
m1_0.city,
m1_0.street,
m1_0.zipcode,
m1_0.lastModifiedBy,
m1_0.lastModifiedDate,
m1_0.name,
m1_0.TEAM_ID
from
Member m1_0
Hibernate:
select
t1_0.id,
t1_0.name
from
Team t1_0
where
t1_0.id=?
member = 회원1, 팀A
member = 회원2, 팀A
Hibernate:
select
t1_0.id,
t1_0.name
from
Team t1_0
where
t1_0.id=?
member = 회원3, 팀B
이렇게 세번나간다. 만약 팀이 더 다양하고 회원이 많으면?
끔찍하다.
이럴때 사용해야하는게 바로 페치조인이다.
String query = "select m From Member m join fetch m.team";
JPQL을 이렇게 바꾸면,
Hibernate:
/* select
m
From
Member m
join
fetch
m.team */ select
m1_0.MEMBER_ID,
m1_0.createdBy,
m1_0.createdDate,
m1_0.city,
m1_0.street,
m1_0.zipcode,
m1_0.lastModifiedBy,
m1_0.lastModifiedDate,
m1_0.name,
t1_0.id,
t1_0.name
from
Member m1_0
join
Team t1_0
on t1_0.id=m1_0.TEAM_ID
member = 회원1, 팀A
member = 회원2, 팀A
member = 회원3, 팀B
위와같이 쿼리가 한개만 나가게된다.
컬렉션 페치 조인
일대다 관계에서.
String query = "select t From Team t join fetch t.members";
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
for(Team team : result){
System.out.println("member = " + team.getName()
+ "|" + team.getMembers().size() + "명");
}
이렇게 바꿔줘도
Hibernate:
/* select
t
From
Team t
join
fetch
t.members */ select
t1_0.id,
m1_0.TEAM_ID,
m1_0.MEMBER_ID,
m1_0.createdBy,
m1_0.createdDate,
m1_0.city,
m1_0.street,
m1_0.zipcode,
m1_0.lastModifiedBy,
m1_0.lastModifiedDate,
m1_0.name,
t1_0.name
from
Team t1_0
join
Member m1_0
on t1_0.id=m1_0.TEAM_ID
member = 팀A|2명
member = 팀B|1명
쿼리는 한번만 나가게 된다.