
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();
}
}
}

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);
}
}

Share article