Account accountLoaded = accountRepository.findAccountWithTagsAndZonesById(account.getId()); // 계정을 따로 조회, 이전에는 account를 바로 model에 담았음
accountRepository 를 통해서 쿼리를 했다. Why? tags와 zones에 대한 목록을 출력할 때 지금 account는 detached 상태이다. 따라서 추가적인 정보를 가져오지 못한다. 즉 persistent 인 상태만 가져올 수 있다. 또한 뷰 렌더링 할 때 추가적으로 두번 가져오는 것이 불편하므로 account를 가져오면서 tags와 zones에 대한 data도 함께 fetch 해서 가져오도록 한다.
준영속(detached)
영속(managed)
모임 목록은 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도 조회해야 하고 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