인프런 커뮤니티 질문&답변

taehee-kim-dev님의 프로필 이미지
taehee-kim-dev

작성한 질문수

스프링과 JPA 기반 웹 애플리케이션 개발

모임 목록 조회

@NamedEntityGraph 와 Fetch

작성

·

209

0

안녕하세요, 강의 잘 보고 있습니다.

@NamedEntityGraph로 N+1 select를 해결하는것은 잘 되는데요, 문득 궁금증이 생겨 @NamedEntityGraph를 제거하고 그냥 해당 필드에 EAGER를 붙여보았습니다.

Notification entity를 조회할 때 참조하고있는 Tag entity를 같이 가져오는 것을 구현연습 하고 있었습니다.

Notification.java

@Entity
@Getter
@Setter
@EqualsAndHashCode(of = "id")
@NoArgsConstructor
public class Notification {

    @Id @GeneratedValue
    private Long id;

    @Enumerated(EnumType.STRING)
    private NotificationType notificationType;

    private String title;

    private String link;

    private boolean ringBellChecked = false;

    private boolean linkVisited = false;

    @ManyToOne
    private Account account;

    @ManyToMany(fetch = FetchType.EAGER)
    private List<Tag> commonTag = new LinkedList<>();

    private LocalDateTime createdDateTime;
}

NotificationRepository.java

public interface NotificationRepository extends JpaRepository<Notification, Long> {
    List<Notification> findByAccount(Account sessionAccount);
}

그리고 이를 활용하여

NotificationService.java

@Transactional
@RequiredArgsConstructor
@Service
public class NotificationService {

    private final NotificationRepository notificationRepository;

    public List<Notification> ringBellCheck(Account sessionAccount) {
        List<Notification> allNotification = notificationRepository.findByAccount(sessionAccount);
        allNotification.forEach(notification -> {
            notification.setRingBellChecked(true);
        });
        return allNotification;
    }
}

위와같은 작업을 처리합니다.

근데 현재 Notification이 3개가 존재할 때,

2020-07-28 10:14:57.791 DEBUG 15072 --- [nio-8080-exec-9] org.hibernate.SQL                        : 
    select
        notificati0_.id as id1_2_,
        notificati0_.account_id as account_8_2_,
        notificati0_.created_date_time as created_2_2_,
        notificati0_.link as link3_2_,
        notificati0_.link_visited as link_vis4_2_,
        notificati0_.notification_type as notifica5_2_,
        notificati0_.ring_bell_checked as ring_bel6_2_,
        notificati0_.title as title7_2_ 
    from
        notification notificati0_ 
    where
        notificati0_.account_id=?
2020-07-28 10:14:57.792 TRACE 15072 --- [nio-8080-exec-9] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [1]
2020-07-28 10:14:57.840 DEBUG 15072 --- [nio-8080-exec-9] org.hibernate.SQL                        : 
    select
        account0_.id as id1_0_0_,
        account0_.bio as bio2_0_0_,
        account0_.count_of_sending_email_verification_email as count_of3_0_0_,
        account0_.email_first_verified as email_fi4_0_0_,
        account0_.email_verification_token as email_ve5_0_0_,
        account0_.email_verified as email_ve6_0_0_,
        account0_.email_waiting_to_be_verified as email_wa7_0_0_,
        account0_.first_count_of_sending_email_verification_email_set_date_time as first_co8_0_0_,
        account0_.location as location9_0_0_,
        account0_.nickname as nicknam10_0_0_,
        account0_.nickname_before_update as nicknam11_0_0_,
        account0_.notification_comment_on_my_comment_by_email as notific12_0_0_,
        account0_.notification_comment_on_my_comment_by_web as notific13_0_0_,
        account0_.notification_comment_on_my_post_by_email as notific14_0_0_,
        account0_.notification_comment_on_my_post_by_web as notific15_0_0_,
        account0_.notification_like_on_my_comment_by_email as notific16_0_0_,
        account0_.notification_like_on_my_comment_by_web as notific17_0_0_,
        account0_.notification_like_on_my_post_by_email as notific18_0_0_,
        account0_.notification_like_on_my_post_by_web as notific19_0_0_,
        account0_.notification_my_interest_tag_added_to_existing_post_by_email as notific20_0_0_,
        account0_.notification_my_interest_tag_added_to_existing_post_by_web as notific21_0_0_,
        account0_.notification_new_post_with_my_interest_tag_by_email as notific22_0_0_,
        account0_.notification_new_post_with_my_interest_tag_by_web as notific23_0_0_,
        account0_.occupation as occupat24_0_0_,
        account0_.password as passwor25_0_0_,
        account0_.profile_image as profile26_0_0_,
        account0_.show_password_update_page_token as show_pa27_0_0_,
        account0_.sign_up_date_time as sign_up28_0_0_,
        account0_.user_id as user_id29_0_0_,
        account0_.verified_email as verifie30_0_0_ 
    from
        account account0_ 
    where
        account0_.id=?
