[ 화면을 알아야 DTO를 만든다 ]
회원조회를 했을 때, 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 수정하기 ]
//원래 코드
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}") 바꿔서 테스트 해보자! (인터셉터 걸려있어서...)
이게 바로 DTO !! 실패 할 때든, 성공 할 때든 항상 동일한 형태의 데이터를 줘야한다 -> 그래야 프런트 엔드 쪽에서 파싱하기가 편해짐!
이런식으로 자기 멋대로 주면 안됨. 끝까지! 다 통일해서 줘야함!!
[ 게시글 목록 보기 ]
[ 화면 확인 ]
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다 ]
이렇게 줘야 한다!!
Share article