JPA 패치 조인
일반 조인과 패치 조인
엔티티
- order
@Entity @Table(name = "orders") @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Order { @Id @GeneratedValue @Column(name = " order_id") private Long id; @ManyToOne(fetch = LAZY) @JoinColumn(name = "member_id") private Member member; @OneToOne(fetch = LAZY , cascade = CascadeType.ALL) @JoinColumn(name = "delivery_id") private Delivery delivery;
- member
@Entity @Getter @Setter public class Member { @Id@GeneratedValue @Column(name = "member_id") private Long id; @JsonIgnore @OneToMany(mappedBy= "member") private List<Order> orders = new ArrayList<>();
- delivery
@Entity @Getter @Setter public class Delivery { @Id @GeneratedValue @Column(name = "delivery_id") private Long id; @JsonIgnore @OneToOne(mappedBy= "delivery", fetch = LAZY) private Order order;
- order 객체에서 member, delivery 객체로의 Fetch Strategy를 Lazy로 설정하였다.
일반 조인
- JPQL
public List<Order> findAllByString() { return em.createQuery( "select 0 from Order o "+ " join o.member m" + " join o.delivery d", Order.class ).getResultList(); }
- 실행 쿼리
select order0_.order_id as order_id1_6_, order0_.delivery_id as delivery4_6_, order0_.member_id as member_i5_6_, order0_.order_date as order_da2_6_, order0_.status as status3_6_ from orders order0_ inner join member member1_ on order0_.member_id=member1_.member_id inner join delivery delivery2_ on order0_.delivery_id=delivery2_.delivery_id
- 객체를 모두 따로 조회하여 가져옴으로써 성능상 문제가 발생할 수 있다. -> N+1문제
- join 조건을 제외하고 실제 질의하는 대상 Entity에 대한 컬럼만 조회
fetch join
- JPQL
public List<Order> findAllByString() { return em.createQuery( "select 0 from Order o "+ " join fetch o.member m" + " join fetch o.delivery d", Order.class ).getResultList(); }
- 실행 쿼리
select order0_.order_id as order_id1_6_0_, member1_.member_id as member_i1_4_1_, delivery2_.delivery_id as delivery1_2_2_, order0_.delivery_id as delivery4_6_0_, order0_.member_id as member_i5_6_0_, order0_.order_date as order_da2_6_0_, order0_.status as status3_6_0_, member1_.city as city2_4_1_, member1_.street as street3_4_1_, member1_.zipcode as zipcode4_4_1_, member1_.name as name5_4_1_, delivery2_.city as city2_2_2_, delivery2_.street as street3_2_2_, delivery2_.zipcode as zipcode4_2_2_, delivery2_.status as status5_2_2_ from orders order0_ inner join member member1_ on order0_.member_id=member1_.member_id inner join delivery delivery2_ on order0_.delivery_id=delivery2_.delivery_id
- 일반 조인에서는 단순히 member, delivery의 식별자 값만 가지고 있었다면, 패치 조인에서는 member, delivery 원본 객체를 가져왔다.
- 실제 질의하는 대상 Entity와 Fetch join이 걸려있는 Entity를 포함한 컬럼을 함께 조회
정리
- 일반 조인
- 연관 엔티티에 join을 하게되면 Select 대상의 엔티티는 영속화하여 가져오지만, 조인의 대상은 영속화하여 가져오지 않는다.
- 연관 엔티티가 검색 조건에 포함되고, 조회의 주체가 검색 엔티티뿐일 때 사용하면 좋다.
- 패치 조인
- 연관 엔티티에 fetch join을 하게되면 select 대상의 엔티티뿐만 아니라 조인의 대상까지 영속화하여 가져온다.
- 연관 엔티티까지 select의 대상일 때, N+1의 문제를 해결하여 가져올 수 있는 좋은 방법이다.
- 쿼리 방식 선택 권장 순서
- 우선 엔티티를 DTO로 변환하는 방법을 선택한다.
- 필요하면 패치 조인으로 성능을 최적화 한다. -> 대부분의 성능 이슈가 향상된다.
- 그래도 안되면 DTO로 직접 조회하는 방법을 사용한다.
- 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용한다.