BoardService - 게시글 상세보기 (DTO 사용)

coding S's avatar
Mar 20, 2024
BoardService - 게시글 상세보기 (DTO 사용)

[ 게시글 상세보기 ]

notion image
💡
수정 전 코드. 드래그 한 해당 부분이 서비스에 있어야 함. 앗 그런데!! isOwner이라고 쓰지 말고, isBoardOwner 이런 식으로 구분해서 써주자! 사유 → isReplyOwner 등등 만들어야 할 오너들이 대기 중
 

[ BoardService ]

// board와 isOwner를 응답해야 하는데, 메소드는 1개밖에 응답하지 못함. -> 하나의 덩어리(DTO)로 만들어서 줘야함 public Board 글상세보기(Integer boardId, User sessionUser) { Board board = boardJPARepository.findByIdJoinUser(boardId) .orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다.")); // 로그인을 하고 게시글의 주인이면 isOwner가 true가 된다 ! boolean isOwner = false; if(sessionUser != null){ if(sessionUser.getId() == board.getUser().getId()){ isOwner = true; } } board.setOwner(isOwner); return board; } }
💡
보드랑 isOwner를 한방에 합쳐서 보내는 게 핵심! 그런데 return을 return board, isOwner 로 할 수가 없다… → 이 둘을 담아서 옮길 만한 DTO 가 필요하다
 

isOwner까지 주는 완벽한 DTO 만들기! (핵심! 해보자!) ★

[ BoardResponse ]

notion image
💡
데이터 있는 걸 하나 하나씩 옮기는 것이다. user은 UserDTO 타입 (굴곡진 테이터) 으로 받아온다. (왜 그런지는 아래에)

[ Board객체랑 BoardResponse(DTO) 다른것 3가지 ]

1. 화면에 보이는 것만 전달함 (view용 DTO) 2. User 객체를 안 씀. -> 이것도 화면에 필요한 것만 주기 위함!!
 

[ isOwner까지 주는 완벽한 DTO 만들기! ]

[ BoardResponse ]

