공통 응답 DTO 만들기

coding S's avatar
Mar 24, 2024
공통 응답 DTO 만들기

[ 화면을 알아야 DTO를 만든다 ]

notion image
회원조회를 했을 때, User가 들고 있는 모든 정보를 줄 필요는 없고, id, username, email을 주면 된다. 딱 화면에 뿌려지는 이 3개짜리 DTO를 만들어보자!
💡
회원 조회 == 회원 정보 수정
 

[ 회원 조회 ]

[ UserResponse - 회원 조회 DTO ]

★ 엔티티를 dto로 변경시켜야 한다!! ★ DTO new(생성자)를 내가 만들어줘야함 응답!! 은 DTO의 풀 생성자를 만들어야함. 응답!은 생성자를 만들고, 여기다가 무조건!! User를 받아야함
package shop.mtcoding.blog.user; import lombok.Data; public class UserResponse { @Data public static class DTO { private int id; private String username; private String email; public DTO(User user) { this.id = user.getId(); this.username = user.getUsername(); this.email = user.getEmail(); } } }
💡
딱!! 화면에 필요한 DTO를 만들었다! → 엄청나게 편해질 것
💡
이걸 dto로 바꾸면 됨. 제일 기본적인 dto는 이름을 그냥 DTO로 지정한다. (재사용 위해서?)
게시글을 하나 만들건데, 유저랑 댓글 이런 것들을.. 어느 것도 조인하지 않고 딱 걔만 있으면 -> DTO * 제일 일관적으로 많이 쓰는 것들은 그냥 DTO라고 이름 적으면 재사용하는 것도 쉬움 만약 조인하면 DetailDTO 이런 식으로 이름을 지어주면 됨
 

[ UserService 수정하기 ]

notion image
//원래 코드
public UserResponse.DTO 회원조회(int id) { User user = userJPARepository.findById(id) .orElseThrow(() -> new Exception404("회원정보를 찾을 수 없습니다.")); return new UserResponse.DTO(user); //엔티티 생명 종료 }
[ 엔티티 생명 종료 ] -> 유저는 영속화된 존재인데, DTO에 옮기고 버려버리니까 엔티티를 DTO로 변환하는 순간, 이 엔티티는 더 이상 영속성 컨텍스트에 의해 관리되지 않는 '분리 상태(Detached state)'가 된다. 이후에는 엔티티의 변경 사항이 데이터베이스에 자동으로 반영되지 않는다. -> 레이지 로딩이 여기서(서비스에서) 다 끝나버리니까 엔티티 생명이 종료된다.
 

[ UserController 수정하기 ]

@RequiredArgsConstructor @RestController public class UserController { private final UserService userService; private final HttpSession session; @GetMapping("/api/user/{id}") public ResponseEntity<?> userinfo(@PathVariable Integer id) { UserResponse.DTO respDTO = userService.회원조회(id); return ResponseEntity.ok(new ApiUtil(respDTO)); }
💡
respDTO 로 통일하자!
 

[ 테스트 해보기 ]

@GetMapping("/api/users/{id}") -> @GetMapping("users/{id}") 바꿔서 테스트 해보자! (인터셉터 걸려있어서...)
notion image
이게 바로 DTO !! 실패 할 때든, 성공 할 때든 항상 동일한 형태의 데이터를 줘야한다 -> 그래야 프런트 엔드 쪽에서 파싱하기가 편해짐!
notion image
이런식으로 자기 멋대로 주면 안됨. 끝까지! 다 통일해서 줘야함!!
 

[ 게시글 목록 보기 ]

[ 화면 확인 ]

notion image
ID와 TITLE만 받아오면 되겠네
 

[ BoardResponse ]

public class BoardResponse { @Data public static class MainDTO { private int id; private String title; //여기서 getget 거리면 여기서 레이지 로딩이 됨 public MainDTO(Board board) { this.id = board.getId(); this.title = board.getTitle(); } }
Getter를 사용해 Board 엔티티의 필드에 대한 접근이 이루어지면서, 해당 필드가 레이지 로딩(Lazy Loading) 설정이 되어 있다면, 그 시점에서 실제 데이터베이스로부터 필요한 데이터를 로드하는 작업이 발생한다.

만약 User도 받아야 한다면 이 코드

@Data public static class DetailDTO { private Integer id; private String title; private String content; private UserDTO user; private Boolean isOwner; 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; } } } @Data public class UserDTO { private int id; private String username; public UserDTO(User user) { this.id = user.getId(); this.username = user.getUsername(); } } }
 

[ BoardService ]

public List<BoardResponse.MainDTO> 글목록조회() { Sort sort = Sort.by(Sort.Direction.DESC, "id"); List<Board> boardList = boardJPARepository.findAll(sort); return boardList.stream().map(board -> new BoardResponse.MainDTO(board)).toList(); }
return => stream을 사용해 물가에 다시 뿌리는 것. 물가에 뿌려진 board 객체를 생성자에 그대로 집어 넣기만 한다면 return되는 객체는 MainDTO로 바뀌어서 들어온다. 즉, Board객체가 MainDTO로 바뀌어져서 뿌려지는 것!
💡
람다식 안에 있는 board = boardList 안에 있는 board

[ stream을 한 줄로 간단하게 적는 방법! ]

public List<BoardResponse.MainDTO> 글목록조회() { Sort sort = Sort.by(Sort.Direction.DESC, "id"); List<Board> boardList = boardJPARepository.findAll(sort); return boardList.stream().map(BoardResponse.MainDTO::new).toList(); }
💡
이렇게 쓰는게 어려우면 람다를 사용하자 !
 

[ BoardController ]

@RequiredArgsConstructor @RestController public class BoardController { private final BoardService boardService; private final HttpSession session; // TODO : 글 목록조회 API 필요 -> @GetMapping("/"); @GetMapping("/") public ResponseEntity<?> main() { List<BoardResponse.MainDTO> respDTO = boardService.글목록조회(); return ResponseEntity.ok(new ApiUtil(respDTO)); }

[ 이 DTO는 영속 객체가 아니니까 레이지 로딩이 터지지 않는다 ]

BoardResponse.MainDTO 객체는 Board 엔티티의 데이터를 복사하여 가지고 있지만, Board 엔티티의 레이지 로딩 필드에 대한 실제 데이터 로딩 작업은 BoardResponse.MainDTO 객체를 생성하기 전, 즉 엔티티를 DTO로 변환하는 과정에서 필요한 데이터를 모두 로드한 뒤에 이루어진다. 변환 과정에서 필요한 데이터가 로드되면, DTO는 그 데이터의 복사본을 가지고 있게 되며, 이후 DTO에서는 레이지 로딩과 관련된 어떠한 데이터베이스 접근도 발생하지 않는다.
💡
결론! DTO를 만드는 과정에서 필요한 모든 데이터를 미리 불러와서 담아두기 때문에, DTO를 사용자에게 보낸 후에는 레이지 로딩이 일어날 일이 없다!
 

[ 이게 DTO다 ]

notion image
💡
이렇게 줘야 한다!!
Share article

codingb