SELECT bt.id, bt.title, bt.content, ut.username, rt.comment, rut.username
FROM board_tb bt
INNER JOIN user_tb ut ON bt.user_id = ut.id
LEFT OUTER JOIN reply_tb rt on bt.id = rt.board_id
LEFT OUTER JOIN user_tb rut on rut.id = rt.user_id
where bt.id = 5;
쿼리문은 작성 완료 함.
데이터를 넘길 DTO 를 만들 때
int id;
String title;
String content;
List<reply> replies;
이렇게 한 게시글에 속한 댓글을 컬렉션으로 주는게 좋다.
이것을 JPQL 쿼리로 작성하면 이러하다
@Query("select b from Board b join fetch b.user left join fetch b.replies r left join fetch r.user where b.id=:id")
    Optional<Board> mFindByIdWithReply(@Param("id")int id);
- 쿼리 테스트
    @Test
    public void mFindByIdWithReply_test(){
        Board board = boardRepository.mFindByIdWithReply(5).get();
        System.out.println(board.getReplies().get(0).getComment());
    }필요한 데이터만 전달할 때 
stream 같은 걸 사용해 수동으로 매핑할 필요 없이, 퍼시스턴스 컨텍스트가 자동으로 매핑해준다.즉, 
board 엔티티는 다음과 같은 구조를 가지고 있다:- board
- user
- reply
이처럼 데이터가 자동으로 매핑되는 이유는 엔티티 클래스에서 양방향 매핑이 설정되어 있기 때문이다. 이를 통해 연관된 데이터들을 직관적으로 불러올 수 있고, 따로 데이터를 변환하거나 매핑하는 코드가 필요하지 않다.
데이터 매핑을 위한 DTO 생성
코드
package org.example.springv3.board;
import lombok.Data;
import org.example.springv3.reply.Reply;
import org.example.springv3.user.User;
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 Boolean isOwner;
        private String username;
        // 댓글들
        private List<ReplyDTO> replies = new ArrayList<>(); //엔티티 말고 DTO 를 넣어야함. 엔티티 넣으면 레이지로딩? 나옴
        public DetailDTO(Board board, User sessionUser) {
            this.id = board.getId();
            this.title = board.getTitle();
            this.content = board.getContent();
            this.isOwner = false;
            if (sessionUser != null) {
                if (board.getUser().getId() == sessionUser.getId()) {
                    isOwner = true; // 권한체크
                }
            }
            this.username = board.getUser().getUsername();
            for (Reply reply : board.getReplies()) {
                replies.add(new ReplyDTO(reply, sessionUser));
            }
        }
        @Data
        class ReplyDTO {
            private Integer id;
            private String comment;
            private String username;
            private Boolean isOwner;
            public ReplyDTO(Reply reply, User sessionUser) {
                this.id = reply.getId();
                this.comment = reply.getComment();
                this.username = reply.getUser().getUsername();
                this.isOwner = false;
                if (sessionUser != null) {
                    if (reply.getUser().getId() == sessionUser.getId()) {
                        isOwner = true; // 권한체크
                    }
                }
            }
        }
    }
}
{
    "id": 5,
    "title": "제목5",
    "content": "내용5",
    "isOwner": false,
    "username": "cos",
    "replies": [
        {
            "id": 1,
            "comment": "댓글1",
            "username": "ssar",
            "isOwner": false
        },
        {
            "id": 2,
            "comment": "댓글2",
            "username": "ssar",
            "isOwner": false
        },
        {
            "id": 3,
            "comment": "댓글3",
            "username": "cos",
            "isOwner": false
        }
    ]
} 데이터 결과는 이러하다.
이제 템플릿 엔진만 수정하면 완료.
전체 코드
{{>layout/header}}
 
<div class="container p-5">
    <!-- 수정삭제버튼 -->
    {{#model.isOwner}}
        <div class="d-flex justify-content-end">
            <a href="/api/board/{{model.id}}/update-form" class="btn btn-warning me-1">수정</a>
            <form action="/api/board/{{model.id}}/delete" method="post">
                <button class="btn btn-danger">삭제</button>
            </form>
        </div>
    {{/model.isOwner}}
    <div class="d-flex justify-content-end">
            <b>작성자</b> : {{model.username}}
    </div>
    <!-- 게시글내용 -->
    <div>
        <h2><b>{{model.title}}</b></h2>
        <hr/>
        <div class="m-4 p-2">
            {{{model.content}}}
        </div>
    </div>
</div>
<!-- 댓글 -->
<div class="card mt-3">
    <!-- 댓글등록 -->
    <div class="card-body">
        <form action="/reply/save" method="post">
            <input type="hidden" name="boardId" value="">
            <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">
        {{#model.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">{{username}}</div>
                    <div>{{comment}}</div>
                </div>
                {{#isOwner}}
                    <form action="#" method="post">
                        <button class="btn">🗑</button>
                    </form>
                {{/isOwner}}
            </div>
        {{/model.replies}}
    </div>
</div>
{{>layout/footer}}

결과

ssar 로 로그인 하였기 때문에 본인 댓글만 휴지통 이모지가 보인다.
다음 포스팅에서는 AJAX통신을 활용하여 댓글 수정 삭제를 구현할 예정이다.
사용된 DTO 데이터 관리 방법 
SpringBoot 블로그 만들기 - v3 시리즈 1. https://inblog.ai/hj/v3-시작-27809 (개발환경 설정 및 post 맨 이용한 api 테스트) 2. https://inblog.ai/hj/v3-springboot-블로그-만들기-2-28708 (댓글 엔티티 생성 및 양방향 매핑) 3. https://inblog.ai/hj/v3-springboot-블로그-만들기-3-28793 (댓글 조회하기 완료)
Share article



