39. 댓글쓰기v6

송민경's avatar
Mar 19, 2024
39. 댓글쓰기v6

1. Board 에서 수정하기

  • isOwner → isBoardOwner
package shop.mtcoding.blog.board; import jakarta.persistence.*; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import shop.mtcoding.blog._core.utils.MyDateUtil; import shop.mtcoding.blog.reply.Reply; import shop.mtcoding.blog.user.User; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; @NoArgsConstructor @Data @Table(name = "board_tb") @Entity public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String title; private String content; //@JoinColumn(name = "user_id") @ManyToOne(fetch = FetchType.LAZY) private User user; // db -> user_id @CreationTimestamp // pc -> db (날짜주입) private Timestamp createdAt; @OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) // Entity 객체의 변수명 == FK의 주인 private List<Reply> replies = new ArrayList<>(); @Transient // 테이블 생성이 안됨 private boolean isBoardOwner; @Builder public Board(Integer id, String title, String content, User user, Timestamp createdAt) { this.id = id; this.title = title; this.content = content; this.user = user; this.createdAt = createdAt; } public String getBoardDate() { return MyDateUtil.timestampFormat(createdAt); } }
 

2. Reply 에서 isReplyOwner 추가하기

package shop.mtcoding.blog.reply; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIncludeProperties; import jakarta.persistence.*; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import shop.mtcoding.blog.board.Board; import shop.mtcoding.blog.user.User; import shop.mtcoding.blog._core.utils.MyDateUtil; import java.sql.Timestamp; @NoArgsConstructor @Data // 변경되는 데이터에만 setter가 필요함 @Table(name = "reply_tb") @Entity public class Reply { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String comment; // 1(user) 대 N(reply)의 관계 @ManyToOne(fetch = FetchType.LAZY) private User user; // user_id // 1(board) 대 N(reply)의 관계 @ManyToOne(fetch = FetchType.LAZY) private Board board; @Transient private boolean isReplyOwner; @CreationTimestamp // PC로 인해 DB에 INSERT될 때 날짜 주입 private Timestamp createdAt; @Builder public Reply(Integer id, String comment, User user, Board board, Timestamp createdAt) { this.id = id; this.comment = comment; this.user = user; this.board = board; this.createdAt = createdAt; } }
 

3. BoardService 에서 isBoardOwner로 수정하기

isReplyOwner 추가하기

