Spring Boot

[게시판 만들기 (11)] ajax 댓글 처리

서윤-정 2024. 1. 15. 14:01

 

마지막 댓글 부분이다.

댓글은 ajax를 이용해서 한다.

ajax를 사용하면 비동기적으로 통신하기 때문에 페이지를 새로 고침하지 않아도

데이터를 서버로부터 받아오거나 서버에 데이터를 보낼 수 있다.

또 전체 페이지를 다시 로드하지 않고도 특정 부분만 업데이트가 가능하다.

 

 

 

 

 

 

 

 

 

 

detail.html 에 댓글 관련 코드를 추가한다. 

board.id를 받아오는 것이 중요하다.

 

[ detail.html ]

<!-- 댓글 작성 부분 -->
<div id="comment-write">
    <input type="text" id="commentWriter" placeholder="작성자">
    <input type="text" id="commentContents" placeholder="내용">
    <button id="comment-write-btn" onclick="commentWrite()">댓글작성</button>
</div>

<!-- 댓글 출력 부분 -->
<div id="comment-list">
    <table>
        <tr>
            <th>댓글번호</th>
            <th>작성자</th>
            <th>내용</th>
            <th>작성시간</th>
        </tr>
        <tr th:each="comment: ${commentList}">
            <td th:text="${comment.id}"></td>
            <td th:text="${comment.commentWriter}"></td>
            <td th:text="${comment.commentContents}"></td>
            <td th:text="${comment.commentCreatedTime}"></td>
        </tr>
    </table>
</div>

</body>
<script th:inline="javascript">

    const commentWrite = () => {
        const writer = document.getElementById("commentWriter").value;
        const contents = document.getElementById("commentContents").value;
        console.log("작성자: ", writer);
        console.log("내용: ", contents);
        const id = [[${board.id}]];
        $.ajax({
            // 요청방식: post, 요청주소: /comment/save, 요청데이터: 작성자, 작성내용, 게시글번호
            type: "post",
            url: "/comment/save",
            data: {
                "commentWriter": writer,
                "commentContents": contents,
                "boardId": id
            },
            success: function (res) {
                console.log("요청성공", res);
                let output = "<table>";
                output += "<tr><th>댓글번호</th>";
                output += "<th>작성자</th>";
                output += "<th>내용</th>";
                output += "<th>작성시간</th></tr>";
                for (let i in res) {
                    output += "<tr>";
                    output += "<td>" + res[i].id + "</td>";
                    output += "<td>" + res[i].commentWriter + "</td>";
                    output += "<td>" + res[i].commentContents + "</td>";
                    output += "<td>" + res[i].commentCreatedTime + "</td>";
                    output += "</tr>";
                }
                output += "</table>";
                document.getElementById('comment-list').innerHTML = output;
                document.getElementById('commentWriter').value = '';
                document.getElementById('commentContents').value = '';
            },
            error: function (err) {
                console.log("요청실패", err);
            }
        });
    }

 

 

 

 

 

 

 

 

 

 

 

 

 

CommentController 클래스를 생성한다.

 

[ CommentController ]

package test.SpringBootBoard.board.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import test.SpringBootBoard.board.dto.CommentDTO;
import test.SpringBootBoard.board.service.CommentService;

import java.util.List;

@Controller
@RequiredArgsConstructor
@RequestMapping("/comment")
public class CommentController {
    private final CommentService commentService;
    @PostMapping("/save")
    public ResponseEntity save(@ModelAttribute CommentDTO commentDTO) {
        System.out.println("commentDTO = " + commentDTO);
        Long saveResult = commentService.save(commentDTO);
        if (saveResult != null) {
            List<CommentDTO> commentDTOList = commentService.findAll(commentDTO.getBoardId());
            return new ResponseEntity<>(commentDTOList, HttpStatus.OK);
        } else {
            return new ResponseEntity<>("해당 게시글이 존재하지 않습니다.", HttpStatus.NOT_FOUND);
        }
    }

}

 

 

public ResponseEntity save(@ModelAttribute CommentDTO commentDTO) 

클라이언트로부터 받은 데이터는 CommentDTO 객체로 매핑된다.

 

 

