
Contents
4. detail.mustache 파일 수정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;

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




Share article