본문 바로가기
JAVA

Spring Boot 게시판 및 댓글 출력 (thymeleaf, ajax) JPA 양방향 매핑

by fouink 2022. 9. 6.

 

처음에 댓글을 만들 때에는 게시판 테이블을 먼저 불러오고 댓글 테이블을 불러오는 형식이 됐었는데 

이렇게 된 이유가 댓글 테이블에만 게시판 테이블의 연관관계를 걸어(단방향) 게시판 테이블을 조회했을 때 댓글 테이블이 같이

조회가 될 수 없는 구조 였다. 이렇게 만들게 되면 굉장히 비효율적일 것 같았다.. 

 

그래서 게시판 테이블에 댓글 테이블의 연관관계 @OneToMany를 같이 걸어줘서 양방향으로 만든 후 게시판 테이블을 조회했을 때

댓글까지 불러올 수 있었다.

 

 - thymeleaf (board.html)

 - Controller

/**
 * 타임 리프 게시판 상세내용 화면
 * @param model
 * @param board_id
 * @return
 */
@GetMapping("/thymeleaf/get_board")
public String getBoard(
        Model model,
        @RequestParam("board_id") Long board_id) {

    OneBoardResponseDTO oneBoard = boardService.getOneBoard(board_id);
    model.addAttribute("board", oneBoard);

    return "boardDetail";
}

 

boardDetail.html의 글번호 1을 눌렀을 때 매핑되는 컨트롤러 함수이다.

 

 - BoardServiceImpl

/**
 * 게시판 상세내용 응답함수
 * @param board_id
 * @return
 */
@Override
public OneBoardResponseDTO getOneBoard(Long board_id) {

    Optional<BoardEntity> boardEntity = boardRepository.findById(board_id);
    List<CommentResponseDTO> commentResponseDTOList = new ArrayList<>();

    OneBoardResponseDTO oneBoardResponseDTO = OneBoardResponseDTO.builder()
            .board_id(boardEntity.get().getId())
            .title(boardEntity.get().getTitle())
            .content(boardEntity.get().getContent())
            .writer(boardEntity.get().getUserInfoEntity().getNickname())
            .createdDate(boardEntity.get().getCreatedDate())
            .viewCnt(boardEntity.get().getViewCnt())
            .build();

    for(int i=0;i<boardEntity.get().getCommentEntity().size();i++){
        CommentResponseDTO individualCommentResponse = CommentResponseDTO.builder()
                .comment(boardEntity.get().getCommentEntity().get(i).getComment())
                .writer(boardEntity.get().getCommentEntity().get(i).getWriter())
                .createdDate(boardEntity.get().getCommentEntity().get(i).getCreatedDate())
                .build();

        commentResponseDTOList.add(individualCommentResponse);

        System.out.println("개별적인 코멘트 Response"+individualCommentResponse.getComment());
    }

    oneBoardResponseDTO.setCommentResponseDTOList(commentResponseDTOList);

    return oneBoardResponseDTO;
}

 

서비스를 보면 게시판 Repository를 통해 한번만 boardEntity를 호출하는데 유저정보와 코멘트를 같이 찾아올 수 있다

이렇게 되는 이유는 아래와 같이 boradEntity에 연관관계를 걸어줘서 가능한 것이다.

 

 - boardEntity

@Entity
@RequiredArgsConstructor
@Getter
public class BoardEntity extends BaseTimeEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String title;

    @ManyToOne(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
    private UserInfoEntity userInfoEntity;

    @OneToMany(mappedBy = "boardEntity", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
    private List<CommentEntity> commentEntity;


    @Column(nullable = false)
    private String content;

    @Column(nullable = false)
    private int viewCnt;


    @Builder
    public BoardEntity(Long id, String title, UserInfoEntity userInfoEntity, List<CommentEntity> commentEntity, String content, int viewCnt) {
        this.id = id;
        this.title = title;
        this.userInfoEntity = userInfoEntity;
        this.commentEntity = commentEntity;
        this.content = content;
        this.viewCnt = viewCnt;
    }
}

하나의 게시글에 댓글은 여러개가 생길 수 있으므로 댓글 연관 관계의 변수는 List 또는 Set으로 해줘야 될 것 같다.

 

 - CommentEntity

@Entity
@Getter
@RequiredArgsConstructor
public class CommentEntity extends BaseTimeEntity{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String writer;

    @Column(nullable = false)
    private String comment;

    @JsonBackReference
    @ManyToOne(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
    private BoardEntity boardEntity;

    @Builder
    public CommentEntity(Long id, String writer, String comment, BoardEntity boardEntity) {
        this.id = id;
        this.writer = writer;
        this.comment = comment;
        this.boardEntity = boardEntity;
    }
}

 

BoardEntity의 연관관계에 @JsonBackRefernce를 달아준 이유는 위의 BoardServiceImpl에서 게시글을 JPA로 findby를 하게 되면 연관관계까지 전부 다 select를 하게 된다고 했는데 그 과정에서 연관관계의 연관관계 테이블도 호출하게 된다. 그렇게 되면 BoardEntity는 CommentEntity를 호출하게 되고 CommentEntity는 BoardEntity를 연관관계로 가지고 있으니까 다시 BoardEntity를 호출하게되어 오버플로우로 무한 순환참조가 되기때문에 이 현상을 막기 위해 필요한 어노테이션이다.

 

 - boardDetail.html (thymeleaf)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>BoardDetail</title>
</head>
<body>
    <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
    <script>
        const map = {
            team_id: null,
            comment: null
        }

        function requestComment() {
            map.comment = document.getElementById('comment').value;
            map.team_id = document.getElementById('team_id').value;

            console.log(map.comment);
            console.log(map.team_id);

            $.ajax({
                contentType : "application/json",
                url: "/thymeleaf/create/comment",
                type: "POST",
                dataType: JSON,
                data: JSON.stringify(map),
                xhrFields: {
                    withCredentials: true
                }
            }).done(function (res) {
                console.log(res);
            });
        }
    </script>

    <h2 th:text="${board.title}"></h2>
    <strong>작성자 : </strong>
    <span th:text="${board.getWriter()}"></span>
    <hr/>
    <strong>생성 날짜 : </strong>
    <span th:text="${board.getCreatedDate()}"></span>
    <hr/>
    <strong>조회 수 : </strong>
    <span th:text="${board.getViewCnt()}"></span>
    <hr/>
    <strong>내용 : </strong>
    <span th:text="${board.getContent()}"></span>

    <hr/>
    <h3>댓글</h3>
    <table>
    <tr>
        <th>작성자</th>
        <th>내용</th>
        <th>생성 날짜</th>
    </tr>
    <tr th:each="commentList : ${board.getCommentResponseDTOList()}">
        <td th:text="${commentList.getWriter()}"></td>
        <td th:text="${commentList.getComment()}"></td>
        <td th:text="${commentList.getCreatedDate()}"></td>
        </form>
    </tr>
    </table>
    <p></p>
    <input id="team_id" type="hidden"  th:value="${board.getBoard_id()}">
    <input placeholder="댓글 작성" id="comment">
    <input type="button" value="입력" onclick="requestComment()">

</body>
</html>

 

<script>의 ajax는 댓글 생성 요청 api이다.

댓글