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
이처럼 데이터가 자동으로 매핑되는 이유는 엔티티 클래스에서 양방향 매핑이 설정되어 있기 때문이다. 이를 통해 연관된 데이터들을 직관적으로 불러올 수 있고, 따로 데이터를 변환하거나 매핑하는 코드가 필요하지 않다.

이대로 데이터를 뿌리면 replies 를 꺼내기 위해 스크립트를 많이 짜게됨.

데이터 매핑을 위한 DTO 생성

댓글도 수정, 삭제 할 것이기 때문에
isOwner
추가하여 작성자 일치 여부 확인코드
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