NHN Cloud 2019 강연
https://www.youtube.com/watch?v=rYj8PLIE6-k
Spring JPA의 사실과 오해
- 단방향이면 항상 충분한가?
- 대부분은 그렇다. 하지만 일대다(1:N) 단방향 연관관계 매핑에서 영속성 전이(cascade)를 통한 insert시에 외래키 지정을 위한 추가적인 update 쿼리가 발생한다
- 이 경우 오히려 일대다 양방향 연관관계로 변경시에 추가적인 update 쿼리가 없어진다
- N+1 문제는 EAGER fetch 전략 때문이다?
- 아니다. EAGER, LAZY는 fecth 되는 시점만 차이가 있다
- 연관 엔티티를 참조한다면 그 순간 추가적인 쿼리가 발생하는 것은 동일하다
- findAll() 메서드는 N+1 문제를 발행하지 않는다?
- 아니다. Fetch 전략 (LAZY)을 적용하는 경우는 단일 레코드에서만 적용된다.
- 단일 레코드가 아닌 경우 (JPQL을 수행하는 경우)
- 해당 JPQL 을 먼저 수행
- 반환된 레코드 하나하나에 대해 Entity에 설정된 Fetch 전략을 적용해서 연관 Entity를 가져옴
- 그렇게 때문에 findAll() 메소드 호출도 이 과정에서 N+1 문제 발생
- Fetch Join에서의 흔히하는 실수
- Pagination + fetch join
- 둘 이상의 컬렉션을 Fetch join ⇒ MultipleBagFetchException
- List ⇒ Bag이라는 타입으로 매핑
- 둘 이상의 컬렉션(Bag)을 fetch join하는 경우 그 결과로 만들어지는 카타시안곱에서 어느 행이 유효한 중복을 포함하고있는지 판단이 불가
- List를 Set으로 바꾸거나 @OrderColumn으로 변경하여 해결
Spring Dat JPA Repository의 사실과 오해
- JPA Repository 메소드로는 JOIN 쿼리를 실행할 수 없다?
- 아니다
- List<Person> findByAddress_Zipcode(String zipcode);
- Person의 Embeddable인 Address 아래 (_, underscore) zipcode 속성으로 찾아라!
- List<Member> findByDetails_Pk_Type(String type);
- Member의 일대다 관계 매핑인 details (List<MemberDetail>) 의 아래인 Embeddable인 Pk 아래의 type 속성으로 찾아라!
- Page Interface vs Slice Interface
- Page가 Slice를 상속받고 있음
- 쿼리시 Page는 count(*) 쿼리가 하나 더 실행된다
- Slice는 페이징 수보다 1개 더 가져와서 더 가져올 수 있는지를 판단한다
- Page<Member> …
- Slice<Member> …
- 따라서 Slice를 사용하면 성능상의 이점을 받을 수 있다
- 하지만 limit 보다 적은 개수를 가져오는 경우에는
- JPA Repository 메소드로는 DTO Projection을 할 수 없다?
-
Class 기반 (DTO) Projection
- DTO에 @Value 애노테이션
- Collection<MemberDto> findByName(String name) (그대로임)
-
Interface 기반 Projection
- 인터페이스에서 getName만 선언
- Collction<MemberNameOnly> findByCreateDateAfter(LocalDateTime createDate);
- select name from Members where create_dt > {createDate}
public interface MemberNameOnly{
String getName();
// 계층 구조 지원
List<MemberDetailDto> getDetails();
interface MemberDetailDto{
// Member 아래 MemberDetail 아래 pk 아래 type
@Value("#{target.pk.type}")
String getType();
String getDescription();
}
}
-
Dynamic Projection (제네릭!)
- <T> Collector<T> findByCreateDateAfter(LocalDateTime createDate, Class<T> type);