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

댓글,좋아요 없는 게시물 삭제

❓ 삭제 누르면


❗ 화면에서도 해당 게시물이 삭제되고, DB에서도 삭제됨
좋아요만 있는 게시물 삭제

❓ 삭제 누르면


❗ 화면에서도 해당 게시물이 삭제되고, DB에서도 삭제됨
댓글만 있는 게시물 삭제

❓ 삭제 누르면


❗ 화면에서도 해당 게시물이 삭제되고, DB에서도 삭제됨
Share article