2020-07-28 10:14:57.840 TRACE 15072 --- [nio-8080-exec-9] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [1]
2020-07-28 10:14:57.888 DEBUG 15072 --- [nio-8080-exec-9] org.hibernate.SQL                        : 
    select
        commontag0_.notification_id as notifica1_3_0_,
        commontag0_.common_tag_id as common_t2_3_0_,
        tag1_.id as id1_8_1_,
        tag1_.title as title2_8_1_ 
    from
        notification_common_tag commontag0_ 
    inner join
        tag tag1_ 
            on commontag0_.common_tag_id=tag1_.id 
    where
        commontag0_.notification_id=?
2020-07-28 10:14:57.889 TRACE 15072 --- [nio-8080-exec-9] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [15]
2020-07-28 10:14:57.937 DEBUG 15072 --- [nio-8080-exec-9] org.hibernate.SQL                        : 
    select
        commontag0_.notification_id as notifica1_3_0_,
        commontag0_.common_tag_id as common_t2_3_0_,
        tag1_.id as id1_8_1_,
        tag1_.title as title2_8_1_ 
    from
        notification_common_tag commontag0_ 
    inner join
        tag tag1_ 
            on commontag0_.common_tag_id=tag1_.id 
    where
        commontag0_.notification_id=?
2020-07-28 10:14:57.937 TRACE 15072 --- [nio-8080-exec-9] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [13]
2020-07-28 10:14:57.984 DEBUG 15072 --- [nio-8080-exec-9] org.hibernate.SQL                        : 
    select
        commontag0_.notification_id as notifica1_3_0_,
        commontag0_.common_tag_id as common_t2_3_0_,
        tag1_.id as id1_8_1_,
        tag1_.title as title2_8_1_ 
    from
        notification_common_tag commontag0_ 
    inner join
        tag tag1_ 
            on commontag0_.common_tag_id=tag1_.id 
    where
        commontag0_.notification_id=?
2020-07-28 10:14:57.984 TRACE 15072 --- [nio-8080-exec-9] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [11]
2020-07-28 10:14:58.137 DEBUG 15072 --- [nio-8080-exec-9] org.hibernate.SQL                        : 
    select
        count(notificati0_.id) as col_0_0_ 
    from
        notification notificati0_ 
    where
        notificati0_.account_id=? 
        and notificati0_.ring_bell_checked=?
2020-07-28 10:14:58.137 TRACE 15072 --- [nio-8080-exec-9] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [1]
2020-07-28 10:14:58.137 TRACE 15072 --- [nio-8080-exec-9] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BOOLEAN] - [false]

이처럼 필드에 EAGER를 붙인것은 N+1 select 문제가 해결되진 않네요.

하나의 Notification만 Id로 조회했을 때는

Tag도 같이 가져오는걸 확인했습니다.

하지만 List<Notification>에서는 적용이 안되네요.

필드에 EAGER를 붙이는 것은 하나만 조회할 때 유효한 건가요?

그리고 NotificationInterceptor.java에서 SecurityContextHolder에서 세션객체 꺼내서

해당 세션객체로 Notification를 조회할 때는 왜 Account를 조회하는 쿼리는 안날아가나요?

detached상태인데..

NotificationService에서 Account로 Notification을 조회했을 때는 새로 Account를 다시 조회하는 쿼리가 날아가는데..

답변 5

1

백기선님의 프로필 이미지
백기선
지식공유자

Notification이 존재하지 않을때는 어차피 조회할 Notification이 없으니 Account 자체를 조회하지 않는 것 같고..

그런데 쿼리를 보면 Notification에 대한 select쿼리를 먼저 하고 그 다음에 Account를 조회하는 쿼리를 날리는데,

Notification에 대한 select쿼리를 일단 날리고 보니 Account객체가 필요해서 Account객체를 조회하는 select쿼리를 또 날리는 건가요?

=> 네 맞습니다. Notifiation이 가지고 있는 Account 정보를 참조하려는 순간에 해당 쿼리가 발생할겁니다. Lazy Loading으로요.


count조회는 Account조회를 하지 않는데 Notification 자체 조회는 왜 Account조회를 하는지 궁금합니다.

