DTO ↔ Entity 변환하기
2023. 11. 16. 04:10ㆍ백엔드/Spring Boot
✏️ DTO와 Entity의 변환 방법과 어느 계층에서 변환하는 것이 효율적인가에 대하여 알아보자.
JPA에서는 데이터 은닉과 보안, 성능 최적화, 네트워크 트래픽 감소, 유연성 및 확장성, 코드 유지보수성을 향상하기 위해
외부에서 입력받은 데이터를 DB에 저장하는 경우 DTO -> Entity로 변환하고
DB에서 조회한 데이터를 외부로 표출할 때에는 Entity -> DTO로 변환해야 줘야 한다.
🔎 DTO ↔ Entity 변환 방법
1. ModelMapper
개념
- 객체의 필드 값을 다른 객체의 필드 값으로 자동으로 맵핑해주는 라이브러리
- MapStruct에 비해 성능이 떨어지며 컴파일 단계에서 오류를 처리할 수 없다.
방법
- 의존성 주입
// build.gradle 파일에 추가
implementation 'org.modelmapper:modelmapper:2.4.4'
- DTO
@Getter
@RequriredArgsConstructor // final 필드가 없어 추가할 필요 없음
@ToString
@EqualsAndHashCode
// @Data 어노테이션은 setter가 생성될 수 있으므로 사용을 지양
public class ProductDTO {
private Long id;
private String name;
private double price;
// 생성자, Getter 등 (간편성을 위해 Lombok을 사용할 수 있습니다)
}
- Entity
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
// 생성자, Getter 등 (간편성을 위해 Lombok을 사용할 수 있습니다)
}
- Service
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
private final ModelMapper modelMapper;
private final ProductRepository productRepository;
@Autowired
public ProductService(ModelMapper modelMapper, ProductRepository productRepository) {
this.modelMapper = modelMapper;
this.productRepository = productRepository;
}
// DTO to Entity
public Product convertToEntity(ProductDto productDto) {
return modelMapper.map(productDto, Product.class);
}
// Entity To DTO
public ProductDto convertToDto(Product product) {
return modelMapper.map(product, ProductDto.class);
}
public List<Product> save(ProductDto productDto) {
// DTO를 Entity로 복사
Product product = convertToEntity(productDto);
return productRepository.save(product);
}
}
- ModelMapper 설정
// @Configuration 이 선언된 파일에서 작성
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
장점
- 구성이 간단하며 대부분의 기본적인 매핑 작업이 자동으로 처리됨
- 컴파일 시점에 타입 안정성을 유지할 수 있어 런타임 오류를 줄여줌
- 필드 이름이 동일한 경우 대부분 자동으로 매핑이 이루어져 매핑 작업을 빠르게 수행할 수 있음
단점
- 매핑 작업을 수행하기 위해 리플렉션을 사용하여 매핑이 복잡하거나 대규모 객체를 다루어야 할 때 성능 오버헤드가 발생
- 필드 이름이나 매핑 규칙이 일치하지 않은 경우 사용자 정의 설정이 필요, 코드 복잡성 증가
- 디버깅이 어려움
2. MapStruct
개념
- 엔티티와 DTO 간에 변환할 때 자동으로 매핑시켜 변환되도록 도와주는 라이브러리
- 매핑해 줄 클래스에는 setter가 있어야 하고 매핑이 되는 클래스에서는 getter가 선언되어 있어야 사용이 가능
- ModelMapper보다 성능이 좋고 컴파일 단계에서 오류를 확인할 수 있음
방법
- 의존성 주입
implementation "org.mapstruct:mapstruct:1.4.2.Final"
annotationProcessor "org.mapstruct:mapstruct-processor:1.4.2.Final"
- DTO
@Getter
@RequriredArgsConstructor // final 필드가 없어 추가할 필요 없음
@ToString
@EqualsAndHashCode
// @Data 어노테이션은 setter가 생성될 수 있으므로 사용을 지양
public class ProductDTO {
private Long id;
private String name;
private double price;
// 생성자, Getter 등 (간편성을 위해 Lombok을 사용할 수 있습니다)
}
- Entity
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
// 생성자, Getter 등 (간편성을 위해 Lombok을 사용할 수 있습니다)
}
- Mapper 인터페이스 생성
@Mapper(componentModel = "spring")
public interface ProductMapper {
ProductMapper INSTANCE = Mappers.getMapper(ProductMapper.class);
// DTO to Entity
Product product(ProductDto productDto);
// Entity to DTO
ProductDto productDto(Product product);
}
- Service
public Product save(ProductDto dto) {
Product pd = ProductMapper.INSTANCE.productToEntity(dto);
return productRepository.save(pd);
}
장점
- 별도의 설정 없이도 간단한 매핑 작업을 할 수 있어 프로젝트에 쉽게 통합 할 수 있음
- 컴파일 할 때 코드를 생성하므로 런타임 비용이 거의 없음
- 매핑 커스터마이징이 유연함
단점
- 일반적이고 단순한 매핑에는 효과적이지만 복잡한 로직이 필요한 경우에는 한계가 올 수 있음
- MapStruct의 사용법을 익히는데 시간이 걸릴 수 있음
- 매핑 작업이 복잡해지게 되면 런타임에서 생성된 코드의 양이 증가하게 되어 빌드 시간이 증가
3. @Builder 어노테이션 사용
개념
- 객체를 생성하는 복잡한 과정을 추상화하고, 객체의 표현과 생성을 분리하는 디자인 패턴 중 하나
- 많은 매개변수를 갖는 객체를 생성할 때 유용하며, 가독성을 향상시키고 코드를 유연하게 만듬
방법
- DTO
@Getter
@RequriredArgsConstructor // final 필드가 없어 추가할 필요 없음
@ToString
@EqualsAndHashCode
// @Data 어노테이션은 setter가 생성될 수 있으므로 사용을 지양
public class ProductDTO {
private Long id;
private String name;
private double price;
// 생성자, Getter 등 (간편성을 위해 Lombok을 사용할 수 있습니다)
}
- Entity
@Entity
@Builder
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
// 생성자, Getter 등 (간편성을 위해 Lombok을 사용할 수 있습니다)
}
- Service
public Product save(ProductDto dto) {
Product product = Product.builder()
.name(productDTO.getName())
.price(productDTO.getPrice())
.build();
return productRepository.save(product);
}
장점
- 빌더 패턴을 자동으로 생성해주기 때문에 코드 양이 감소하고 가독성이 향상
- 객체를 생성할 때 어떤 값을 설정하는지 명시적으로 표현
- Builder 클래스가 자동으로 생성되어 중복되는 코드 감소
단점
- 특정한 생성 로직이나 초기화 코드를 추가하기 어려움
- 일부 라이브러리나 프레임워크에서 Lombok을 지원하지 않을 수도 있어 호환성 문제 발생
🧐 DTO - Entity 변환은 어디에서 하는게 좋을까 ...?
: 저는 Service 에서 하는것을 추천합니다!
컨트롤러는 비즈니스 로직을 처리하는 곳이고 리포지토리는 DB와의 통신을 하는곳이기 때문이기도 하고
서비스 계층이 컨트롤러에서 받은 값을 리포지토리로 넘겨주는 다리 역할을 하기 때문에 이곳에서 형 변환을 하는것이 맞다고 생각하기 때문입니다.
내가 생각하는 구현 플로우
1. controller 에서 입력할 데이터를(from client) DTO에 담아와서 service를 호출
2. service에서 Dto -> Entity로 변환 후 repository를 호출
3. repository에서 데이터 저장 후 Entity를 반환
'백엔드 > Spring Boot' 카테고리의 다른 글
[Spring Security] 의존성 추가해보기 (1) | 2024.01.03 |
---|---|
JWT를 적용한 로그인 구현하기 (4) | 2024.01.03 |
JPA - Entity 생성을 위한 어노테이션 (0) | 2023.11.15 |
JPA : Java Persistence API (1) | 2023.11.15 |
Entity vs DTO vs VO 개념과 특징 (1) | 2023.11.08 |