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을 사용하지 않는다.