Long saveResult = commentService.save(commentDTO);

CommentService를 사용하여 댓글을 저장하고, 결과로 저장된 댓글의 ID 값을 받아온다.

 

if (saveResult != null) 

저장 결과가 null이 아닌 경우(즉, 댓글이 성공적으로 저장된 경우) 해당 블록을 실행한다.

 

List<CommentDTO> commentDTOList = commentService.findAll(commentDTO.getBoardId());

댓글이 성공적으로 저장되면 해당 게시글에 대한 모든 댓글을 가져온다.

 

return new ResponseEntity<>(commentDTOList, HttpStatus.OK);

댓글 목록을 HttpStatus.OK(200) 상태 코드와 함께 응답한다.

 

return new ResponseEntity<>("해당 게시글이 존재하지 않습니다.", HttpStatus.NOT_FOUND);

저장 결과가 null 인 경우 (해당 게시글이 존재하지 않는 경우) HttpStatus.NOT_FOUND(404) 상태 코드와 함께 응답한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

CommentService 클래스도 생성한다. save 메서드와 findAll 메서드를 추가한다.

 

[ CommentService ]

package test.SpringBootBoard.board.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import test.SpringBootBoard.board.dto.CommentDTO;
import test.SpringBootBoard.board.entity.BoardEntity;
import test.SpringBootBoard.board.entity.CommentEntity;
import test.SpringBootBoard.board.repository.BoardRepository;
import test.SpringBootBoard.board.repository.CommentRepository;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class CommentService {
    private final CommentRepository commentRepository;
    private final BoardRepository boardRepository;

    public Long save(CommentDTO commentDTO) {
        /* 부모엔티티(BoardEntity) 조회 */
        Optional<BoardEntity> optionalBoardEntity = boardRepository.findById(commentDTO.getBoardId());
        if (optionalBoardEntity.isPresent()) {
            BoardEntity boardEntity = optionalBoardEntity.get();
            CommentEntity commentEntity = CommentEntity.toSaveEntity(commentDTO, boardEntity);
            return commentRepository.save(commentEntity).getId();
        } else {
            return null;
        }
    }

    public List<CommentDTO> findAll(Long boardId) {
        BoardEntity boardEntity = boardRepository.findById(boardId).get();
        List<CommentEntity> commentEntityList = commentRepository.findAllByBoardEntityOrderByIdDesc(boardEntity);
        /* EntityList -> DTOList */
        List<CommentDTO> commentDTOList = new ArrayList<>();
        for (CommentEntity commentEntity: commentEntityList) {
            CommentDTO commentDTO = CommentDTO.toCommentDTO(commentEntity, boardId);
            commentDTOList.add(commentDTO);
        }
        return commentDTOList;
    }

}

 

 

<save 메서드>

Optional<BoardEntity> optionalBoardEntity = boardRepository.findById(commentDTO.getBoardId());

주어진 boardId로 BoardEntity를 조회한다.

 

if (optionalBoardEntity.isPresent()) 

조회된 BoardEntity가 존재하는 경우 해당 블록을 실행한다.

 

BoardEntity boardEntity = optionalBoardEntity.get();

조회된 BoardEntity를 가져온다.

 

CommentEntity commentEntity = CommentEntity.toSaveEntity(commentDTO, boardEntity);

CommentEntity 객체를 생성하고, CommentDTO 및 BoardEntity의 정보를 사용하여 초기화한다.

 

return commentRepository.save(commentEntity).getId();

생성된 CommentEntity를 저장하고, 저장된 댓글의 ID 값을 반환한다.

 

 

<findAll 메서드> 특정 게시글에 대한 모든 댓글을 조회하는 메서드이다.

BoardEntity boardEntity = boardRepository.findById(boardId).get();

주어진 boardId로 BoardEntity를 조회한다.

 

List<CommentEntity> commentEntityList = commentRepository.findAllByBoardEntityOrderByIdDesc(boardEntity);

해당 게시글에 대한 댓글 목록을 생성일자(ID 필드) 역순으로 조회한다.

 