package shop.mtcoding.blog.board; import jakarta.persistence.FetchType; import jakarta.persistence.ManyToOne; import lombok.Data; import org.hibernate.Session; import org.hibernate.annotations.CreationTimestamp; import shop.mtcoding.blog.user.User; import java.sql.Timestamp; public class BoardResponse { @Data public static class DetailDTO { private Integer id; private String title; private String content; private UserDTO user; private Boolean isOwner; //Board를 그대로 받아오니까 board.getId로 접근이 가능하구나 //DetailDTO는 초기화될 때 받아온 값? 조회된 값?으로 태어나겠군 public DetailDTO(Board board, User sessionUser) { this.id = board.getId(); this.title = board.getTitle(); this.content = board.getContent(); //지금 Board에 User 객체를 필드로 쓰고 있기 때문에 board.getUser로 가져옴 this.user = new UserDTO(board.getUser()); //isOwner 기본값은 false로 설정해둠 this.isOwner = false; if(sessionUser != null){ if(sessionUser.getId() == board.getUser().getId()){ this.isOwner = true; } } } @Data public class UserDTO { private int id; private String username; public UserDTO(User user) { this.id = user.getId(); this.username = user.getUsername(); } } } }
💡
이게 하나의 화면을 위한 DTO. 화면을 위한 로직 변수명이 충돌날 수 있기 때문에 UserDTO 클래스를 따로 빼서 만들어준 듯 하다.
this.user = new UserDTO(board.getUser()); -> board.getUser()를 통해 Board 객체에 연결된 User 객체를 가져옴 (Board 객체 내에는 User 객체가 필드로 존재) 그걸 public UserDTO(User user) 생성자에 전달하여 UserDTO 객체를 생성. 생성된 UserDTO 객체는 DetailDTO의 user 필드에 저장되어, 게시글의 작성자 정보를 포함
 

[ BoardService ]

// board, isOwner public BoardResponse.DetailDTO 글상세보기(int boardId, User sessionUser) { Board board = boardJPARepository.findByIdJoinUser(boardId) .orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다")); // 로그인을 하고, 게시글의 주인이면 isOwner가 true가 된다. return new BoardResponse.DetailDTO(board, sessionUser); }
💡
isOwner로직을 DTO에서 하도록 넘겨버리니 Service도 깔끔!
💡
타입 : BoardResponse.DetailDTO
 

[ BoardController ]

@GetMapping("/board/{id}") public @ResponseBody BoardResponse.DetailDTO detail(@PathVariable Integer id, HttpServletRequest request) { User sessionUser = (User) session.getAttribute("sessionUser"); return boardService.글상세보기(id, sessionUser); // request.setAttribute("isOwner", isOwner); // request.setAttribute("board", board); // return "board/detail"; }
💡
내부 클래스 만들어서 DTO에 옮기는 과정
 

DTO를 사용하면 이런 데이터가 나온다.

notion image
이걸 보고... 프런트 엔드가 와~~~ 데이터다~~~ 화면에 맞는 데이터다~~~ 하고 뿌림 원래는 컨트롤러에서 isOwner를 썼는데, 그러지 않고! 서비스에서 한방에 와악 넘겨주려고 dto에서 담아서 한꺼번에 처리함!
 

[ 이게 DTO!! 이게 DetailDTO !! (중요하니까 크게 보기) ]

notion image
이렇게 필요한 것만 딱! 뽑는 것!! 이렇게 줘야한다!! -> 이걸 잘 만들어야 프런트 엔드가 좋아함 id는 (보이지 않아도) 무조건 주는 것! -> user객체 안에 있으니까 이건 user.id로 뽑아 쓸 수 있음.
 

이런 식으로 주면 안 된다. 극혐! 최악!

@GetMapping("/board/{id}") public Board detail(@PathVariable Integer id, HttpServletRequest request) { User sessionUser = (User) session.getAttribute("sessionUser"); return boardService.글상세보기(id, sessionUser); // // request.setAttribute("isOwner", isOwner); // request.setAttribute("board", board); // return "board/detail"; }
notion image
💡
Board 객체를 받아서 오니까 이렇게 모든 데이터가 다 날아온다. 극혐!! isOwner도 못넣고!! 통신으로 패스워드가 날아오다니?!
 

notion image
💡
고럼 이제 이렇게 하면은.. 된다.
 
 

DTO가 너무 어렵나요? 그러면 다른 버전을 해봅시다

notion image
💡
Board를 리턴하면 안된다고 하지 않았어요? 네.. 그랬죠 그랬는데... 여러분들이 어려워하니까 지금은 일단 이렇게 해봐요
 

[ Board ]

notion image
💡
@Transient 어노테이션 사용

[ @Transient 어노테이션 ]

특정 필드를 영속성 처리에서 제외하고 싶을 때 사용. 즉, 해당 필드는 데이터베이스 테이블에 매핑되지 않기 때문에 데이터베이스 테이블에 해당 필드에 대한 컬럼이 생성되지 않는다.
 

[ BoardController ] - (/board/{id}/v2 보기)

notion image
 

내가 안 뽑을 건 Ignore로 무시하자! (또 다른 방법)

notion image
jsonIgnore은 파싱할 때 애를 무시하는것 -> 클라이언트로부터 전달받거나 클라이언트에게 전달할 때 해당 필드의 데이터 무시 JsonIgnoreProperties는 객체에 있는 필드를 무시하는 것 -> 지정된 필드 목록 전체를 한 번에 무시하고 싶을 때
 

[ 결과는? ]

notion image
💡
DTO랑 똑같이 나온다~ 그치만 추천하지 않는다
 

화면 확인 (결론 : DTO가 최고)

notion image
💡
DTO 쓴 게 제일 예쁨!!
 

코드 정리 → detail.mustache랑 v2만 보면 될 듯 (거의 필요 없음)

[ Board ]

package shop.mtcoding.blog.board; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import shop.mtcoding.blog.user.User; import java.sql.Timestamp; @NoArgsConstructor @Data @Table(name = "board_tb") @Entity public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String title; private String content; //@JoinColumn(name = "user_id") @ManyToOne(fetch = FetchType.LAZY) private User user; // db -> user_id @CreationTimestamp // pc -> db (날짜주입) private Timestamp createdAt; @Transient // 테이블 생성이 안됨 private boolean isOwner; @Builder public Board(Integer id, String title, String content, User user, Timestamp createdAt) { this.id = id; this.title = title; this.content = content; this.user = user; this.createdAt = createdAt; } }
 

[ BoardService ]

// board, isOwner public Board 글상세보기(int boardId, User sessionUser) { Board board = boardJPARepository.findByIdJoinUser(boardId) .orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다")); boolean isOwner = false; if(sessionUser != null){ if(sessionUser.getId() == board.getUser().getId()){ isOwner = true; } } board.setOwner(isOwner); return board; }
 

[ BoardController ]

@GetMapping("/board/{id}") public String detail(@PathVariable Integer id, HttpServletRequest request) { User sessionUser = (User) session.getAttribute("sessionUser"); Board board = boardService.글상세보기(id, sessionUser); request.setAttribute("board", board); return "board/detail"; }
 

[ detail.mustache ]

{{#board.isOwner}} <!-- 수정삭제버튼 --> <div class="d-flex justify-content-end"> <a href="/board/{{board.id}}/update-form" class="btn btn-warning me-1">수정</a> <form action="/board/{{board.id}}/delete" method="post"> <button class="btn btn-danger">삭제</button> </form> </div> {{/board.isOwner}}
 

[ V2는 여러분들이 해보세요~ ]

notion image
💡
@ResponseBody만 걸면 되어요
 
 
Share article

codingb