![[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