package shop.mtcoding.blog.board; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import shop.mtcoding.blog._core.errors.exception.Exception403; import shop.mtcoding.blog._core.errors.exception.Exception404; import shop.mtcoding.blog.user.User; import java.util.List; @RequiredArgsConstructor @Service public class BoardService { private final BoardJPARepository boardJPARepository; // board와 isOwner를 응답해야하나 method는 하나밖에 응답할 수 없음 -> 하나의 덩어리로 만들어서 줘야 함 public Board detail(int boardId, User sessionUser) { Board board = boardJPARepository.findByIdJoinUser(boardId) .orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다")); boolean isBoardOwner = false; if(sessionUser != null){ if(sessionUser.getId() == board.getUser().getId()){ isBoardOwner = true; } } board.setBoardOwner(isBoardOwner); board.getReplies().forEach(reply -> { boolean isReplyOwner = false; if(sessionUser != null){ if(reply.getUser().getId() == sessionUser.getId()){ isReplyOwner = true; } } reply.setReplyOwner(isReplyOwner); }); return board; } public List<Board> findAll() { Sort sort = Sort.by(Sort.Direction.DESC, "id"); return boardJPARepository.findAll(sort); } @Transactional public void delete(int boardId, Integer sessionUserId) { Board board = boardJPARepository.findById(boardId) .orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다")); if (sessionUserId != board.getUser().getId()) { throw new Exception403("게시글을 삭제할 권한이 없습니다"); } boardJPARepository.deleteById(boardId); } public void update(int boardId, int sessionUserId, BoardRequest.UpdateDTO reqDTO) { Board board = boardJPARepository.findById(boardId) .orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다")); board.setTitle(reqDTO.getTitle()); board.setContent(reqDTO.getContent()); } public Board updateForm(int boardId, int sessionUserId) { // 1. 조회 및 예외처리 Board board = boardJPARepository.findById(boardId) .orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다")); // 2. 권한 처리 if (sessionUserId != board.getUser().getId()) { throw new Exception403("게시글 수정페이지로 이동할 권한이 없습니다"); } return board; } @Transactional public void save(BoardRequest.SaveDTO reqDTO, User sessionUser) { boardJPARepository.save(reqDTO.toEntity(sessionUser)); } }
 

4. detail.mustache에서 수정하기

  • {{#board.boardOwner}}
  • {{#replyOwner}}
{{> /layout/header}} <div class="container p-5"> {{#board.boardOwner}} <!-- 수정삭제버튼 --> <div class="d-flex justify-content-end"> <a href="/board/{{board.id}}/update-form" class="btn btn-warning me-1">수정</a> <form action="/board/{{board.id}}/delete" method="post"> <button class="btn btn-danger">삭제</button> </form> </div> {{/board.boardOwner}} <div class="d-flex justify-content-end"> <b>작성자</b> : {{board.user.username}} </div> <!-- 게시글내용 --> <div> <h2><b>{{board.title}}</b></h2> <hr/> <div class="m-4 p-2"> {{board.content}} </div> </div> <!-- 댓글 --> <div class="card mt-3"> <!-- 댓글등록 --> <div class="card-body"> <form action="/reply/save" method="post"> <input type="hidden" name="boardId" value="{{board.id}}"> <textarea class="form-control" rows="2" name="comment"></textarea> <div class="d-flex justify-content-end"> <button type="submit" class="btn btn-outline-primary mt-1">댓글등록</button> </div> </form> </div> <!-- 댓글목록 --> <div class="card-footer"> <b>댓글리스트</b> </div> <div class="list-group"> {{#board.replies}} <!-- 댓글아이템 --> <div class="list-group-item d-flex justify-content-between align-items-center"> <div class="d-flex"> <div class="px-1 me-1 bg-primary text-white rounded">{{user.username}}</div> <div>{{comment}}</div> </div> {{#replyOwner}} <form action="/reply/{{id}}/delete" method="post"> <button class="btn">🗑</button> </form> {{/replyOwner}} </div> {{/board.replies}} </div> </div> </div> {{> /layout/footer}}
 

5. ReplyRequest 에서 SaveDTO 만들기

  • 정보를 가방에 담아서 뷰에 전달하기
package shop.mtcoding.blog.reply; import lombok.Data; import shop.mtcoding.blog.board.Board; import shop.mtcoding.blog.user.User; public class ReplyRequest { @Data public static class SaveDTO { private Integer boardId; private String comment; public Reply toEntity(User sessionUser, Board board){ return Reply.builder() .comment(comment) .board(board) .user(sessionUser) .build(); } } }
 

6. ReplyController 에서 save 만들기

package shop.mtcoding.blog.reply; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import shop.mtcoding.blog.user.User; @RequiredArgsConstructor @Controller public class ReplyController { private final ReplyService replyService; private final HttpSession session; @PostMapping("/reply/save") public String save(ReplyRequest.SaveDTO reqDTO){ User sessionUser = (User) session.getAttribute("sessionUser"); replyService.save(reqDTO, sessionUser); return "redirect:/board/"+reqDTO.getBoardId(); } }
 

7. WebMvcConfig 에서 “/reply/**” 주소 추가하기

package shop.mtcoding.blog._core.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import shop.mtcoding.blog._core.intercepter.LoginInterceptor; @Configuration // IoC public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/board/**", "/user/**", "/reply/**") .excludePathPatterns("/board/{id:\\d+}"); } }
 

8. ReplyService 에 save 추가하기

  • 먼저 게시글을 조회하고 그 게시글에 댓글 저장하기
package shop.mtcoding.blog.reply; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import shop.mtcoding.blog._core.errors.exception.Exception404; import shop.mtcoding.blog.board.Board; import shop.mtcoding.blog.board.BoardJPARepository; import shop.mtcoding.blog.user.User; @RequiredArgsConstructor @Service public class ReplyService { private final BoardJPARepository boardJPARepository; private final ReplyJPARepository replyJPARepository; @Transactional public void save(ReplyRequest.SaveDTO reqDTO, User sessionUser) { Board board = boardJPARepository.findById(reqDTO.getBoardId()) .orElseThrow(() -> new Exception404("없는 게시글에 댓글을 작성할 수 없어요")); Reply reply = reqDTO.toEntity(sessionUser, board); replyJPARepository.save(reply); } }
 

9. SaveDTO 에 Reply toEntity 에 .board() 수정하기

  • board를 조회해서 넣어야 함
package shop.mtcoding.blog.reply; import lombok.Data; import shop.mtcoding.blog.board.Board; import shop.mtcoding.blog.user.User; public class ReplyRequest { @Data public static class SaveDTO { private Integer boardId; private String comment; public Reply toEntity(User sessionUser, Board board){ return Reply.builder() .comment(comment) .board(board) // 조회해서 넣어야 함 .user(sessionUser) .build(); } } }
notion image
 

10. 댓글 목록 정렬하기

@OrderBy("id desc") // 최신순으로 정렬
package shop.mtcoding.blog.board; import jakarta.persistence.*; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import shop.mtcoding.blog._core.utils.MyDateUtil; import shop.mtcoding.blog.reply.Reply; import shop.mtcoding.blog.user.User; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; @NoArgsConstructor @Data @Table(name = "board_tb") @Entity public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String title; private String content; //@JoinColumn(name = "user_id") @ManyToOne(fetch = FetchType.LAZY) private User user; // db -> user_id @CreationTimestamp // pc -> db (날짜주입) private Timestamp createdAt; @OrderBy("id desc") @OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) // Entity 객체의 변수명 == FK의 주인 private List<Reply> replies = new ArrayList<>(); @Transient // 테이블 생성이 안됨 private boolean isBoardOwner; @Builder public Board(Integer id, String title, String content, User user, Timestamp createdAt) { this.id = id; this.title = title; this.content = content; this.user = user; this.createdAt = createdAt; } public String getBoardDate() { return MyDateUtil.timestampFormat(createdAt); } }
notion image
Share article
RSSPowered by inblog