[Spring Boot] 11. Spring Boot Project (Blog v2 - jpa)_9.Board - Delete

김미숙's avatar
Jul 22, 2025
[Spring Boot] 11. Spring Boot Project (Blog v2 - jpa)_9.Board - Delete

Board

package shop.mtcoding.blog.board; import jakarta.persistence.*; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; 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 @Getter @Table(name = "board_tb") @Entity public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String title; private String content; private Boolean isPublic; @ManyToOne(fetch = FetchType.LAZY) private User user; // ORM @OneToMany(mappedBy = "board", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) // board = one , reply = many , mappedBy -> FK의 주인 @OneToMany -> 조회용도 private List<Reply> replies = new ArrayList<>(); //조회할 때만 넣기위함 @CreationTimestamp private Timestamp createdAt; @Builder public Board(Integer id, String title, String content, Boolean isPublic, User user, Timestamp createdAt) { this.id = id; this.title = title; this.content = content; this.isPublic = isPublic; this.user = user; this.createdAt = createdAt; } // 게시글 수정 Setter public void update(String title, String content, String isPublic) { this.title = title; this.content = content; this.isPublic = isPublic == null ? false : true; } }

BoardController

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.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import shop.mtcoding.blog.user.User; @RequiredArgsConstructor @Controller public class BoardController { private final BoardService boardService; private final HttpSession session; @GetMapping("/") public String list(HttpServletRequest request) { User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) { request.setAttribute("models", boardService.글목록보기(null)); } else { request.setAttribute("models", boardService.글목록보기(sessionUser.getId())); } return "board/list"; } @PostMapping("/board/save") public String save(BoardRequest.SaveDTO saveDTO) { // 인증로직 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new RuntimeException("인증이 필요합니다."); boardService.글쓰기(saveDTO, sessionUser); return "redirect:/"; } @GetMapping("/board/save-form") public String saveForm() { return "board/save-form"; } @GetMapping("/board/{id}") public String detail(@PathVariable("id") Integer id, HttpServletRequest request) { User sessionUser = (User) session.getAttribute("sessionUser"); // 비로그인 시 상세보기 Integer sessionUserId = (sessionUser == null ? null : sessionUser.getId()); BoardResponse.DetailDTO detailDTO = boardService.글상세보기(id, sessionUserId); request.setAttribute("model", detailDTO); return "board/detail"; } @GetMapping("/board/{id}/update-form") public String updateForm(@PathVariable("id") Integer id, HttpServletRequest request) { // 인증로직 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new RuntimeException("인증이 필요합니다."); Board board = boardService.업데이트글보기(id, sessionUser.getId()); request.setAttribute("model", board); return "board/update-form"; } @PostMapping("/board/{id}/update") public String update(@PathVariable("id") Integer id, BoardRequest.UpdateDTO reqDTO) { // 인증로직 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new RuntimeException("인증이 필요합니다."); boardService.게시글수정(reqDTO, id, sessionUser.getId()); return "redirect:/board/" + id; } @PostMapping("/board/{id}/delete") public String delete(@PathVariable("id") Integer id) { // 인증로직 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new RuntimeException("인증이 필요합니다."); boardService.게시글삭제(id, sessionUser.getId()); return "redirect:/"; } }

BoardService

package shop.mtcoding.blog.board; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import shop.mtcoding.blog._core.error.ex.Exception403; import shop.mtcoding.blog._core.error.ex.Exception404; import shop.mtcoding.blog.love.Love; import shop.mtcoding.blog.love.LoveRepository; import shop.mtcoding.blog.reply.Reply; import shop.mtcoding.blog.reply.ReplyRepository; import shop.mtcoding.blog.user.User; import java.util.List; @RequiredArgsConstructor @Service public class BoardService { private final BoardRepository boardRepository; private final LoveRepository loveRepository; private final ReplyRepository replyRepository; @Transactional public void 글쓰기(BoardRequest.SaveDTO saveDTO, User sessionUser) { Board board = saveDTO.toEntity(sessionUser); boardRepository.save(board); } public List<Board> 글목록보기(Integer userId) { return boardRepository.findAll(userId); } public BoardResponse.DetailDTO 글상세보기(Integer id, Integer userId) { Board board = boardRepository.findByIdJoinUser(id); Love love = loveRepository.findByUserIdAndBoardId(userId, id); Boolean isLove = love == null ? false : true; Integer loveId = love == null ? null : love.getId(); Long loveCount = loveRepository.findByBoardId(board.getId()); List<Reply> replies = replyRepository.findAllByBoardId(id); BoardResponse.DetailDTO detailDTO = new BoardResponse.DetailDTO(board, userId, isLove, loveCount.intValue(), loveId, replies); return detailDTO; } public Board 업데이트글보기(Integer id, Integer sessionUserId) { Board boardPs = boardRepository.findById(id); if (boardPs == null) throw new RunTimeException("게시글을 찾을 수 없습니다."); if (!boardPs.getUser().getId().equals(sessionUserId)) throw new RunTimeException("권한이 없습니다."); return boardPs; } @Transactional public Board 게시글수정(BoardRequest.UpdateDTO reqDTO, Integer id, Integer sessionUserId) { Board board = boardRepository.findById(id); if (board == null) throw new RunTimeException("게시글을 찾을 수 없습니다"); if (!board.getUser().getId().equals(sessionUserId)) throw new RunTimeException("권한이 없습니다."); board.update(reqDTO.getTitle(), reqDTO.getContent(), reqDTO.getIsPublic()); return board; } @Transactional public void 게시글삭제(Integer id, Integer sessionUserId) { // 게시글 존재 확인 Board boardPs = boardRepository.findById(id); if (boardPs == null) throw new RunTimeException("게시글을 찾을 수 없습니다"); if (!boardPs.getUser().getId().equals(sessionUserId)) throw new RunTimeException("권한이 없습니다."); // 좋아요 삭제 loveRepository.deleteByBoardId(boardPs.getId()); boardRepository.deleteById(id); } }

BoardRepository

package shop.mtcoding.blog.board; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import java.util.List; @RequiredArgsConstructor @Repository public class BoardRepository { private final EntityManager em; public void save(Board board) { em.persist(board); } public List<Board> findAll(Integer userId) { // Integer를 써야 null을 넘길 수 있다 // 동적 query String s1 = "select b from Board b where b.isPublic = true or b.user.id = :userId order by b.id desc"; String s2 = "select b from Board b where b.isPublic = true order by b.id desc"; Query query = null; if (userId == null) { query = em.createQuery(s2, Board.class); } else { query = em.createQuery(s1, Board.class); query.setParameter("userId", userId); } return query.getResultList(); } public Board findById(Integer id) { return em.find(Board.class, id); } public Board findByIdJoinUser(Integer id) { // b -> board에 있는 필드만 프로잭션 / fetch를 써야 board안에 있는 user 객체도 같이 프로잭션됨 Query query = em.createQuery("select b from Board b join fetch b.user u where b.id = :id", Board.class); // inner join (on절은 생략가능하다) -> 객체지향 쿼리 query.setParameter("id", id); return (Board) query.getSingleResult(); } public Board findByIdJoinUserAndReplies(Integer id) { Query query = em.createQuery("select b from Board b join fetch b.user u left join fetch b.replies r left join fetch r.user where b.id = :id order by r.id desc", Board.class); // left join (on절은 생략가능하다) -> 객체지향 쿼리 query.setParameter("id", id); return (Board) query.getSingleResult(); } public void deleteById(Integer id) { Board board = em.find(Board.class, id); // 영속성 컨텍스트에 넣기 em.remove(board); // 연관된 Reply들도 함께 삭제됨 (Cascade 동작) } }

LoveRepository

package shop.mtcoding.blog.love; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @RequiredArgsConstructor @Repository public class LoveRepository { private final EntityManager em; public Love findByUserIdAndBoardId(Integer userId, Integer boardId) { Query query = em.createQuery("select lo from Love lo where lo.user.id = :userId and lo.board.id = :boardId", Love.class); query.setParameter("userId", userId); query.setParameter("boardId", boardId); try { return (Love) query.getSingleResult(); // unique 제약조건이기 때문에 SingleResult } catch (Exception e) { return null; } } public Long findByBoardId(int boardId) { Query query = em.createQuery("select count(lo) from Love lo where lo.board.id = :boardId"); query.setParameter("boardId", boardId); Long count = (Long) query.getSingleResult(); try { return count; // unique 제약조건이기 때문에 SingleResult } catch (Exception e) { return null; } } public Love save(Love love) { em.persist(love); return love; } public void deleteById(Integer id) { em.createQuery("delete from Love lo where lo.id = :id") .setParameter("id", id) .executeUpdate(); } public Love findById(Integer id) { return em.find(Love.class, id); } public void deleteByBoardId(Integer id) { em.createQuery("delete from Love lo where lo.board.id = :id") .setParameter("id", id) .executeUpdate(); } }

Test

현재 DB
notion image
댓글,좋아요 없는 게시물 삭제
notion image
❓ 삭제 누르면
notion image
notion image
❗ 화면에서도 해당 게시물이 삭제되고, DB에서도 삭제됨
 
좋아요만 있는 게시물 삭제
notion image
 
❓ 삭제 누르면
notion image
notion image
❗ 화면에서도 해당 게시물이 삭제되고, DB에서도 삭제됨
 
댓글만 있는 게시물 삭제
notion image
❓ 삭제 누르면
notion image
notion image
❗ 화면에서도 해당 게시물이 삭제되고, DB에서도 삭제됨
 
 
Share article

parangdajavous