작성
·
42
0
게시글 프로젝트를 하다가 컬렉션 조회 관련 영속성 컨텍스트가 이해가 가지 않는 부분이 있어서 질문을 드리게 되었습니다.
getRecommedPosts2메서드에서
findAllByMemePostId로 태그 정보를 미리 가져오면 영속성 컨텍스트에 MemePostTag와 Tag가 같이 불려와지니까
이후에 memePost.getTagNames()를 했을때 따로 쿼리가 나가지 않을 거라고 생각했는데, 별도로 또 쿼리가 나가는 것을 확인했습니다.
그래서 getRecommedPosts3메서드에서 findByIdWithTags와 같이 MemePostTag와 Tag를 같이 fetch join하면 따로 쿼리가 나가지 않더라구요
혹시 이유가 무엇인가요? 제가 어떤 개념을 잘 모르는 것 같은데 뭘 모르는 것인지 모르겠어요. 저는 getTagNames()를 하면 memePostTags 컬렉션이 필요할때 id로 영속성 컨텍스트에 있는지 조회한다고 생각해서 필요한 엔티티들을 미리 불러와 놓으면 따로 쿼리가 나가지 않을 것이라고 생각했습니다.
public List<MemePostSummaryResponse> getRecommendedPosts2(Long memePostId, int size) {
MemePost memePost = getMemePostById(memePostId);
//fetch join으로 memePost의 태그 정보 미리 가져오기
memePostTagRepository.findAllByMemePostId(memePost.getId());
//tag 이름으로 관련있는 포스트 찾기
List<MemePost> recommendedPosts = memePostRepository.findRelatedPostsByTagNames(memePost.getTagNames(), memePostId, PageRequest.of(0, size));
memePostTagRepository.findTagsByMemePostIdIn(getPostIds(recommendedPosts));
return recommendedPosts.stream()
.map(mp -> new MemePostSummaryResponse(mp, false, mp.getTagNames()))
.toList();
}
public interface MemePostTagRepository extends JpaRepository<MemePostTag, Long> {
@EntityGraph(attributePaths = {"tag"})
List<MemePostTag> findAllByMemePostId(Long postId);
}
public class MemePost {
public List<String> getTagNames() {
return memePostTags.stream()
.map(mpt -> mpt.getTag().getName())
.toList();
}
}
public List<MemePostSummaryResponse> getRecommendedPosts3(Long memePostId, int size) {
MemePost memePost = memePostRepository.findByIdWithTags(memePostId)
.orElseThrow(() -> new FindMyMemeException(ErrorCode.NOT_FOUND_MEME_POST));
//fetch join으로 memePost의 태그 정보 미리 가져오기
//tag 이름으로 관련있는 포스트 찾기
List<MemePost> recommendedPosts = memePostRepository.findRelatedPostsByTagNames(memePost.getTagNames(), memePostId, PageRequest.of(0, size));
memePostRepository.findAllWithTagsInPostIds(getPostIds(recommendedPosts));
return recommendedPosts.stream()
.map(mp -> new MemePostSummaryResponse(mp, false, mp.getTagNames()))
.toList();
}
@Query("select distinct m from MemePost m " +
"left join fetch m.memePostTags mpt " +
"left join fetch mpt.tag t " +
"where m.id = :id and m.deletedAt is null")
Optional<MemePost> findByIdWithTags(@Param("id") Long id);
답변 1
0
안녕하세요, 인프런 AI 인턴입니다.
안채연님, 질문해주신 영속성 컨텍스트 관련 문제는 JPA에서의 엔티티 관리와 페치 전략이 복합적으로 작용하는 과정에서 발생할 수 있는 일입니다.
영속성 컨텍스트와 fetch join: getRecommendedPosts3
메서드처럼 fetch join
을 사용하는 경우, 연관된 엔티티들이 한 번의 쿼리를 통해 영속성 컨텍스트에 저장됩니다. 따라서 이미 영속성 컨텍스트에 로딩된 엔티티에 대해서는 추가적인 쿼리가 나가지 않습니다.
EntityGraph 및 findAllByMemePostId
메서드: getRecommendedPosts2
메서드에서는 @EntityGraph
와 함께 findAllByMemePostId
메서드를 사용하지만 이것은 실제 fetch join
쿼리와는 다르게 JPA에서 일반적으로 이력되지 않은 결합을 가져오는 것을 의미할 수 있습니다. EntityGraph
는 JPQL
의 fetch join
처럼 한 번의 쿼리로 여러 엔티티를 가져오는 것이 아니라, Lazy Loading
될 수 있는 환경을 제공합니다. 즉, memePost.getTagNames()
호출 시점에 태그가 로드되지 않은 상태라면 추가 쿼리가 발생할 수 있습니다.
Lazy Loading: MemePost
의 memePostTags
나 Tag
엔티티가 lazy
로딩 방식으로 초기화된다면 필요한 시점에만 로딩될 수 있습니다. 이는 findByIdWithTags
처럼 fetch join
을 통해 eager
로 로딩하지 않는 한, 기본적으로 lazy
로 동작할 수 있습니다.
따라서, 문제를 해결하기 위해서는 fetch join
을 이용하여 한 번의 쿼리로 데이터를 확보하거나, EntityGraph
사용 시 특정 설정을 확인하여 lazy
로딩이 일어나는 시점과 데이터를 참조하는 시점을 맞추어야 합니다.
추가적인 링크와 코드 예제를 참조해 주세요:
- Spring Data JPA - Reference Documentation
질문 남겨주셔서 감사합니다. 더 궁금한 점이 있다면 언제든지 문의하세요!