로그인 후 첫 페이지

Yuri Lee·2020년 12월 15일
0

Todo

N+1 Select 문제 없이, 쿼리 6개를 사용해서 만들기

  • 계정 조회 (관심 주제, 지역 정보 포함)
  • 참석할 모임 조회 (내가 참가 신청을 했고 이게 수락이 된, enrollment를 조회해야 한다. )
  • 나의 주요 활동 지역과 관심 주제에 해당하는 스터디 조회 (Tag와 Zone은 &&
    조건.)
  • 관리중인 스터디 조회 (최대 5개까지, 없으면 없다고 보여줌) , 관리자로 등록된 ..
  • 참여중인 스터디 조회 (최대 5개까지), 멤버로 등록된
  • 알림 조회

뷰에 전달해야 할 모델 데이터

  • account: 현재 로그인한 사용자의 정보
  • enrollmentList: 참석 확정된 참가 신청 정보를 통해 ‘참석할 모임' 목록 출력
  • studyList: ‘주요 활동 지역의 관심 주제 스터디' 목록 출력
  • studyManagerOf: ‘관리중인 스터디’ 목록 출력
  • studyMemberOf: ‘참여중인 스터디' 목록 출력

관심 스터디 주제 & 주요 활동 지역

Account accountLoaded = accountRepository.findAccountWithTagsAndZonesById(account.getId()); // 계정을 따로 조회, 이전에는 account를 바로 model에 담았음

accountRepository 를 통해서 쿼리를 했다. Why? tags와 zones에 대한 목록을 출력할 때 지금 account는 detached 상태이다. 따라서 추가적인 정보를 가져오지 못한다. 즉 persistent 인 상태만 가져올 수 있다. 또한 뷰 렌더링 할 때 추가적으로 두번 가져오는 것이 불편하므로 account를 가져오면서 tags와 zones에 대한 data도 함께 fetch 해서 가져오도록 한다.

준영속(detached)

  • 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 영속성 컨텍스트에서 지운 상태

영속(managed)

  • 영속성 컨텍스트에 저장된 상태
  • Entity가 영속성 컨텍스트에 의해 관리되는 상태

참석할 모임

모임 목록은 enrollmentList 에서 가져온다. 수락된 모임을 정렬해서! 모임 목록 뷰를 만들 때 스터디에 대한 정보까지 조회해야 한다. enrollment 에서 study 정보까지 조회한다. find(ex. findByAccountAndAcceptedOrderByEnrolledAtDesc..etc) 류는 enrollment가 아무리 ManyToOne으로 가지고 있다하더라도 다음과 같은 정보를 가져오지 않는다. fetch mode는 entity manager을 통해서 id로 쿼리를 해올 때 적용되는 것이다. 여태 entity graph를 사용한 것도 같은 맥락이다. 따라서 참석할 모임에서 추가적인 쿼리가 많이 발생할 것이다.

        <div class="col-md-7">
            <h5 th:if="${#lists.isEmpty(enrollmentList)}" class="font-weight-light">참석할 모임이 없습니다.</h5>
            <h5 th:if="${!#lists.isEmpty(enrollmentList)}" class="font-weight-light">참석할 모임</h5>
            <div class="row row-cols-1 row-cols-md-2" th:if="${!#lists.isEmpty(enrollmentList)}">
                <div class="col mb-4" th:each="enrollment: ${enrollmentList}">
                    <div class="card">
                        <div class="card-body">
                            <h5 class="card-title" th:text="${enrollment.event.title}">Event title</h5>
                            <h6 class="card-subtitle mb-2 text-muted" th:text="${enrollment.event.study.title}">Study title</h6>
                            <p class="card-text">
                                <span>
                                    <i class="fa fa-calendar-o"></i>
                                    <span class="calendar" th:text="${enrollment.event.startDateTime}">Last updated 3 mins ago</span>
                                </span>
                            </p>
                            <a th:href="@{'/study/' + ${enrollment.event.study.path} + '/events/' + ${enrollment.event.id}}" class="card-link">모임 조회</a>
                            <a th:href="@{'/study/' + ${enrollment.event.study.path}}" class="card-link">스터디 조회</a>
                        </div>
                    </div>
                </div>
            </div>
  • enrollment.event.title: enrollment의 event를 참조해야 한다.
  • enrollment.event.study.title: enrollment의 study 정보까지 참조해야 한다.

enrollment가 가지고 있는 event도 조회해야 하고 study도 조회해야 한다. enrollment의 event를 가져오는 건 쉽다. entity graph를 주면 된다. 참고로 enrollment의 event의 study 까지 가져오는 것은 다뤄본 적이 없다.

Enrollment.java

@NamedEntityGraph(
        name = "Enrollment.withEventAndStudy",
        attributeNodes = {
                @NamedAttributeNode(value = "event", subgraph = "study")
        },
        subgraphs = @NamedSubgraph(name = "study", attributeNodes = @NamedAttributeNode("study"))
)

