-
[Spring JPA] JPA의 사실과 오해 (feat. NHN Cloud)Back End/Spring Data JPA 2021. 12. 23. 14:34
JPA 를 쓰면 쓸수록, 너무 불편한게 많다.
검색으로 원하는 고급정보를 찾기도 어렵다.
(정보가 다들 카더라 통신을 통해 대량으로 재생산 되고 있어 깊이가 없을 뿐더러 정확한 검색을 막고 있다.)
(깊이 있는 정보는 어딘가에 묻혀있겠지 😢)
그리하여 알짜정보를 찾기 위해 검색의 무한루프에 빠져있던 중
카더라 통신에서 잘 보지 못한 새로운 정보를 알게되어 정리해두고자 한다.
(중복된 정보 또한 다시 복습할 겸 정리한다.)
* 해당 정보는 NHN Cloud 유튜브채널에서 획득하였습니다.
(모든 저작권은 NHN에 있습니다.)
해당 포스팅과는 관련이 없지만
NHN에 관한 포스팅이 너무 재밌어서 공유해봅니다.
헤헿..
# 목차
1. 연관관계 매핑
1-1) 양방향 연관관계보다 단방향 연관관계가 좋다?
1-2) N+1 문제는 EAGER Fetch 때문에 발생한다?
1-3) findAll() 메서드는 N+1 문제를 발생시키지 않는다?
1-4) Fetch JOIN 으로 N+1 문제 해결시 흔히 하는 실수
2. JPA Repository
2-1) JPA Repository 메서드로는 JOIN 쿼리를 실행할 수 없다?
2-2) Page 와 Slice
2-3) JPA Repository 메서드로는 DTO Projection을 할 수 없다?
# 1-1) 양방향 연관관계보다 단방향 연관관계가 좋다? (항상 그렇지는 않다!)
1:N 관계에서 Cascade 를 통한 Insert를 수행할때는 양방향이 좋다.
# [CASE 1, [N:1 관계]]
* memberDetail을 Insert 시, 총 3개의 쿼리 발생
# [CASE 2, [1:N 관계]] (1)
* 하나의 트랜잭션으로 묶여있기 때문에 3개의 쿼리를 기대하나, 업데이트 쿼리가 발생하여 총 5개 쿼리발생
(FK 지정을 위해 update 쿼리가 발생한다)
* 해결방법 : 일대다(1:N) 양방향 연결관계로 만들어 주어야 함
# [CASE 2, [1:N 관계]] (2) - 양방향 복합키
* 복합키를 외래키로 지정할때는 @JoinColumn 대신 @MapsId 사용 (연관관계 주인설정)
* 연관관계의 주인이 아니면 @JoinColumn 사용하면 안된다.
* 양방향을 걸어주면, 추가적인 Update 쿼리가 생성되지 않고, 기대하는 3개의 쿼리만 실행
# 1-2) N+1 문제는 EAGER Fetch 때문에 발생한다? (아니다!)
Lazy Fetch 를 택하더라도, 연관 Entity를 참조하면 그 순간 추가적인 쿼리가 수행된다.
# 1-3) findAll() 메서드는 N+1 문제를 발생시키지 않는다? (아니다!)
Fetch 전략은 단일레코드에 대해서만 적용이 된다.
findAll() 과 같은 단일레코드 조회가 아닌 경우(=JPQL 수행의 경우)는 Fetch 전략이 적용되지 않는다
허나, 반환된 레코드 하나하나를 선택하여 연관 Entity를 참조하는 순간 추가적인 쿼리가 수행되어 N+1 문제 발생
* Fetch JOIN , @EntityGraph 로 해결
# 1-4) Fetch JOIN 으로 N+1 문제 해결시 흔히 하는 실수
[1] Pagination 과 Fetch JOIN 을 같이 쓰는 경우
limit 쿼리가 수행되는 것이 아니라, limit 없이 모든 레코드를 가져오는 쿼리가 실행된다.
인메모리에 모든레코드를 올려놓고 Pagination이 실행되는 것이다.
(많은 레코드가 메모리에 올라가게 되는 것)
* Pagination 쿼리와 Fetch JOIN 쿼리를 분리하여 사용하여 해결
[2] 하나의 엔티티에서 2개 이상의 컬렉션(연관관계)을 FetchJoin
=> MultipleBagFetchException 발생
Hibernate의 Bag이 수행결과로 만들어지는 카테시안 곱(Cartesian Product)에서
어느 행이 유효한 중복인지 아닌지를 판단할 수 없어서 발생
[Hibernate의 Bag]
Java의 util.List 타입은 Hibernate의 Bag 타입으로 맵핑된다.
Bag 타입은 중복요소를 허용하는 unordered(비순차) 컬렉션이다.
* List를 Set으로 변경 또는 @OrderColumn 사용으로 순서부여로 해결
# 2-1) JPA Repository 메서드로는 JOIN 쿼리를 실행할 수 없다? (할 수 있다!!!)
Underscore("_")를 통해 가능하다
[JPA Repo에서 "_" 의 역할]
* 내부 속성값에 접근
# 2-2) Page 와 Slice (새로 알게된 사실)
* Page 는 집계쿼리인 count 쿼리로 다음 페이지의 유무를 확인
* Slice 는 limit 에 {limit+1} 만큼 가져와 다음레코드가 있는지 확인
* 레코드가 많을때는 집계쿼리도 부담이므로 Slice 를 사용하면 좋다.
* Page 는 지정한 limit 보다 적게 레코드가 반환되면 집계쿼리를 내보내지 않긴 한다.
# 2-3) JPA Repository 메서드로는 DTO Projection을 할 수 없다? (할 수 있다!!!)
위의 4개 쿼리메서드 모두 동일한 쿼리를 생성하기 때문에, 흔히 DTO Projection을 하지 못한다고 알고 있다.
[How ?]
1. Class 기반 Projection- 생성자가 존재하는 DTO를 Repository에서 반환형으로 사용하면 아래와 같은 쿼리로 Projection 된다. (Wow..)* 2.0.x 버전 부터 지원되지 않는 기능이다. (https://github.com/spring-projects/spring-data-jpa/issues/1729)
2. Interface 기반 Projection
* 인터페이스만 생성해주면 JPA가 Proxy로 구현클래스를 만들어 수행해준다.
* 2.6.1 까지는 작동하는 것으로 확인되나, 2.6.3 에서 작동되지 않음을 확인함
* 중첩인터페이스도 지원한다
* @Value(SPEL) 의 사용으로 더 간단하게 매핑가능하다.
3. Dynamic Projection
* 제네릭타입으로 Projection 을 수행한다. (필요한 DTO를 넣기만 하면된다. 역시 네이버의 뿌리는.. 👍)
# Reference
1. NHN Youtube
728x90'Back End > Spring Data JPA' 카테고리의 다른 글
[Spring JPA] Spring Test 에서 Auditing 이 안될때 (@DataJpaTest) (0) 2021.12.24 [Spring JPA] 왜 테스트코드와 실제 어플리케이션코드가 실행이 다르게 될까? (0) 2021.12.24 [Spring JPA] Setter를 사용하지 않는다. (0) 2021.11.30 [Spring JPA] FetchType 이란? (EAGER, LAZY Fetch) (0) 2021.11.02 [Spring JPA] Dirty Check 는 무엇이고, 그로 인한 성능적인 손해는 무엇일까? (0) 2021.11.02