=> count 쿼리는 Account를 추이적으로 가져올 수가 없죠. 가져오는 타입 자체가 Notification 같은 엔티티가 아니라 long 타입으로 개수를 가져온건데 무슨 연관관계를 타고 Account를 가져올 수 있을까요.


1

백기선님의 프로필 이미지
백기선
지식공유자

좋은 질문입니다. 안타깝지만 지금 하시려는 그 설정은 EntityManager.find를 사용해서 조회할 때만 사용이 되는 옵션이고 JPQL이나 Criteria API를 사용해서 조회할 때 적용되지 않습니다. 스프링 데이터 JPA가 기본으로 제공하는 조회 기능 중에 일부 (findById() 또는  delete())등은 em.find()를 사용하지만 그밖에 대부분 자동으로 만들어 주는 쿼리는 Criteria를 사용하기 때문에 해당 옵션이 적용되지 않습니다. 그래서 이 수업에서 @ManyToMany에 설정하는 방법 대신 엔티티 그래프를 사용했습니다. 

0

아, 제가 했던것은 Notification 자체를 조회하는 것 이었습니다.

Notification.java

@Entity
@Getter
@Setter
@EqualsAndHashCode(of = "id")
@NoArgsConstructor
public class Notification {

    @Id @GeneratedValue
    private Long id;

    @Enumerated(EnumType.STRING)
    private NotificationType notificationType;

    private String title;

    private String link;

    private boolean ringBellChecked = false;

    private boolean linkVisited = false;

    @ManyToOne
    private Account account;

    @ManyToMany
    private List<Tag> commonTag = new LinkedList<>();

    private LocalDateTime createdDateTime;
}

NotificationController.java

@RequiredArgsConstructor
@Controller
public class NotificationController {

    private final NotificationService notificationService;

    @GetMapping(ALL_NOTIFICATION_LIST_URL)
    public String showALLNotificationList(@SessionAccount Account sessionAccount, Model model){
        model.addAttribute(SESSION_ACCOUNT, sessionAccount);
        List<Notification> allNotification = notificationService.ringBellCheck(sessionAccount);
        model.addAttribute("allNotification", allNotification);
        return ALL_NOTIFICATION_LIST_VIEW_NAME;
    }
}

여기서 @SessionAccount는 강의에서의 @CurrentUser와 이름만 다를 뿐, 나머지는 같습니다.

NotificationService.java

@Transactional
@RequiredArgsConstructor
@Service
public class NotificationService {

    private final NotificationRepository notificationRepository;

    public List<Notification> ringBellCheck(Account sessionAccount) {
        List<Notification> allNotification = notificationRepository.findByAccountOrderByCreatedDateTimeDesc(sessionAccount);
        allNotification.forEach(notification -> notification.setRingBellChecked(true));
        return allNotification;
    }
}

N+1 select 문제 해결 적용 안한 상태입니다.

디버깅 해봤을 때, 

List<Notification> allNotification = notificationRepository.findByAccountOrderByCreatedDateTimeDesc(sessionAccount);

에서 

2020-07-29 15:49:42.664 DEBUG 8952 --- [io-8080-exec-10] org.hibernate.SQL                        : 
    select
        notificati0_.id as id1_2_,
        notificati0_.account_id as account_8_2_,
        notificati0_.created_date_time as created_2_2_,
        notificati0_.link as link3_2_,
        notificati0_.link_visited as link_vis4_2_,
        notificati0_.notification_type as notifica5_2_,
        notificati0_.ring_bell_checked as ring_bel6_2_,
        notificati0_.title as title7_2_ 
    from
        notification notificati0_ 
    where
        notificati0_.account_id=? 
    order by
        notificati0_.created_date_time desc
