![[Tistory] 게시글 상세보기](https://image.inblog.dev?url=https%3A%2F%2Finblog.ai%2Fapi%2Fog%3Ftitle%3D%255BTistory%255D%2520%25EA%25B2%258C%25EC%258B%259C%25EA%25B8%2580%2520%25EC%2583%2581%25EC%2584%25B8%25EB%25B3%25B4%25EA%25B8%25B0%26logoUrl%3Dhttps%253A%252F%252Finblog.ai%252Finblog_logo.png%26blogTitle%3DCoding_study&w=2048&q=75)
1. 화면

2. PostResponse - DetailDTO 생성
// 게시글 상세보기
@Data
public static class DetailDTO {
private Integer id;
private String title;
private String content;
private Integer userId;
private String username;
private String createdAt;
private Boolean isPostOwner;
public DetailDTO(Integer id, String title, String content, Integer userId, String username, LocalDateTime createdAt, Boolean isPostOwner) {
this.id = id;
this.title = title;
this.content = content;
this.userId = userId;
this.username = username;
this.createdAt = createdAt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
}
}
3. PostJPARepository
// 게시글 상세보기
@Query("select new site.metacoding.blogv3.post.PostResponse$DetailDTO(p.id, p.title, p.content, p.user.id, p.user.username, p.createdAt)" +
"from Post p where p.id = :postId")
Optional<PostResponse.DetailDTO> findByPostId(Integer postId);
4. PostService
public PostResponse.DetailDTO postDetail(Integer postId, User sessionUserId) {
PostResponse.DetailDTO postDetail = postJPARepo.findByPostId(postId)
.orElseThrow(() -> new RuntimeException("게시글이 존재하지 않습니다."));
System.out.println("postDetail = " + postDetail);
Boolean isPostOwner = false;
if (sessionUserId != null) {
if (sessionUserId.getId() == postDetail.getUserId()) {
isPostOwner = true;
}
}
postDetail.setIsPostOwner(isPostOwner);
return postDetail;
}
5. PostController
@GetMapping("/post/detail/{postId}")
public String postDetail(@PathVariable Integer postId, HttpServletRequest request) {
User user = (User) session.getAttribute("sessionUser");
PostResponse.DetailDTO postDetail = postService.postDetail(postId, user);
System.out.println("Post Detail: " + postDetail);
request.setAttribute("model", postDetail);
return "post/detail";
}
6. detail.mustache
{{>/layout/main-header}}
<div class="container">
<input id="postId" type="hidden" value="{{model.postId}}"/>
<input id="pageOwnerId" type="hidden" value="1"/>
<input id="my-loveId" type="hidden" value="1">
<div class="my_post_detail_title">
<h2>{{model.title}}</h2>
</div>
<hr/>
<div class="my_post_detail_content">
{{model.content}}
</div>
<div class="my_post_info_box d-flex">
<div class="my_post_info">
<i class="fa-solid fa-heart my_fake_like my_mr_sm_1" id="heart-1"></i>
by <b>{{model.username}}</b> <span class="my_text_body_sm">날짜</span>
<!-- <i class="far fa-heart my_fake_un_like my_mr_sm_1" id="my-heart"></i>-->
<!-- by <b>유저네임</b> <span class="my_text_body_sm">날짜</span>-->
</div>
</div>
{{#model.isPostOwner}}
<div class="my_mt_md_1">
<a class="btn btn-outline-success" href="#">수정</a>
<button id="btn-delete" class="btn btn-outline-danger">삭제</button>
</div>
{{/model.isPostOwner}}
<br/>
<!-- <div class="my_livere">-->
<!-- <!– 라이브리 시티 설치 코드 –>-->
<!-- <div id="lv-container" id="city" data-uid="MTAyMC81MTM0MC8yNzgyMQ==">-->
<!-- <script type="text/javascript">-->
<!-- (function (d, s) {-->
<!-- var j, e = d.getElementsByTagName(s)[0];-->
<!-- if (typeof LivereTower === 'function') {-->
<!-- return;-->
<!-- }-->
<!-- j = d.createElement(s);-->
<!-- j.src = 'https://cdn-city.livere.com/js/embed.dist.js';-->
<!-- j.async = true;-->
<!-- e.parentNode.insertBefore(j, e);-->
<!-- })(document, 'script');-->
<!-- </script>-->
<!-- <noscript>라이브리 댓글 작성을 위해 JavaScript를 활성화 해주세요</noscript>-->
<!-- </div>-->
<!-- <!– 시티 설치 코드 끝 –>-->
<!-- </div>-->
</div>
{{>/layout/footer}}
7. 오류터짐…

list.mustache에서 상세보기 넘어갈때 postId 바인딩 왜 안되지???

8. PostService 수정
처음 쿼리 작성했을때 중첩 클래스의 경우 데이터베이스 쿼리에서 적절히 매핑이 안된거 같음
Post 엔티티를 데이터베이스에서 가져와보자..
public PostResponse.DetailDTO postDetail(Integer postId, User sessionUser) {
Post post = postJPARepo.findById(postId)
.orElseThrow(() -> new Exception404("게시글이 존재하지 않습니다."));
PostResponse.DetailDTO detailDTO = new PostResponse.DetailDTO(post, sessionUser);
return detailDTO;
}

public PostResponse.DetailDTO postDetail(Integer postId, User sessionUser) {
Post post = postJPARepo.findByIdWithUser(postId)
.orElseThrow(() -> new Exception404("게시글이 존재하지 않습니다."));
PostResponse.DetailDTO detailDTO = new PostResponse.DetailDTO(post, sessionUser);
return detailDTO;
}
9. PostJPARepository 수정
레이지 로딩 터짐 - 조인 패치로 쿼리 수정
// 게시글 상세보기 (조인패치)
@Query("SELECT p FROM Post p JOIN FETCH p.user WHERE p.id = :postId")
Optional<Post> findByIdWithUser(@Param("postId") Integer postId);
10. PostResponse - DetailDTO 수정(코드 가독성 향상)
DTO 합쳐서 응답하자 - 내부클래스로 만듦, 모든걸 DTO로 옮기는 방식
@Data
public static class DetailDTO {
private Integer postId;
private String title;
private String content;
private Integer userId;
private String username;
private String createdAt;
private Boolean isPostOwner;
public DetailDTO(Post post, User sessionUser){
this.postId = post.getId();
this.title = post.getTitle();
this.content = post.getContent();
this.userId = post.getUser().getId();
this.username = post.getUser().getUsername();
this.createdAt = post.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
// 세션유저 받아서 isPostOwner 만듬
isPostOwner = false;
if (sessionUser != null) {
if (sessionUser.getId() == post.getUser().getId()) {
isPostOwner = true;
}
}
}
}
11. 화면확인

아.. 됐다!!!
Share article