-
Entity와 DTO 분리하기Spring 2023. 1. 25. 15:53
Entity
Entity 클래스는 실제 DB 테이블과 매핑되는 핵심 클래스로, 데이터베이스의 테이블에 존재하는 컬럼들을 필드로 가지는 객체이다.
DB 테이블과 1:1로 매핑되며, 테이블이 가지지 않는 컬럼을 필드로 가져서는 안된다.
Entity는 데이터베이스 영속성(persistent)의 목적으로 사용되는 객체이기 때문에, Entity 클래스가 변경되면 여러 클래스에 영향을 줄 수 있다.
이러한 이유로, Entity에서는 setter 메서드의 사용을 지양해야 한다.
https://jinniedev.tistory.com/6
따로 게시글로 정리해두었지만, 간단히 말하자면
변경되지 않는 인스턴스에 대해서도 setter로 접근이 가능해지기 때문에 객체의 일관성, 안정성을 보장하기 힘들어진다.
Entity에서는 주로 setter 대신 생성자(Constructor) 또는 Builder를 사용한다.
생성자를 이용해서 초기화하는 경우 불변 객체로 활용할 수 있고, 불변 객체로 만들면 데이터를 전달하는 과정에서 데이터가 변조되지 않음을 보장할 수 있다.
Builder를 사용하면 멤버 변수가 많아지더라도 어떤 값을 어떤 필드에 넣는지 코드를 통해 확인할 수 있고, 필요한 값만 넣는 것이 가능하다는 장점이 있다.
DTO
DTO는 계층(Layer) 간 데이터 교환이 이루어질 수 있도록 하는 객체로 JSON serialization과 같은 직렬화에도 사용되는 객체다.
DTO는 원래 DAO(Data Access Object) 패턴에서 유래된 단어로 DAO에서 DB 처리 로직을 숨기고 DTO라는 결괏값을 내보내는 용도로 활용했다.
Controller 같은 클라이언트 단과 직접 마주하는 계층에서는 Entity 대신 DTO를 사용해서 데이터를 교환하며, Controller외에도 여러 레이어 사이에서 DTO를 사용할 수 있지만 주로 View와 Controller 사이에서 데이터를 주고받을 때 활용성이 높다.
Entity와 DTO를 분리하는 이유
Entity와 DTO를 분리하는 이유는 DB와 View 사이의 역할 분리를 위해서이다.
DTO(Data Transfer Object)는 Entity 객체와 달리 각 계층끼리 주고받는 우편물이나 상자의 개념이다.
특히 JPA를 이용하게 되면 Entity 객체는 단순히 데이터를 담는 객체가 아니라 실제 데이터베이스와 관련된 중요한 역할을 하며, 내부적으로 EM(Entity Manager)에 의해 관리되는 객체라는 것을 알 수 있다.
테이블에 매핑되는 정보와 실제 View에서 요청되는 정보가 다를 경우 테이블에 필요한 정보에 맞게 데이터를 변환하는 로직이 필요할 수 있는데 해당 로직이 Entity에 들어가게 되는 것은 깔끔하지 못하다.
DB로 부터 조회된 Entity를 그대로 View로 넘기게 되었을 때 불필요한 정보 및 노출되면 안 되는 정보까지 노출할 수 있고, 이를 막기 위한 로직을 따로 구현해야 한다.
DTO 사용 범위
Client
client -> controller : RequestDTO를 통해서 요청을 받는다.
controller -> client : ResponseDTO로 응답한다.
Controller
controller -> service : 서비스에게 요청받은 RequestDTO를 전달한다.
service -> controller : ResponseDTO 형태로 변환시켜 컨트롤러에게 전달한다.
Service
toEntity 함수를 통해서 Enity로 변환하여 사용하거나 Repository에서 Entity를 얻어내면 응답형식에 맞는 ResponseDTO로 변환한다.
사용 예시
나는 프로젝트 게시글에 댓글 기능을 추가하며, Comment를 Entity와 DTO(Request, Response)로 분리해보았다.
Comment.java
@Entity @Getter @EqualsAndHashCode(of ="id") @NoArgsConstructor @AllArgsConstructor @Builder public class Comment { @Id @GeneratedValue @Column(name = "comment_id") private Long id; private String comment; private String createdDate; private String modifiedDate; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "study_group_id") private StudyGroup studyGroup; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="user_id") private User user; public void update(String comment){ this.comment = comment; } }
CommentRequest.java
@Data @NoArgsConstructor @Builder @AllArgsConstructor public class CommentRequest { private Long id; private String comment; private String createdDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm")); private String modifiedDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm")); private StudyGroup studyGroup; private User user; // Dto -> Entity public Comment toEntity(){ return Comment.builder() .id(id) .comment(comment) .createdDate(createdDate) .modifiedDate(modifiedDate) .studyGroup(studyGroup) .user(user) .build(); } }
CommentResponse.java
@Getter public class CommentResponse { private Long id; private String comment; private String createdDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm")); private String modifiedDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm")); private String nickname; private Long studyGroupId; // Entity -> Dto public CommentResponse(Comment comment){ this.id = comment.getId(); this.comment = comment.getComment(); this.createdDate = comment.getCreatedDate(); this.modifiedDate = comment.getModifiedDate(); this.nickname = comment.getUser().getNickname(); this.studyGroupId = comment.getStudyGroup().getId(); } }
CommentServiceImpl.java
DTO -> Entity
public Long createComment(User user, Long studyGroupId, CommentRequest commentRequest) { StudyGroup studyGroup = studyGroupRepository.findById(studyGroupId).orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다." + studyGroupId)); Comment comment = commentRequest.toEntity(); commentRepository.save(comment); return commentRequest.getId(); }
Entity -> DTO
.comments(studyGroup.getComments().stream().map(CommentResponse::new).collect(Collectors.toList()))
참고
https://tecoble.techcourse.co.kr/post/2021-04-25-dto-layer-scope/
https://w97ww.tistory.com/m/98
'Spring' 카테고리의 다른 글
Open EntityManager In View 필터 (0) 2023.02.14 Spring의 @EventListener (0) 2023.01.30 Setter 사용을 지양해야 하는 이유 (0) 2023.01.19 PasswordEncoder, BcryptPasswordEncoder (0) 2023.01.19 Spring Security : Web Ignore (0) 2023.01.19