List<CommentDTO> commentDTOList = new ArrayList<>();

댓글 목록을 저장할 CommentDTO 객체의 리스트를 생성한다.

 

for (CommentEntity commentEntity: commentEntityList) {

각 댓글에 대해 반복하면서 DTO로 변환하고 리스트에 추가한다.

 

 

 

 

 

 

 

 

 

 

 

 

CommentDTO 도 생성해준다.

 

[ CommentDTO ]

package test.SpringBootBoard.board.dto;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import test.SpringBootBoard.board.entity.CommentEntity;

import java.time.LocalDateTime;

@Getter
@Setter
@ToString
public class CommentDTO {
    private Long id;
    private String commentWriter;
    private String commentContents;
    private Long boardId;
    private LocalDateTime commentCreatedTime;

    public static CommentDTO toCommentDTO(CommentEntity commentEntity, Long boardId) {
        CommentDTO commentDTO = new CommentDTO();
        commentDTO.setId(commentEntity.getId());
        commentDTO.setCommentWriter(commentEntity.getCommentWriter());
        commentDTO.setCommentContents(commentEntity.getCommentContents());
        commentDTO.setCommentCreatedTime(commentEntity.getCreatedTime());
        commentDTO.setBoardId(boardId);
        return commentDTO;
    }

}

 

 

 

 

 

 

 

 

 

 

 

 

CommentRepostiory 도 생성해준다.

 

[ CommentRepostiory

package test.SpringBootBoard.board.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import test.SpringBootBoard.board.entity.BoardEntity;
import test.SpringBootBoard.board.entity.CommentEntity;

import java.util.List;

public interface CommentRepository extends JpaRepository<CommentEntity, Long> {
    // select * from comment_table where board_id=? order by id desc;
    List<CommentEntity> findAllByBoardEntityOrderByIdDesc(BoardEntity boardEntity);
}

 

 List<CommentEntity> findAllByBoardEntityOrderByIdDesc(BoardEntity boardEntity);

BoardEntity에 속한 모든 댓글들을 가져오는데 사용된다. 

findAllByBoardEntity: BoardEntity에 속한 모든 엔티티를 찾는다.

OrderByIdDesc: id 필드를 기준으로 내림차순으로 정렬한다.

 

 

 

 

 

 

 

 

 

 

CommentEntity 도 생성한다.

 

[ CommentEntity ]

@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
    private String commentContents;

    /* Board:Comment = 1:N */
    @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;
    }
}

 

저번에 File 테이블과 비슷하다.

 

 

 

 

 

 

 

 

 

BoardEntity에도 댓글 관련 코드를 추가한다.

 

[ BoardEntity ]

@OneToMany(mappedBy = "boardEntity", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY)
private List<CommentEntity> commentEntityList = new ArrayList<>();

 

 

 

 

 

 

 

 

 

BoardController 도 수정해준다.

 

[ BoardController ]

private final CommentService commentService;

 

@GetMapping("/{id}")
public String findById(@PathVariable Long id, Model model,
                       @PageableDefault(page=1) Pageable pageable){
    /*
        해당 게시글의 조회수를 하나 올리고
        게시글 데이터를 가져와서 detail.html에 출력
     */
    boardService.updateHits(id);
    BoardDTO boardDTO = boardService.findById(id);
    /* 댓글 목록 가져오기 */
    List<CommentDTO> commentDTOList = commentService.findAll(id);
    model.addAttribute("commentList", commentDTOList);

    model.addAttribute("board", boardDTO);
    model.addAttribute("page", pageable.getPageNumber());
    return "detail";
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

실행해보좌

 

 

댓글 폼에 댓글을 입력한다.

 

 

 

 

 

 

 

짠 생성 완

비동기이기 때문에 페이지가 로드되지 않아도 바로 생성되는 것을 확인할 수 있다.

 

 

 

 

 

 

콘솔을 보면 잘 찍힌걸 알 수 있다. 

 

 

 

 

 

 

두번째 댓글도 작성

 

 

 

 

2개가 됐다. 

 

 

 

 

 

 

 

디비버에서도 잘 들어온걸 확인할 수 있다.