36. 댓글 구현 : 댓글 목록 구현하기

Feb 15, 2024
36. 댓글 구현 : 댓글 목록 구현하기

    1. BoardResponse에 DetailDTO과 ReplyDTO 생성하기

    • 생성자로 초기화 해줘야 함
    package shop.mtcoding.blog.board; import lombok.AllArgsConstructor; import lombok.Data; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; public class BoardResponse { @Data public static class DetailDTO { private Integer id; private String title; private String content; private Integer userId; private String username; private Timestamp createdAt; private Boolean pageOwner; // 페이지 주인 여부 private List<ReplyDTO> replies = new ArrayList<>(); // 생성자로 초기화 public void addReply(ReplyDTO reply){ replies.add(reply); } // 생성자로 초기화 public DetailDTO(Integer id, String title, String content, Integer userId, String username, Timestamp createdAt) { this.id = id; this.title = title; this.content = content; this.userId = userId; this.username = username; this.createdAt = createdAt; } } @AllArgsConstructor @Data public static class ReplyDTO { private Integer rId; private Integer rUserId; private String rUsername; private String rComment; private Boolean rOwner; // 로그인 한 유저가 댓글의 주인인지 여부 } }
     

    2. BoardRepository에 findByIdWithUserAndWithReply() 만들기

    select bt.id, bt.title, bt.content, bt.user_id, but.username, bt.created_at, rt.id r_id, rt.user_id r_user_id, rut.username, rt.comment from board_tb bt left outer join reply_tb rt on bt.id = rt.board_id inner join user_tb but on bt.user_id = but.id left outer join user_tb rut on rt.user_id = rut.id where bt.id = 4;
    notion image
    package shop.mtcoding.blog.board; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import java.sql.Timestamp; import java.util.List; @RequiredArgsConstructor @Repository public class BoardRepository { private final EntityManager em; public List<Board> findAll() { Query query = em.createNativeQuery("select * from board_tb order by id desc", Board.class); return query.getResultList(); } public Board findById(int id) { Query query = em.createNativeQuery("select * from board_tb where id = ?", Board.class); query.setParameter(1, id); Board board = (Board) query.getSingleResult(); return board; } // public BoardResponse.DetailDTO findByIdWithUser(int idx) { // Query query = em.createNativeQuery("select b.id, b.title, b.content, b.user_id, u.username from board_tb b inner join user_tb u on b.user_id = u.id where b.id = ?"); // query.setParameter(1, idx); // // Object[] row = (Object[]) query.getSingleResult(); // Integer id = (Integer) row[0]; // String title = (String) row[1]; // String content = (String) row[2]; // int userId = (Integer) row[3]; // String username = (String) row[4]; // System.out.println("id : " + id); // System.out.println("title : " + title); // System.out.println("content : " + content); // System.out.println("userId : " + userId); // System.out.println("username : " + username); // BoardResponse.DetailDTO responseDTO = new BoardResponse.DetailDTO(); // responseDTO.setId(id); // responseDTO.setTitle(title); // responseDTO.setContent(content); // responseDTO.setUserId(userId); // responseDTO.setUsername(username); // return responseDTO; // } public BoardResponse.DetailDTO findByIdWithUserAndWithReply(int idx) { String q = """ select bt.id, bt.title, bt.content, bt.user_id, but.username, bt.created_at, rt.id r_id, rt.user_id r_user_id, rut.username, rt.comment from board_tb bt left outer join reply_tb rt on bt.id = rt.board_id inner join user_tb but on bt.user_id = but.id left outer join user_tb rut on rt.user_id = rut.id where bt.id = ? """; Query query = em.createNativeQuery(q); query.setParameter(1, idx); // 1. 전체 결과 받기 List<Object[]> rows = (List<Object[]>) query.getResultList(); // 2. Board 결과가 3개가 중복되기 때문에, 0번지의 값만 가져오기 Integer id = (Integer) rows.get(0)[0]; String title = (String) rows.get(0)[1]; String content = (String) rows.get(0)[2]; int userId = (Integer) rows.get(0)[3]; String username = (String) rows.get(0)[4]; Timestamp createdAt = (Timestamp) rows.get(0)[5]; BoardResponse.DetailDTO detailDTO = new BoardResponse.DetailDTO( id, title, content, userId, username, createdAt ); // 3 바퀴 돌면서 댓글 추가하기 for (Object[] row : rows) { Integer rId = (Integer) row[6]; Integer rUserId = (Integer) row[7]; String rUsername = (String) row[8]; String rComment = (String) row[9]; BoardResponse.ReplyDTO replyDTO = new BoardResponse.ReplyDTO( rId, rUserId, rUsername, rComment, false ); // 댓글이 없으면 add 안하기 if (rId != null) detailDTO.addReply(replyDTO); } return detailDTO; } @Transactional public void save(BoardRequest.SaveDTO requestDTO, int userId) { Query query = em.createNativeQuery("insert into board_tb(title, content, user_id, created_at) values(?,?,?, now())"); query.setParameter(1, requestDTO.getTitle()); query.setParameter(2, requestDTO.getContent()); query.setParameter(3, userId); query.executeUpdate(); } @Transactional public void deleteById(int id) { Query query = em.createNativeQuery("delete from board_tb where id = ?"); query.setParameter(1, id); query.executeUpdate(); } @Transactional public void update(BoardRequest.UpdateDTO requestDTO, int id) { Query query = em.createNativeQuery("update board_tb set title=?, content=? where id = ?"); query.setParameter(1, requestDTO.getTitle()); query.setParameter(2, requestDTO.getContent()); query.setParameter(3, id); query.executeUpdate(); } }
     

    3. BoardController에서 detail() 수정하기

    package shop.mtcoding.blog.board; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import shop.mtcoding.blog.user.User; import java.util.List; @RequiredArgsConstructor @Controller public class BoardController { private final HttpSession session; // DI private final BoardRepository boardRepository; // DI // ?title=제목1&content=내용1 // title=제목1&content=내용1 // 쿼리 스트링과 -x-www-form-urlencoded와 파싱 방법이 동일함 @PostMapping("/board/{id}/update") public String update(@PathVariable int id, BoardRequest.UpdateDTO requestDTO) { // 파싱 전략이 json으로 바뀜 // System.out.println(requestDTO); 정보 받기 확인 // 인증 체크하기 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) { return "redirect:/loginForm"; } // 권한 체크하기 Board board = boardRepository.findById(id); if (board.getUserId() != sessionUser.getId()) { return "error/403"; } // update board_tb set title =?, content =?, where id =? boardRepository.update(requestDTO, id); return "redirect:/board/" + id; // 수정한 게시글로 돌아가기 } @GetMapping("/board/{id}/updateForm") // 보드에 해당 페이지 public String updateFormn(@PathVariable int id, HttpServletRequest request) { // 인증 체크하기 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) { return "redirect:/loginForm"; } // 권한 체크하기 Board board = boardRepository.findById(id); if (board.getUserId() != sessionUser.getId()) { request.setAttribute("status", 403); request.setAttribute("msg", "게시글을 수정할 권한이 없습니다"); return "error/40x"; // 리다이렉트 하면 데이터 사라지니까 하면 안됨 } // 가방에 담기 request.setAttribute("board", board); return "board/updateForm"; } @PostMapping("/board/{id}/delete") // body데이터가 없어서 유효성 검사 안해도 됨 public String delete(@PathVariable int id) { // 1. 인증 검사하기 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) { return "redirect:/loginForm"; } // 2. 권한 검사하기 Board board = boardRepository.findById(id); if (board.getUserId() != sessionUser.getId()) { return "error/403"; } boardRepository.deleteById(id); return "redirect:/"; } @PostMapping("/board/save") public String save(BoardRequest.SaveDTO requestDTO, HttpServletRequest request) { // 1. 인증 체크 User sessionUser = (User) session.getAttribute("sessionUser"); System.out.println("sessionUser:" + sessionUser); if (sessionUser == null) { return "redirect:/loginForm"; } // 2. 바디 데이터 확인 및 유효성 검사 System.out.println(requestDTO); if (requestDTO.getTitle().length() > 30) { request.setAttribute("status", 400); request.setAttribute("msg", "title의 길이가 30자를 초과해서는 안되요"); return "error/40x"; // BadRequest } // 3. 모델 위임 // insert into board_tb(title, content, user_id, created_at) values(?,?,?, now()); boardRepository.save(requestDTO, sessionUser.getId()); return "redirect:/"; } // http://localhost:8080?page=0 @GetMapping({"/", "/board"}) public String index(HttpServletRequest request) { List<Board> boardList = boardRepository.findAll(); request.setAttribute("boardList", boardList); return "index"; } @GetMapping("/board/saveForm") // /board/saveForm Get요청이 옴 public String saveForm() { // session 영역에 접근하기 위한 // 1. session 영역에 sessionUser 키 값에 user 객체가 있는지 체크하기 User sessionUser = (User) session.getAttribute("sessionUser"); // 2. 값이 null이면 로그인 페이지로 리다이렉션 if (sessionUser == null) { return "redirect:/loginForm"; } // 3. null이 아니면 /board/saveForm으로 이동 return "board/saveForm"; } // 상세보기시 호출 @GetMapping("/board/{id}") public String detail(@PathVariable int id, HttpServletRequest request) { // 1. 모델 진입 - 상세보기 데이터 가져오기 BoardResponse.DetailDTO responseDTO = boardRepository.findByIdWithUserAndWithReply(id); // 2. 페이지 주인 여부 체크 (board의 userId와 sessionUser의 id를 비교) User sessionUser = (User) session.getAttribute("sessionUser"); boolean pageOwner; if (sessionUser == null) { pageOwner = false; } else { int 게시글작성자번호 = responseDTO.getUserId(); int 로그인한사람의번호 = sessionUser.getId(); pageOwner = 게시글작성자번호 == 로그인한사람의번호; } // 페이지 주인 여부 responseDTO.setPageOwner(pageOwner); // 댓글 주인 여부 if (sessionUser != null) { for (BoardResponse.ReplyDTO reply : responseDTO.getReplies()){ if(reply.getRUserId() == sessionUser.getId()){ reply.setROwner(true); } } } request.setAttribute("board", responseDTO); return "board/detail"; } }
     

    4. detail.mustache 파일 수정

    • pageOwner : 게시글 수정, 삭제 버튼 활성화 조건
    • rOwner : 댓글 삭제 버튼 활성화 조건
    {{> /layout/header}} <div class="container p-5"> {{#board.pageOwner}} <!-- 수정삭제버튼 --> <div class="d-flex justify-content-end"> <a href="/board/{{board.id}}/updateForm" class="btn btn-warning me-1">수정</a> <form action="/board/{{board.id}}/delete" method="post"> <button class="btn btn-danger">삭제</button> </form> </div> {{/board.pageOwner}} <div class="d-flex justify-content-end"> <b>작성자</b> : {{board.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">{{rUsername}}</div> <div>{{rComment}}</div> </div> {{#rOwner}} <form action="/reply/{{rId}}/delete" method="post"> <button class="btn">🗑</button> </form> {{/rOwner}} </div> {{/board.replies}} </div> </div> </div> {{> /layout/footer}}
    notion image
    notion image
    notion image
    notion image
    Share article

    vosw1