방법: sub query로 study를 가져오도록 등록한다. (subgraphs 를 사용) 직접적인 연관관계를 가지고 있는 event, 그 event가 가지고 있는 study까지 같이 가져올 수 있다.

    select
        enrollment0_.id as id1_3_0_,
        event1_.id as id1_4_1_,
        study2_.id as id1_7_2_,
        enrollment0_.accepted as accepted2_3_0_,
        enrollment0_.account_id as account_5_3_0_,
        enrollment0_.attended as attended3_3_0_,
        enrollment0_.enrolled_at as enrolled4_3_0_,
        enrollment0_.event_id as event_id6_3_0_,
        event1_.created_by_id as created10_4_1_,
        event1_.created_date_time as created_2_4_1_,
        event1_.description as descript3_4_1_,
        event1_.end_date_time as end_date4_4_1_,
        event1_.end_enrollment_date_time as end_enro5_4_1_,
        event1_.event_type as event_ty6_4_1_,
        event1_.limit_of_enrollments as limit_of7_4_1_,
        event1_.start_date_time as start_da8_4_1_,
        event1_.study_id as study_i11_4_1_,
        event1_.title as title9_4_1_,
        study2_.closed as closed2_7_2_,
        study2_.closed_date_time as closed_d3_7_2_,
        study2_.full_description as full_des4_7_2_,
        study2_.image as image5_7_2_,
        study2_.member_count as member_c6_7_2_,
        study2_.path as path7_7_2_,
        study2_.published as publishe8_7_2_,
        study2_.published_date_time as publishe9_7_2_,
        study2_.recruiting as recruit10_7_2_,
        study2_.recruiting_updated_date_time as recruit11_7_2_,
        study2_.short_description as short_d12_7_2_,
        study2_.title as title13_7_2_,
        study2_.use_banner as use_ban14_7_2_ 
    from
        enrollment enrollment0_ 
    left outer join
        event event1_ 
            on enrollment0_.event_id=event1_.id 
    left outer join
        study study2_ 
            on event1_.study_id=study2_.id 
    where
        enrollment0_.account_id=? 
        and enrollment0_.accepted=? 
    order by
        enrollment0_.enrolled_at desc

enrollment를 조회할 때 event와 study까지 다 조회하는 query를 볼 수 있다.

주요 활동 지역의 관심 주제 스터디

페이징은 필요 없고, 쿼리는 1개만 발생한다.

StudyRepositoryExtensionImpl.java

    @Override
    public List<Study> findByAccount(Set<Tag> tags, Set<Zone> zones) {
        QStudy study = QStudy.study;
        JPQLQuery<Study> query = from(study).where(study.published.isTrue() //study가 공개 되어있고
                .and(study.closed.isFalse()) // 닫히지 않았고
                .and(study.tags.any().in(tags))
                .and(study.zones.any().in(zones)))
                .leftJoin(study.tags, QTag.tag).fetchJoin()
                .leftJoin(study.zones, QZone.zone).fetchJoin()
                .orderBy(study.publishedDateTime.desc())
                .distinct()
                .limit(9);
        return query.fetch();
    }

study의 tag가 account가 가지고 있는 tags 중에 아무거나 매칭이 되어야 하고, zones 도 같은 방식으로! study의 tag와 zone에 대한 정보를 출력하고 있기 때문에 fetchJoin()으로 가져온다. leftJoin 할때는 중복데이터가 생길 수 있으므로 distinct를 추가해준다. 그리고 orderBy를 사용하여 최근에 공개된 스터디 순으로 정렬하고, 최대 9개만 정렬할 수 있도록 한다.

관리중인 스터디 & 참여중인 스터디

StudyRepository.java

    @EntityGraph(attributePaths = {"zones", "tags"})
    List<Study> findFirst9ByPublishedAndClosedOrderByPublishedDateTimeDesc(boolean published, boolean closed);

    List<Study> findFirst5ByManagersContainingAndClosedOrderByPublishedDateTimeDesc(Account account, boolean closed);

    List<Study> findFirst5ByMembersContainingAndClosedOrderByPublishedDateTimeDesc(Account account, boolean closed);

@EntityGraph(attributePaths = {"zones", "tags"}) 를 추가할 필요는 없다. 그냥 스터디 이름과 링크만 표현하기 때문에 스터디가 갖고 있는 기본 정보만으로도 표현할 수 있다. 그래서 별다른 연관관계 없이 조회한다.

select
        study0_.id as id1_7_,
        study0_.closed as closed2_7_,
        study0_.closed_date_time as closed_d3_7_,
        study0_.full_description as full_des4_7_,
        study0_.image as image5_7_,
        study0_.member_count as member_c6_7_,
        study0_.path as path7_7_,
        study0_.published as publishe8_7_,
        study0_.published_date_time as publishe9_7_,
        study0_.recruiting as recruit10_7_,
        study0_.recruiting_updated_date_time as recruit11_7_,
        study0_.short_description as short_d12_7_,
        study0_.title as title13_7_,
        study0_.use_banner as use_ban14_7_ 
    from
        study study0_ 
    where
        (
            ? in (
                select
                    members1_.members_id 
                from
                    study_members members1_ 
                where
                    study0_.id=members1_.study_id
            )
        ) 
        and study0_.closed=? 
    order by
        study0_.published_date_time desc limit ?

출처 : 인프런 백기선님의 스프링과 JPA 기반 웹 애플리케이션 개발
https://gmlwjd9405.github.io/2019/08/08/jpa-entity-lifecycle.html

profile
Step by step goes a long way ✨

0개의 댓글