[REFACTOR] 지도 API 쿼리 최적화#129
Conversation
Test Results42 tests 42 ✅ 1m 1s ⏱️ Results for commit 3f849cf. ♻️ This comment has been updated with latest results. |
| @Query( | ||
| value = "SELECT m.* FROM menu m " + | ||
| "JOIN store s ON m.store_id = s.id " + | ||
| "JOIN map ON s.map_id = map.id " + | ||
| "WHERE m.user_id = :userId " + | ||
| "AND LOWER(m.title) LIKE CONCAT('%', LOWER(:title), '%') " + | ||
| "ORDER BY ST_Distance_Sphere(map.location, :userLocation) ASC", | ||
| countQuery = "SELECT COUNT(*) FROM menu m " + | ||
| "JOIN store s ON m.store_id = s.id " + | ||
| "JOIN map ON s.map_id = map.id " + | ||
| "WHERE m.user_id = :userId " + | ||
| "AND LOWER(m.title) LIKE CONCAT('%', LOWER(:title), '%')", | ||
| nativeQuery = true | ||
| ) | ||
| Page<Menu> findByUserIdTitleContainingOrderByDistance(@Param("userId") Long userId, |
There was a problem hiding this comment.
네이티브 쿼리를 사용하면 N+1 쿼리를 해결할 수 있지 않나요?
해당 부분이 N+1 문제가 발생하는 지점이 맞을까요?
There was a problem hiding this comment.
말씀해주신대로 네이티브 쿼리를 사용해도 추가적인 쿼리(N+1문제)가 발생하지 않는 것을 확인했습니다.
궁금한 점이 있는데, 위 쿼리에서 Menu의 컬럼만을 조회하고자 하였는데 Store와 Map의 컬럼까지 모두 가져오는 것이 어째서 그런 것인지 잘 모르겠어서 간략하게 알려주실 수 있을까요?? JOIN의 결과 자체를 반환해주는 건가요??
제가 생각한 바로는 위 쿼리를 통해 Menu의 데이터를 조회하고, Store와 Map의 경우 LAZY 설정이 되어있어 get과 같은 호출로 인해 추가적인 쿼리가 발생해야 된다고 생각했는데 하나의 쿼리로 모든 엔티티의 컬럼이 모두 조회되는 이유를 잘 모르겠습니다.
추가적으로 JPQL을 사용하는 경우 SQL 함수를 사용하는 것에 제약이 있어 네이티브 쿼리를 사용하는게 맞는 것 같아 수정하겠습니다
There was a problem hiding this comment.
저도 해당 부분에 대해서 추상적으로만 이해하고 있어서 관련해서 찾아본 내용을 공유해보겠습니다(틀린 내용이 있을 수 있어요~~)
결론적으로 위 쿼리에 Menu컬럼 만을 조회해도 Store, Map 엔티티를 모두 조회할 수 있는 내용은 nativeQuery사용하게 되면 해당 쿼리를 직접 JDBC API를 호출하기 때문에 연관관계 로딩 전략의 영향에서 벗어나게 됩니다.
그러면 위 쿼리를 패치 조인으로 바꾸게 되면 N+1을 방지할 수 있을까요?
방지할 수 있습니다! 저도 패치 조인에서 오인하고 있던 부분이 2개의 이상의 패치조인을 사용하게 되면 전부 적용되지 않고 일부분은 Lazy 전략을 사용하게된다고 이해하고 있었습니다.
그런데 이말이 틀리면서 맞는 오해의 소지가 있습니다. 정정하면 둘 이상의 컬렉션을 패지 할 수 있다라는 표현이 정확합니다.
public class Map extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "map")
private List<Store> stores;
@OneToMany(mappedBy = "user")
private List<User> users;
}만약에 Map 엔티티에 Users라는 컬랙션 일다대 연관관계가 추가되었고, Map엔티티와 Store,User를 모두 조회하고 싶어서 Fetch Join을 사용할 경우에는 문제가 됩니다.(조인 결과 컬럼Set DB관점에서 생각해보면 이유를 예상해볼 수 있습니다)
다시 돌아와서 findByUserIdTitleContainingOrderByDistance 를 보겠습니다. menu-store-map 순으로 조인하고 있고, menu-store는 n:1이고 stroe-map은 n:1 이기 때문에 둘이상의 컬렉션을 패치하는 상황이 아니기 때문에 Fetch Join을 사용하든 NativeQuery를 사용하던 문제가 되지 않습니다
There was a problem hiding this comment.
미리 설정해둔 연관관계 전략 자체가 무시되는군요... 직접 쿼리마다 실행 결과도 확인하고 레퍼런스를 찾아봐도 잘 안나와서 질문 드렸는데 상세한 설명 정말 감사드립니다 👍
위에 제가 코멘트 단 것과 같이 SQL 내장 함수를 사용하기 때문에 네이티브 쿼리를 작성하는 방식으로 수정했습니다!
✏️ 작업 개요
⛳ 작업 분류
🔨 작업 상세 내용
💡 생각해볼 문제