Spring/Spring boot icia 74일차
Spring boot 게시판 댓글처리
swkn
2023. 6. 8. 16:45
1. BoardEntity
package com.icia.board.entity;
import com.icia.board.dto.BoardDTO;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "board_table")
@Getter @Setter
public class BoardEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 20, nullable = false)
private String boardWriter;
@Column(length = 50, nullable = false)
private String boardTitle;
@Column(length = 20, nullable = false)
private String boardPass;
@Column(length = 500)
private String boardContents;
@Column
private int boardHits;
@CreationTimestamp
@Column(updatable = false)
private LocalDateTime createdAt;
@Column
private int fileAttached;
@OneToMany(mappedBy = "boardEntity", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY)
// fatchType.Eager = 자식데이터도 같이 가져감 LAZY = 자식데이터를 필요할때 메소드호출로 가져옴
// orphanRemoval = 부모잃은 객체를 어떻게할것인가
private List<BoardFileEntity> boardFileEntityList = new ArrayList<>();
@OneToMany(mappedBy = "boardEntity", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY)
private List<CommentEntity> commentEntityList = new ArrayList<>();
public static BoardEntity toSaveEntity(BoardDTO boardDTO) {
BoardEntity boardEntity = new BoardEntity();
boardEntity.setBoardWriter(boardDTO.getBoardWriter());
boardEntity.setBoardPass(boardDTO.getBoardPass());
boardEntity.setBoardTitle(boardDTO.getBoardTitle());
boardEntity.setBoardContents(boardDTO.getBoardContents());
boardEntity.setBoardHits(0);
boardEntity.setFileAttached(0);
return boardEntity;
}
public static BoardEntity toUpdateEntity(BoardDTO boardDTO) {
BoardEntity boardEntity = new BoardEntity();
boardEntity.setId(boardDTO.getId());
boardEntity.setBoardWriter(boardDTO.getBoardWriter());
boardEntity.setBoardPass(boardDTO.getBoardPass());
boardEntity.setBoardTitle(boardDTO.getBoardTitle());
boardEntity.setBoardContents(boardDTO.getBoardContents());
boardEntity.setBoardHits(boardDTO.getBoardHits());
return boardEntity;
}
public static BoardEntity toSaveEntityWithFile(BoardDTO boardDTO) {
BoardEntity boardEntity = new BoardEntity();
boardEntity.setBoardWriter(boardDTO.getBoardWriter());
boardEntity.setBoardPass(boardDTO.getBoardPass());
boardEntity.setBoardTitle(boardDTO.getBoardTitle());
boardEntity.setBoardContents(boardDTO.getBoardContents());
boardEntity.setBoardHits(0);
boardEntity.setFileAttached(1);
return boardEntity;
}
}
CommentEntity와의 참조관계를 맺기 위해 @OneToMany 어노테이션을 사용하여 참조관계를 맺었다.
2. CommentEntity
package com.icia.board.entity;
import com.icia.board.dto.CommentDTO;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter
@Setter
@Table(name="comment_table")
public class CommentEntity extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length=20, nullable=false)
private String commentWriter;
@Column(length=200, nullable = false)
private String commentContents;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="board_id")
private BoardEntity boardEntity;
public static CommentEntity toSaveEntity(CommentDTO commentDTO,BoardEntity boardEntity) {
CommentEntity commentEntity = new CommentEntity();
commentEntity.setCommentWriter(commentDTO.getCommentWriter());
commentEntity.setCommentContents(commentDTO.getCommentContents());
commentEntity.setBoardEntity(boardEntity);
return commentEntity;
}
}
마찬가지로 BoardEntity와의 참조관계를 위해 @ManyToOne 어노테이션을 사용하여 board_id와 boardDTO의 id를 참조관계를 맺었다.
3. CommentController
package com.icia.board.controller;
import com.icia.board.dto.CommentDTO;
import com.icia.board.service.CommentService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class CommentController {
private final CommentService commentService;
@PostMapping("/comment/save")
@Transactional
public ResponseEntity commentSave(@RequestBody CommentDTO commentDTO) {
List<CommentDTO> commentDTOList = commentService.save(commentDTO);
for(int i=0; i<commentDTOList.size(); i++) {
System.out.println(commentDTOList.get(i));
}
if(commentDTOList.size()!=0) {
return new ResponseEntity<>(commentDTOList,HttpStatus.OK);
}else {
return new ResponseEntity<>(null,HttpStatus.CONFLICT);
}
}
}
댓글입력을 통해 댓글작성자,내용,boardId를 @RequestBody로 CommentDTO로 값을 입력받고 ,
저장을 하는 save 메소드의 매개변수로 전송한다.
4. CommentService
public List<CommentDTO> save(CommentDTO commentDTO) {
// Optional<BoardEntity> boardEntity = boardRepository.findById(commentDTO.getBoard_id());
BoardEntity boardEntity = boardRepository.findById(commentDTO.getBoard_id()).orElseThrow(() -> new NoSuchElementException());
CommentEntity commentEntity = CommentEntity.toSaveEntity(commentDTO,boardEntity);
commentRepository.save(commentEntity);
List<CommentEntity> commentEntityList = boardEntity.getCommentEntityList();
List<CommentDTO> commentDTOList = new ArrayList<>();
for(CommentEntity comment: commentEntityList) {
commentDTOList.add(CommentDTO.toListDTO(comment));
}
return commentDTOList;
}
@Transactional
public List<CommentDTO> findAll(Long boardId) {
BoardEntity boardEntity = boardRepository.findById(boardId).orElseThrow(() -> new NoSuchElementException());
// 1. BoardEntity에서 댓글 목록 가져오기
// List<CommentEntity> commentEntityList = boardEntity.getCommentEntityList();
// 2. CommentRepository에서 가져오기
// select * from comment_table where board_id=?
List<CommentEntity> commentEntityList = commentRepository.findByBoardEntityOrderByIdDesc(boardEntity);
List<CommentDTO> commentDTOList = new ArrayList<>();
commentEntityList.forEach(comment -> {
commentDTOList.add(CommentDTO.toListDTO(comment));
});
return commentDTOList;
}
5. CommentRepository
List<CommentEntity> findByBoardEntityOrderByIdDesc(BoardEntity boardEntity);
Native query 대신 id를 기준으로 내림차순하는 쿼리문을 보내기 위해 메소드명을 수정하였다.
6 BoardDetail
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:replace="component/config :: config"></th:block>
</head>
<body>
<div th:replace="component/header :: header"></div>
<div th:replace="component/nav :: nav"></div>
<div id="section">
<table class="table table-hover">
<tr>
<th>id</th>
<td th:text="${board.id}"></td>
</tr>
<tr>
<th>title</th>
<td th:text="${board.boardTitle}"></td>
</tr>
<tr>
<th>writer</th>
<td th:text="${board.boardWriter}"></td>
</tr>
<tr>
<th>date</th>
<td th:text="${board.createdAt}"></td>
</tr>
<tr>
<th>hits</th>
<td th:text="${board.boardHits}"></td>
</tr>
<tr>
<th>contents</th>
<td th:text="${board.boardContents}"></td>
</tr>
<tr th:if="${board.fileAttached == 1}">
<th>image</th>
<td th:each="fileName: ${board.storedFileName}">
<img th:src="@{|/upload/${fileName}}" width="200" height="200" alt="">
</td>
</tr>
</table>
<div class="container">
<button class="btn btn-primary" onclick="list_req()">목록</button>
<button class="btn btn-warning" onclick="req('update')">수정</button>
<button class="btn btn-danger" onclick="req('delete')">삭제</button>
</div>
<div id="pass-check">
</div>
<div class="container">
<label for="comment-writer">댓글 작성자</label>
<input type="text" id="comment-writer" name="commentWriter" class="form-control" placeholder="댓글 작성자 입력">
<label for="comment-contents">댓글 내용</label>
<input type="text" id="comment-contents" name="commentContents" class="form-control" placeholder="댓글 내용">
<input type="hidden" name="board-id" th:value="${board.id}">
<button class="btn btn-primary" onclick="commentSave()">댓글 작성하기</button>
</div>
<div class="container" id="comment-result">
<h2>댓글 목록</h2>
<table class="table table-hover" th:if="${comment != null}">
<thead>
<tr>
<th>댓글번호</th>
<th>댓글작성자</th>
<th>댓글내용</th>
<th>작성시간</th>
</tr>
</thead>
<tbody>
<tr th:each="comment : ${comment}">
<td th:text="${comment.id}"></td>
<td th:text="${comment.commentWriter}"></td>
<td th:text="${comment.commentContents}"></td>
<td th:text="${comment.createAt}"></td>
</tr>
</tbody>
</table>
<p th:unless="${comment != null}">댓글이 없습니다.</p>
</div>
</div>
<div th:replace="component/footer :: footer"></div>
</body>
<script th:inline="javascript">
const comment_list = (commentList) => {
let result = "";
result += "<h2>댓글 목록</h2>";
result += "<table class='table table-hover'>";
result += "<thead>";
result += "<tr>";
result += "<th>댓글번호</th>";
result += "<th>댓글작성자</th>";
result += "<th>댓글내용</th>";
result += "<th>작성시간</th>";
result += "</tr>";
result += "</thead>";
result += "<tbody>";
commentList.forEach(comment => {
result += "<tr>";
result += "<td>" + comment.id + "</td>";
result += "<td>" + comment.commentWriter + "</td>";
result += "<td>" + comment.commentContents + "</td>";
result += "<td>" + comment.createAt + "</td>";
result += "</tr>";
});
result += "</tbody>";
result += "</table>";
document.getElementById("comment-result").innerHTML = result;
}
const list_req = () => {
location.href = "/board/";
}
const req = (type) => {
console.log("type", type);
document.getElementById("pass-check").innerHTML = "<input type=\"text\" placeholder=\"password\" id=\"password\">\n" +
" <button onclick=\"pass_check('" + type + "')\">확인</button>";
}
const pass_check = (type) => {
console.log("pass_check type 변수 값", type);
const password = document.getElementById("password").value;
const passDB = [[${board.boardPass}]];
const id = [[${board.id}]];
if (password == passDB) {
if (type == "update") {
location.href = "/board/update/" + id;
} else if (type == "delete") {
// location.href = "/board/delete/"+id;
axios({
method: "delete",
url: "/board/" + id
}).then(res => {
location.href = "/board/";
}).catch(err => {
alert("삭제 실패!!");
});
}
} else {
alert("비밀번호 불일치!!");
}
}
const commentSave = () => {
const writer = document.querySelector('input[name="commentWriter"]').value;
const contents = document.querySelector('input[name="commentContents"]').value;
const board_id = [[${board.id}]];
axios.post("/comment/save", {
commentWriter: writer,
commentContents: contents,
board_id: board_id
}).then(res => {
comment_list(res.data);
document.querySelector("#comment-writer").value = "";
document.querySelector("#comment-contents").value = "";
}).catch(err => {
console.log("실패");
});
};
</script>
</html>
처음 입장할때도 댓글목록이 나와야 하고 , 글을 쓸 경우에도 나와야 하기에 Table을 이용해서 구현을 했다.
7. 주의사항
부모 Entity로 자식 Entity를 접근하려면 그 메소드에는 @Transactional 어노테이션이 필수로 사용되야 한다.
Entity 필드값에 _ ( 언더바 ) 를 넣으면 읽을수가 없기 때문에 카멜케이스( ex) boardTitle => O board_title => X ) 로 해야한다.
Entity 클래스에는 @ToString을 사용하지 않는다.