2020-07-29 15:49:42.665 TRACE 8952 --- [io-8080-exec-10] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [1]
2020-07-29 15:49:42.703 DEBUG 8952 --- [io-8080-exec-10] org.hibernate.SQL                        : 
    select
        account0_.id as id1_0_0_,
        account0_.bio as bio2_0_0_,
        account0_.count_of_sending_email_verification_email as count_of3_0_0_,
        account0_.email_first_verified as email_fi4_0_0_,
        account0_.email_verification_token as email_ve5_0_0_,
        account0_.email_verified as email_ve6_0_0_,
        account0_.email_waiting_to_be_verified as email_wa7_0_0_,
        account0_.first_count_of_sending_email_verification_email_set_date_time as first_co8_0_0_,
        account0_.location as location9_0_0_,
        account0_.nickname as nicknam10_0_0_,
        account0_.nickname_before_update as nicknam11_0_0_,
        account0_.notification_comment_on_my_comment_by_email as notific12_0_0_,
        account0_.notification_comment_on_my_comment_by_web as notific13_0_0_,
        account0_.notification_comment_on_my_post_by_email as notific14_0_0_,
        account0_.notification_comment_on_my_post_by_web as notific15_0_0_,
        account0_.notification_like_on_my_comment_by_email as notific16_0_0_,
        account0_.notification_like_on_my_comment_by_web as notific17_0_0_,
        account0_.notification_like_on_my_post_by_email as notific18_0_0_,
        account0_.notification_like_on_my_post_by_web as notific19_0_0_,
        account0_.notification_my_interest_tag_added_to_existing_post_by_email as notific20_0_0_,
        account0_.notification_my_interest_tag_added_to_existing_post_by_web as notific21_0_0_,
        account0_.notification_new_post_with_my_interest_tag_by_email as notific22_0_0_,
        account0_.notification_new_post_with_my_interest_tag_by_web as notific23_0_0_,
        account0_.occupation as occupat24_0_0_,
        account0_.password as passwor25_0_0_,
        account0_.profile_image as profile26_0_0_,
        account0_.show_password_update_page_token as show_pa27_0_0_,
        account0_.sign_up_date_time as sign_up28_0_0_,
        account0_.user_id as user_id29_0_0_,
        account0_.verified_email as verifie30_0_0_ 
    from
        account account0_ 
    where
        account0_.id=?
2020-07-29 15:49:42.704 TRACE 8952 --- [io-8080-exec-10] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [1]

이와 같이 Account를 조회하는 쿼리도 같이  발생됩니다.

그런데 이는 Notification이 DB에 1개이상 존재할 때 이고,

존재하지 않을때는

2020-07-29 15:52:09.195 DEBUG 4604 --- [nio-8080-exec-7] org.hibernate.SQL                        : 
    select
        notificati0_.id as id1_2_,
        notificati0_.account_id as account_8_2_,
        notificati0_.created_date_time as created_2_2_,
        notificati0_.link as link3_2_,
        notificati0_.link_visited as link_vis4_2_,
        notificati0_.notification_type as notifica5_2_,
        notificati0_.ring_bell_checked as ring_bel6_2_,
        notificati0_.title as title7_2_ 
    from
        notification notificati0_ 
    where
        notificati0_.account_id=? 
    order by
        notificati0_.created_date_time desc
2020-07-29 15:52:09.196 TRACE 4604 --- [nio-8080-exec-7] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [9]

이와같이 Account를 조회하는 쿼리는 발생하지 않았습니다.

Notification count를 조회할 때는 말씀하신 것처럼 Account를 조회할 필요가 없지만,

Notification 자체를 조회할 때는 참조하고있는 Account도 조회해야 하는건가요?

Notification이 존재하지 않을때는 어차피 조회할 Notification이 없으니 Account 자체를 조회하지 않는 것 같고..

그런데 쿼리를 보면 Notification에 대한 select쿼리를 먼저 하고 그 다음에 Account를 조회하는 쿼리를 날리는데,

Notification에 대한 select쿼리를 일단 날리고 보니 Account객체가 필요해서 Account객체를 조회하는 select쿼리를 또 날리는 건가요?

count조회는 Account조회를 하지 않는데 Notification 자체 조회는 왜 Account조회를 하는지 궁금합니다.

Notification 관련 디렉토리 Github 주소입니다. https://github.com/taehee-kim-dev/portfolio2/tree/master/src/main/java/portfolio2/module/notification

0

백기선님의 프로필 이미지
백기선
지식공유자

글쎄요. 조회하는게 Notification도 아니고 Notification의 개수(count)인데 Account를 왜 쿼리해야 하는지 모르겠네요. 참고로 메소드에 전달한 Account는 FK를 참조하려고 넘겨준 객체로 이미 로그인할 때 조회해온 데이터입니다. 

직접 경험하신 코드를 만들어 공유해 주시면 살펴보겠습니다.

0

감사합니다!

그리고 NotificationInterceptor.java에서 SecurityContextHolder에서 세션객체를 꺼내서

해당 세션객체로 Notification를 조회할 때는 왜 Account를 조회하는 쿼리는 안날아가나요?

SecurityContextHolder에 있는 session 객체는 detached 상태이고, NotificationRepository에서

long countByAccountAndChecked(Account account, boolean checked);

로 조회를 할 때,

Account를 가져오는 쿼리가 날아가야하지 않나요??

제가 Service단에서 비슷한 형식의 조회를 했을 때는 Account객체를 찾는 쿼리문도 같이 날아갔거든요..

taehee-kim-dev님의 프로필 이미지
taehee-kim-dev

작성한 질문수

질문하기