47. 응답 DTO - 글 상세보기 만들기v7

송민경's avatar
Mar 21, 2024
47. 응답 DTO - 글 상세보기 만들기v7

1. view 확인하기

notion image
  • 필요한 것 : 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으로 시도하기
notion image
notion image
Share article

vosw1