N+1 문제
이전에 김영한님의 JPA 강의 중 연관관계 어노테이션에 @XToOne에 Fetch.Type을 LAZY를 추천한다.
@XToMany는 기본으로 LAZY가 걸려있지만 @XToOne은 EAGER가 걸려 SELCT 하는 엔티티가 참조하는 엔티티도 모두 불러오기 때문이다. 참조 타입을 LAZY로 걸고 해당 엔티티를 사용하지 않으면 N+1 문제를 발생하는 쿼리를 발생시키지 않지만 LAZY를 걸어도 해당 엔티티를 사용하면 엔티티 프록시 객체는 모든 값을 가지고 있지 않기 때문에 값을 가져오기 위해 (영속성 컨텍스트,DB)로 N+1 문제를 발생하는 쿼리를 날린다.
Join, Fetch Join
Spring Data JPA가 제공해주는 것 외에도 JPQL을 사용해야하는 경우엔 @Query를 사용한다.
JPQL + 일반 JOIN
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query(value = "select o from Order o join o.member m where m.member = :name")
List<Order> findQuery(@Param("name") String name); // Order -> Member 접근시 N+1 쿼리 발생
}
검색할 때만 연관 엔티티를 참조하고 조회된 엔티티에서 연관 엔티티에 접근할 때는 N+1 문제가 발생한다.
JPQL + Fetch Join
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query(value = "select o from Order o Fetch Join o.member m where m.member = :name")
List<Order> findQuery(@Param("name") String name); // Order -> Member OK
}
조회의 주체가 되는 엔티티 외에 Fetch Join이 걸린 연관 엔티티도 함께 영속화 시켜준다.
@Query 사용
native Query 옵션
가장 중요하다
1. 디폴트는 false로 JPQL을 사용한다.
2. true로 바꾸면 특정 DB에 의존하는 SQL을 사용한다.
JPQL은 엔티티 클래스 기준
대소문자 구별이 없는 DB처럼 사용하면 오류난다.클래스는 대문자로, 필드이름도 '_'로 나누는게 아닌 카멜케이스로 나눠야한다.@Param을 이용할 때는 파라미터로 받는 값도 필드와 일치하는 타입을 써야한다.엔티티에 거는@Table(name=value)든 @Column(name=value)들은 DB에 저장되는 내용이고 JPQL 내에 들어가는 클래스와 필드는 작성한 소스코드의 클래스 , 필드를 사용해야한다.
public interface OrderRepository extends JpaRepository<Order, Long> {
// JPQL 사용 시에는 DB 기준이 아닌 클래스, 필드에 초점을 맞추고 대소문자 구분이 없는 DB와 달리 구분해야 한다.
@Query(value = "select o from Order o JOIN FETCH o.member m WHERE (:name is null or m.name = :name) " +
" and (:status is null or o.orderStatus = :status)")
List<Order> findQuery(@Param("name") String name, @Param("status") OrderStatus orderStatus);
}
참고