- N+1문제 해결
모임 목록 요청을 처리하는 핸들러 작성
package me.weekbelt.studyolle.event;
@Controller
@RequestMapping("/study/{path}")
@RequiredArgsConstructor
public class EventController {
// ......
private final EventRepository eventRepository;
// ......
@GetMapping("/events")
public String viewStudyEvents(@CurrentAccount Account account, @PathVariable String path, Model model) {
Study study = studyService.getStudy(path);
model.addAttribute("account", account);
model.addAttribute("study", study);
List<Event> events = eventRepository.findByStudyOrderByStartDateTime(study);
List<Event> newEvents = new ArrayList<>();
List<Event> oldEvents = new ArrayList<>();
events.forEach(e -> {
if (e.getEndDateTime().isBefore(LocalDateTime.now())) {
oldEvents.add(e);
} else {
newEvents.add(e);
}
});
model.addAttribute("newEvents", newEvents);
model.addAttribute("oldEvents", oldEvents);
return "study/events";
}
}
EventRepository에 findByStudyOrderByStartDateTime() 메소드 추가
package me.weekbelt.studyolle.event;
// ......
@Transactional(readOnly = true)
public interface EventRepository extends JpaRepository<Event, Long> {
List<Event> findByStudyOrderByStartDateTime(Study study);
}
모임 목록을 보여주는 study/events.html 작성
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments.html :: head"></head>
<body>
<nav th:replace="fragments.html :: main-nav"></nav>
<div th:replace="fragments.html :: study-banner"></div>
<div class="container">
<div th:replace="fragments.html :: study-info"></div>
<div th:replace="fragments.html :: study-menu(studyMenu='events')"></div>
<div class="row my-3 mx-3 justify-content-center">
<div class="col-10 px-0 row">
<div class="col-2 px-0">
<ul class="list-group">
<a href="#" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
새 모임
<span th:text="${newEvents.size()}">2</span>
</a>
<a href="#" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
지난 모임
<span th:text="${oldEvents.size()}">5</span>
</a>
</ul>
</div>
<div class="col-10 row row-cols-1 row-cols-md-2">
<div th:if="${newEvents.size() == 0}" class="col">
새 모임이 없습니다.
</div>
<div class="col mb-4 pr-0" th:each="event: ${newEvents}">
<div class="card">
<div class="card-header">
<span th:text="${event.title}">title</span>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">
<i class="fa fa-calendar"></i>
<span class="calendar" th:text="${event.startDateTime}"></span> 모임 시작
</li>
<li class="list-group-item">
<i class="fa fa-hourglass-end"></i> <span class="fromNow" th:text="${event.endEnrollmentDateTime}"></span> 모집 마감,
<span th:if="${event.limitOfEnrollments != 0}">
<span th:text="${event.limitOfEnrollments}"></span>명 모집 중
(<span th:text="${event.numberOfRemainSpots()}"></span> 자리 남음)
</span>
</li>
<li class="list-group-item">
<a href="#" th:href="@{'/study/' + ${study.path} + '/events/' + ${event.id}}" class="card-link">자세히 보기</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="col-10 px-0 row">
<div class="col-2"></div>
<div class="col-10">
<table th:if="${oldEvents.size() > 0}" class="table table-hover">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">지난 모임 이름</th>
<th scope="col">모임 종료</th>
<th scope="col"></th>
</tr>
</thead>
<tbody th:each="event: ${oldEvents}">
<tr>
<th scope="row" th:text="${eventStat.count}">1</th>
<td th:text="${event.title}">Title</td>
<td>
<span class="date-weekday-time" th:text="${event.endDateTime}"></span>
</td>
<td>
<a href="#" th:href="@{'/study/' + ${study.path} + '/events/' + ${event.id}}" class="card-link">자세히 보기</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div th:replace="fragments.html :: footer"></div>
</div>
<script th:replace="fragments.html :: tooltip"></script>
<script th:replace="fragments.html :: date-time"></script>
</body>
</html>
모임에 몇자리가 남아있는지 확인하기 위해 Event 엔티티에 numberOfRemainSpots메소드 추가
package me.weekbelt.studyolle.domain;
@NoArgsConstructor
@Getter
@Setter
@EqualsAndHashCode(of = "id")
@Entity
public class Event {
// ........
public int numberOfRemainSpots() {
return this.limitOfEnrollments - (int) this.enrollments.stream().filter(Enrollment::isAccepted).count();
}
}
그런데 이렇게 호출을 하게되면 각각의 모임마다 enrollments를 조회하기때문에 모임이 많아진다면 N+1문제로 성능상에 문제가 생길것이 자명하다. 따라서 N+1을 해결해야한다.
Event Entity에서 Event를 호출할때 enollments를 같이 조인해서 호출하도록 설정을 한다.
package me.weekbelt.studyolle.domain;
@NamedEntityGraph(
name = "Event.withEnrollments",
attributeNodes = @NamedAttributeNode("enrollments")
)
public class Event {
// .........
@OneToMany(mappedBy = "event")
private List<Enrollment> enrollments = new ArrayList<>();
// .........
}
EventRepository에 EntityGraph를 설정한다.
package me.weekbelt.studyolle.event;
@Transactional(readOnly = true)
public interface EventRepository extends JpaRepository<Event, Long> {
@EntityGraph(value = "Event.withEnrollments", type = EntityGraph.EntityGraphType.LOAD) // 추가
List<Event> findByStudyOrderByStartDateTime(Study study);
}
참고: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1#
'스프링과 JPA 기반 웹 어플리케이션 개발 > 5부 모임' 카테고리의 다른 글
66. 모임 취소 (0) | 2020.05.04 |
---|---|
65. 모임 수정 (0) | 2020.05.04 |
63. 모임 조회 (0) | 2020.05.04 |
62. 모임 만들기 폼 서브밋 (0) | 2020.05.04 |
61. 모임 만들기 (0) | 2020.04.30 |