Dto⇄entity
Dto⇄entity
DTO ↔ Entity 변환
DTO를 받는 Entity 생성자란?
1
2
3
4
5
public Registry(RegistryDto registryDto) {
this.title = registryDto.getTitle();
this.main = registryDto.getMain();
this.nickname = registryDto.getNickname();
}
위 코드는 RegistryDto를 인자로 받아 Registry(Entity)를 생성하는 생성자이다.
RegistryDto기반 생성자- DTO → Entity 변환 생성자
라고 부를 수 있다.
그런데 왜 이 코드는 검색해보면 사용하지 않는 곳이 많았다.
Entity가 DTO를 의존하게 되기 때문이다.
왜 Entity가 DTO를 의존하면 안 될까?
일반적인 레이어 구조는 다음과 같다.
1
2
3
4
5
6
7
Controller
↓
Service
↓
Repository
↓
Entity (최하위)
- DTO는 Controller / Service 계층에서 사용
- Entity는 DB와 직접 매핑되는 가장 하위 계층
Entity 생성자가 DTO를 받는다면 Entity → DTO 의존 발생
하위 레이어(Entity)가 상위 레이어(DTO)를 알고 있는 구조가 되면서 레이어 분리 원칙에 어긋난다
해결 방법 : DTO가 Entity를 만들자
- Entity에서 DTO 받지 않기
- DTO에서 Entity를 생성
이때 사용하는 메서드가 바로 toEntity() 이다.
DTO → Entity : toEntity()
Registry DTO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RegistryDto {
private String nickname;
private String title;
private String main;
// dto → entity
public Registry toEntity() {
return Registry.builder()
.nickname(nickname)
.title(title)
.main(main)
.build();
}
}
- DTO가 자신의 데이터를 사용해서 Entity를 직접 생성한다.
- Entity는 DTO의 존재를 모른다
Registry Entity (생성자 제거)
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Registry extends Timestamped {
@OneToMany(mappedBy = "registry")
@JsonIgnore
private List<Comment> comments = new ArrayList<>();
// RegistryDto를 받는 생성자 제거
// public Registry(RegistryDto registryDto) {
// this.title = registryDto.getTitle();
// this.main = registryDto.getMain();
// this.nickname = registryDto.getNickname();
// }
}
Service 코드 변경
변경 전
1
2
3
4
5
public Registry setUpload(RegistryDto registryDto) {
Registry registry = new Registry(registryDto);
registryRepository.save(registry);
return registry;
}
변경 후
1
2
3
public Registry setUpload(RegistryDto registryDto) {
return registryRepository.save(registryDto.toEntity());
}
Service 코드가 단순해지고 책임 분리가 명확해졌다.
Comment
Comment도 동일하다.
Comment DTO
1
2
3
4
5
6
7
8
9
10
11
12
13
public class CommentDto {
private String nickname;
private String comment;
private Long registryIdx;
// dto → entity
public Comment toEntity() {
return Comment.builder()
.nickname(nickname)
.comment(comment)
.build();
}
}
Comment Entity (DTO 생성자 제거)
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Comment extends Timestamped {
@ManyToOne
@JoinColumn(name = "registry_id", nullable = false)
private Registry registry;
// DTO 생성자 제거
// public Comment(CommentDto commentDto) {
// this.nickname = commentDto.getNickname();
// this.comment = commentDto.getComment();
// Long registryIdx = commentDto.getRegistryIdx();
// }
}
Service 변경 전 → 후
변경 전
1
2
3
4
5
6
7
8
9
10
11
12
public Comment setComment(CommentDto commentDto) {
Registry registryId = registryRepository.getById(commentDto.getRegistryIdx());
Comment comment = Comment.builder()
.comment(commentDto.getComment())
.nickname(commentDto.getNickname())
.registry(registryId)
.build();
commentRepository.save(comment);
return comment;
}
변경 후
1
2
3
4
5
6
public Comment setComment(CommentDto commentDto) {
Registry registry = registryRepository.getById(commentDto.getRegistryIdx());
Comment comment = commentDto.toEntity();
comment.setRegistry(registry);
return commentRepository.save(comment);
}
객체를 DTO에 넘기기
DTO refactoring
1
2
3
4
5
6
7
public Comment toEntity(Registry registry) {
return Comment.builder()
.nickname(nickname)
.comment(comment)
.registry(registry)
.build();
}
Service refactoring
1
2
3
4
5
public Comment setComment(CommentDto commentDto) {
Registry registry = registryRepository.findById(commentDto.getRegistryIdx())
.orElseThrow();
return commentRepository.save(commentDto.toEntity(registry));
}
Service가 비즈니스 흐름만 담당하게 된다.
Entity → DTO : of()
조회(Response)용 변환
왜 Entity를 그대로 반환하면 안 될까?
Entity에는 연관관계, 내부 컬럼, 민감한 정보가 모두 포함될 수 있다.
그래서 Response 전용 DTO를 사용한다.
Response DTO + of()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ResRegistryDto {
private Long idx;
private String title;
private String main;
private LocalDateTime createdAt;
private LocalDateTime modifiedAt;
private String nickname;
// entity → dto
public static ResRegistryDto of(Registry registry) {
return ResRegistryDto.builder()
.idx(registry.getIdx())
.title(registry.getTitle())
.main(registry.getMain())
.createdAt(registry.getCreatedAt())
.modifiedAt(registry.getModifiedAt())
.nickname(registry.getUser().getNickname())
.build();
}
}
of() 사용
1
2
3
4
5
public ResRegistryDto getIdxRegistry(Long idx) {
Registry registry = registryRepository.findById(idx)
.orElseThrow(() -> new NullPointerException("해당 게시글 없음"));
return ResRegistryDto.of(registry);
}
of()를 쓰는 이유
- Entity 구조 변경 시
- DTO 변환 로직을 한 곳에서만 수정
- 유지보수에 매우 유리
toEntity vs of 정리
DTO → Entity
1
2
3
4
5
6
7
public Registry toEntity(User user) {
return Registry.builder()
.user(user)
.title(title)
.main(main)
.build();
}
- 인스턴스 메서드
- 자기 자신의 필드 사용
- static x
Entity → DTO
1
2
3
4
5
6
7
public static ResRegistryDto of(Registry registry) {
return ResRegistryDto.builder()
.idx(registry.getIdx())
.title(registry.getTitle())
.main(registry.getMain())
.build();
}
- static 메서드
- Entity를 받아 변환
- 객체 생성 책임 명확
요약
- 저장 / 수정 : DTO → Entity →
toEntity() - 조회 / 응답 : Entity → DTO →
of() - Entity는 DTO를 몰라야 한다
This post is licensed under CC BY 4.0 by the author.