타임리프 Variable Expression에서 객체의 메소드 호출 가능
th:if="${study.isManager(#authentication.principal)}"
StudyRepository에 스터디 URL로 스터디를 조회하는 메소드 추가
package me.weekbelt.studyolle.study;
@Transactional(readOnly = true)
public interface StudyRepository extends JpaRepository<Study, Long> {
// .....
Study findByPath(String path);
}
스터디 조회 요청을 받는 핸들러 작성
package me.weekbelt.studyolle.study;
@RequiredArgsConstructor
@Controller
public class StudyController {
// .......
private final StudyRepository studyRepository;
// .......
@GetMapping("/study/{path}")
public String viewStudy(@CurrentAccount Account account, @PathVariable String path,
Model model) {
model.addAttribute(account);
model.addAttribute(studyRepository.findByPath(path));
return "study/view";
}
}
Study엔티티에 지금 계정의 가입 여부관련 메소드 추가
package me.weekbelt.studyolle.domain;
@Builder @AllArgsConstructor @NoArgsConstructor
@Getter @Setter @EqualsAndHashCode(of = "id")
@Entity
public class Study {
// ..............
public boolean isJoinable(UserAccount userAccount){
Account account = userAccount.getAccount();
return this.isPublished() && this.isRecruiting()
&& !this.members.contains(account)
&& !this.managers.contains(account);
}
public boolean isMember(UserAccount userAccount) {
return this.members.contains(userAccount.getAccount());
}
public boolean isManager(UserAccount userAccount) {
return this.managers.contains(userAccount.getAccount());
}
}
스터디의 기본 정보를 담은 HTML fragment를 fragments.html에 추가
<div th:fragment="study-info">
<div class="row pt-4 text-left justify-content-center bg-light">
<div class="col-6">
<a href="#" class="text-decoration-none" th:href="@{'/study/' + ${study.path}}">
<span class="h2" th:text="${study.title}">스터디 이름</span>
</a>
</div>
<div class="col-4 text-right justify-content-end">
<span th:if="${!study.published}" class="d-inline-block" tabindex="0" data-toggle="tooltip" data-placement="bottom" title="스터디 공개 준비중">
<button class="btn btn-primary btn-sm" style="pointer-events: none;"
type="button" disabled>DRAFT</button>
</span>
<span th:if="${study.closed}" class="d-inline-block" tabindex="0" data-toggle="tooltip" data-placement="bottom" title="스터디 종료함">
<button class="btn btn-primary btn-sm" style="pointer-events: none;"
type="button" disabled>CLOSED</button>
</span>
<span th:if="${!study.recruiting}" class="d-inline-block ml-1" tabindex="0" data-toggle="tooltip" data-placement="bottom" title="팀원 모집중 아님">
<button class="btn btn-primary btn-sm" style="pointer-events: none;"
type="button" disabled>OFF</button>
</span>
<span sec:authorize="isAuthenticated()"
th:if="${study.isJoinable(#authentication.principal)}" class="btn-group" role="group" aria-label="Basic example">
<a class="btn btn-primary" th:href="@{'/study/' + ${study.path} + '/join'}">
스터디 가입
</a>
<a class="btn btn-outline-primary" th:href="@{'/study/' + ${study.path} + '/members'}" th:text="${study.members.size()}">1</a>
</span>
<span sec:authorize="isAuthenticated()"
th:if="${!study.closed && study.isMember(#authentication.principal)}" class="btn-group" role="group">
<a class="btn btn-outline-warning" th:href="@{'/study/' + ${study.path} + '/leave'}">
스터디 탈퇴
</a>
<a class="btn btn-outline-primary" th:href="@{'/study/' + ${study.path} + '/members'}" th:text="${study.members.size()}">1</a>
</span>
<span sec:authorize="isAuthenticated()"
th:if="${study.published && !study.closed && study.isManager(#authentication.principal)}">
<a class="btn btn-outline-primary" th:href="@{'/study/' + ${study.path} + '/new-event'}">
<i class="fa fa-plus"></i>모임 만들기
</a>
</span>
</div>
</div>
<div class="row justify-content-center bg-light">
<div class="col-10">
<p class="lead" th:text="${study.shortDescription}"></p>
</div>
</div>
<div class="row justify-content-center bg-light">
<div class="col-10">
<p>
<span th:each="tag: ${study.tags}" class="font-weight-light text-monospace badge badge-pill badge-info mr-3">
<a th:href="@{'/search/tag/' + ${tag.title}}" class="text-decoration-none text-white">
<i class="fa fa-tag"></i> <span th:text="${tag.title}">Tag</span>
</a>
</span>
<span th:each="zone: ${study.zones}" class="font-weight-light text-monospace badge badge-primary mr-3">
<a th:href="@{'/search/zone/' + ${zone.id}}" class="text-decoration-none text-white">
<i class="fa fa-globe"></i> <span th:text="${zone.localNameOfCity}">Tag</span>
</a>
</span>
</p>
</div>
</div>
</div>
스터디 조회의 탭키에 해당하는 HTMl fragment를 fragments.html에 추가
<!--스터디 소개 탭 키-->
<div th:fragment="study-menu (studyMenu)" class="row px-3 justify-content-center bg-light">
<nav class="col-10 nav nav-tabs">
<a class="nav-item nav-link" th:classappend="${studyMenu == 'info'}? active" th:href="@{'/study/' + ${study.path}}">
<i class="fa fa-info-circle"></i>소개
</a>
<a class="nav-item nav-link" th:classappend="${studyMenu == 'members'}? active" th:href="@{'/study/' + ${study.path} + '/members'}">
<i class="fa fa-user"></i>구성원
</a>
<a class="nav-item nav-link" th:classappend="${studyMenu == 'events'}? active" th:href="@{'/study/' + ${study.path} + '/events'}">
<i class="fa fa-calendar"></i>모임
</a>
<a sec:authorize="isAuthenticated()" th:if="${study.isManager(#authentication.principal)}"
class="nav-item nav-link" th:classappend="${studyMenu == 'settings'}? active" th:href="@{'/study/' + ${study.path} + '/settings/description'}">
<i class="fa fa-cog"></i>설정
</a>
</nav>
</div>
등록한 스터디를 보여주는 study/view.html 작성
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments.html::head"></head>
<body class="bg-light">
<div th:replace="fragments.html::main-nav"></div>
<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='info')"></div>
<!-- 본문 -->
<div class="row px-3 justify-content-center">
<div class="col-10 pt-3" th:utext="${study.fullDescription}"></div>
</div>
<!-- footer -->
<div th:replace="fragments.html::footer"></div>
</div>
<script type="application/javascript">
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
</script>
</body>
</html>
스터디 조회 화면을 요청하면 Study, Account, Tag, Zone와 관련된 쿼리를 각각 호출하게 되어 5개나 되는 쿼리가 호출이 된다. 이것을 조인을 통해 한방에 불러올 수 있게 하는 게 쿼리를 덜 날리기 때문에 효율적이다.
Study엔티티에 조인할 엔티티를 설정
package me.weekbelt.studyolle.domain;
// 추가
@NamedEntityGraph(name = "Study.withAll", attributeNodes = {
@NamedAttributeNode("tags"),
@NamedAttributeNode("zones"),
@NamedAttributeNode("managers"),
@NamedAttributeNode("members")})
@Builder @AllArgsConstructor @NoArgsConstructor
@Getter @Setter @EqualsAndHashCode(of = "id")
@Entity
public class Study {
// .....
}
package me.weekbelt.studyolle.study;
@Transactional(readOnly = true)
public interface StudyRepository extends JpaRepository<Study, Long> {
// ......
@EntityGraph(value = "Study.withAll", type= EntityGraph.EntityGraphType.LOAD) // 추가
Study findByPath(String path);
}
이렇게 설정하고 다시 스터디 조회를 하면 쿼리가 1개만 나가는 것을 알 수 있다.
참고: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1#
'스프링과 JPA 기반 웹 어플리케이션 개발 > 4부 스터디' 카테고리의 다른 글
51. 스터디 폼 & 개설 & 멤버 조회 테스트 (0) | 2020.04.24 |
---|---|
50. 스터디 구성원 조회 (0) | 2020.04.23 |
48. 스터디 개설 (0) | 2020.04.23 |
47. 스터디 도메인 (0) | 2020.04.23 |
46. 스터디 관리 기능 미리보기 (0) | 2020.04.23 |