
1. view 확인하기

- 필요한 것 : boardId, title, content
boardUserId, username
[컬렉션] replyId, comment, replyUserId, replyUsername 

// orm 사용
id, title, content
user : id
username
replies : id
username
id
username
- json 데이터로 변경 (실제 더미 데이터와는 다름)
너무 굴곡지면 복잡해짐
{
"id":1,
"title":"제목1",
"content":"내용1",
"user" : {
"id":1,
"username":"ssar"
},
"replies" : [
{
"id":1,
"comment":"댓글1",
"user" : {
"id":2,
"username":"cos"
}
},
{
"id":2,
"comment":"댓글2",
"user" : {
"id":3,
"username":"love"
}
}
]
}
- 이렇게까지 굴곡지게 만들 필요가 있을까?
{
"id":1,
"title":"제목1",
"content":"내용1",
"userId":1,
"username":"ssar"
"replies" : [
{
"id":1,
"comment":"댓글1",
"userId":2,
"username":"cos"
},
{
"id":2,
"comment":"댓글2",
"userId":3,
"username":"love"
}
]
}
- 위, 아래 다 되지만 ORM안써서 일자로 적는건 절대 안됨!!
JOIN해서 ORM을 못 쓸 경우 DTO에 담아서 변경해서 보내야함!
FOR문 돌리기도 힘듦
"id":1,
"title":"제목1","content":"내용1",
"id":1,
"username":"ssar"
"id":1,
"comment":"댓글1",
"id":2,
"username":"cos"
"id":2,
"comment":"댓글2",
"id":3,
"username":"love"
2. BoardResponse 에 DetailDTO 만들기
- 컬렉션은 DB에서 조회해서 없으면 빈 배열을 돌려주지 NULL을 반환하지 않음
- 한건은 못찾으면 NULL을 반환함 → 오류 발생
- 응답할 때에는 생성자를 만들어야함
package shop.mtcoding.blog.board;
import lombok.Data;
import shop.mtcoding.blog.reply.Reply;
import shop.mtcoding.blog.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 UserDTO user;
private Boolean isOwner; // 좋아요도 마찬가지
private List<ReplyDTO> replies = new ArrayList<>();
@Data
public class UserDTO {
private int id;
private String username;
public UserDTO(User user) {
this.id = user.getId();
this.username = user.getUsername();
}
}
// 응답할 때에는 생성자를 만들어야함
public DetailDTO(Board board, User sessionUser) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.user = new UserDTO(board.getUser());
this.isOwner = false;
if (sessionUser != null) {
if (sessionUser.getId() == board.getUser().getId()) {
isOwner = true;
}
}
this.replies = board.getReplies().stream().map(reply -> new ReplyDTO(reply, sessionUser)).toList();
}
}
}
3. BoardResponse 에 ReplyDTO 만들기
package shop.mtcoding.blog.board;
import lombok.Data;
import shop.mtcoding.blog.reply.Reply;
import shop.mtcoding.blog.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 UserDTO user;
private Boolean isOwner; // 좋아요도 마찬가지
private List<ReplyDTO> replies = new ArrayList<>();
@Data
public class UserDTO {
private int id;
private String username;
public UserDTO(User user) {
this.id = user.getId();
this.username = user.getUsername();
}
}
@Data
public class ReplyDTO {
private Integer id;
private String comment;
private Integer userId; // 댓글 작성자 아이디
private String username; // 댓글 작성자 이름
private Boolean isOwner;
public ReplyDTO(Reply reply, User sessionUser) {
this.id = reply.getId();
this.comment = reply.getComment(); // lazyloading 발동
this.userId = reply.getUser().getId();
this.username = reply.getUser().getUsername(); // lazyloading 발동
this.isOwner = false;
if (sessionUser != null) {
if (sessionUser.getId() == reply.getUser().getId()) {
isOwner = true;
}
}
}
}
// 응답할 때에는 생성자를 만들어야함
public DetailDTO(Board board, User sessionUser) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.user = new UserDTO(board.getUser());
this.isOwner = false;
if (sessionUser != null) {
if (sessionUser.getId() == board.getUser().getId()) {
isOwner = true;
}
}
this.replies = board.getReplies().stream().map(reply -> new ReplyDTO(reply, sessionUser)).toList();
}
}
}
4. BoardService 에 detail() 수정하기
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog._core.errors.exception.Exception403;
import shop.mtcoding.blog._core.errors.exception.Exception404;
import shop.mtcoding.blog.user.User;
import java.util.List;
@RequiredArgsConstructor
@Service
public class BoardService {
private final BoardJPARepository boardJPARepository;
// board와 isOwner를 응답해야하나 method는 하나밖에 응답할 수 없음 -> 하나의 덩어리로 만들어서 줘야 함
public BoardResponse.DetailDTO detail(int boardId, User sessionUser) {
Board board = boardJPARepository.findByIdJoinUser(boardId)
.orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다"));
return new BoardResponse.DetailDTO(board, sessionUser);
}
}
5. BoardController 에 detail 수정하기
- 총 쿼리가 3번 돌아감
- 댓글 작성자가 2명이라 2번 날아가야하나 default match size로 1번만 날아감
- join → 댓글 select(getuser) → 댓글 select(getid)
- lazy loading할때 단건이 아니면, many to one 관계인 애들은 인쿼리에 둠
→ 보드랑 유저 조인, 댓글 유저 조인 → 셀렉트 2번 →dto 만들어서 들고오기
- 한방 쿼리 / lazy loading이 발동하지 않음 → 다 join 했으니까!
@Query("select b from Board b join fetch b.user left join fetch b.replies r join fetch r.user ru where b.id = :id")
Board findDetail(@Param("id") int id);
package shop.mtcoding.blog.board;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import shop.mtcoding.blog._core.utils.ApiUtil;
import shop.mtcoding.blog.user.User;
import java.util.List;
@RequiredArgsConstructor
@RestController
public class BoardController {
private final HttpSession session;
private final BoardService boardService;
@GetMapping("/api/boards/{id}/detail")
public ResponseEntity<?> detail(@PathVariable Integer id) {
User sessionUser = (User) session.getAttribute("sessionUser");
BoardResponse.DetailDTO respDTO = boardService.detail(id, sessionUser);
return ResponseEntity.ok(new ApiUtil<>(respDTO)); // board에 연관된 객체가 있기에 위험함 / 무한 참조가 일어날 수 있음
}
}
- postman으로 시도하기


Share article