728x90
아래 내용은 https://github.com/beaver84/setting-test 에서 실제 소스를 확인할 수 있습니다.
- JPA를 쓰다보면 Entity로 값을 가져오는 일이 많은데, 이를 그대로 리턴하는 일은 드물고 DTO를 상황에 맞게 가공을 거쳐서 반환을 하게 된다.
- 이 때, 단순 필드(컬럼)를 반복 나열하는 일이 많은데, 반복작업을 편리하게 도와주는 라이브러리 중 하나가 mapStruct 이다.
남용하기 보다는 필드가 많은 경우만 사용하는 것을 추천한다.
//문제 상황 - entity에서 DTO로 변환하는데 너무 많은 코드가 필요
private GoodsDTO getGoodsDTO(Goods goods) {
// return GoodsDTO.builder().id(goods.getId())
// .dotYn(goods.getDotYn())
// .moq(goods.getMoq())
// .otherGoodsCode(goods.getOtherGoodsCode())
// .imageId(goods.getImageId())
// .largeCategoryId(goods.getLargeCategoryId())
// .middleCategoryId(goods.getMiddleCategoryId())
// .modifier(goods.getModifier())
// .modifierDate(goods.getModifierDate())
// .domesticYn(goods.getDomesticYn())
// .multipleYn(goods.getMultipleYn())
// .origin(goods.getOrigin())
// .register(goods.getRegister())
// .orderLeadTime(goods.getOrderLeadTime())
// .multiple(goods.getMultiple())
// .packingQuantity(goods.getPackingQuantity())
// .registerDate(goods.getRegisterDate())
// .releaseType(goods.getReleaseType())
// .useYn(goods.getUseYn())
// .safetyStockYn(goods.getSafetyStockYn())
// .unitId(goods.getUnitId())
// .sellerId(goods.getSellerId())
// .standard(goods.getStandard())
// .weekdayClosingTime(goods.getWeekdayClosingTime())
// .weekendClosingTime(goods.getWeekendClosingTime())
// .build();
//위에 코드를 아래의 한줄로 수정 가능(Entity to DTO)
return goodsMapper.toDto(goods);
}
- MapStruct 란?
1. 기본 사용방법
MapStruct를 사용하기 위해서는 먼저 dependency (의존성) 추가가 필요하다.
dependencies {
...
implementation 'org.mapstruct:mapstruct:1.5.3.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
...
}
2. GenericMapper Interface를 만들어준다(최초 1회).
public interface GenericMapper<D, E> {
D toDto(E entity);
E toEntity(D dto);
List<D> toDtos(List<E> entities);
List<E> toEntities(List<D> dtos);
}
728x90
3. 매핑을 위한 GenericMapper를 구체화한 Mapper 인터페이스를 만든다(예 - GoodsMapper).
4. GenericMapper를 상속한 GoodsMapper 생성
@Mapper(componentModel = "spring")
public interface GoodsMapper extends GenericMapper<GoodsDTO, Goods> {
@Mapping(source = "name", target = "goodsName")
GoodsDTO toDto(Goods entity);
@Mapping(source = "goodsName", target = "name")
Goods toEntity(GoodsDTO dto);
List<GoodsDTO> toDtos(List<Goods> entities);
List<Goods> toEntities(List<GoodsDTO> dtos);
}
5. @Mapping 어노테이션 활용
- entity와 dto의 컬럼이름이 다를 경우 @Mapping 어노테이션을 통해 간단히 변환이 가능하다.
- entity에서는 name 컬럼이고, DTO에서는 goodsName일 경우, 위와 같이 셋팅을 하면 아래와 같이 DTO의 출력 필드가 변환이 된다(DB에서는 name 컬럼에 데이터 저장됨).
6. 메서드 앞에 default 접근 제어자를 활용하여 상세 커스터마이징도 가능
- validation을 하거나 ENUM 값을 커스텀하고 싶을 때는 메서드 앞에 default 접근제어자를 붙이면 가능하다(메뉴얼 참고).
@Mapper(componentModel = "spring")
public interface GoodsMapper extends GenericMapper<GoodsDTO, Goods> {
//YesNo ENUM 타입일 경우 DTO -> Entity로 변환할 때, 값이 어떻게 변화할지 셋팅 예시
//접근 제어자를 default로 해야 커스터마이징을 인식
default boolean map(YesNo value) {
switch (value) {
case Y:
return true;
case N:
default:
return false;
}
}
//YesNo ENUM 타입일 경우 Entity -> DTO로 변환할 때, 값이 어떻게 변화할지 셋팅 예시(위와 반대)
//접근 제어자를 default로 해야 커스터마이징을 인식
default YesNo map(boolean value) {
return value ? YesNo.Y : YesNo.N;
}
//default 접근 권한을 설정하여 toDto메서드의 커스터마이징이 가능
default GoodsDTO toDto(Goods entity){
GoodsDTO.GoodsDTOBuilder goodsDTO = GoodsDTO.builder();
goodsDTO.id(entity.getId());
goodsDTO.sellerId(entity.getSellerId());
goodsDTO.largeCategoryId(entity.getLargeCategoryId());
goodsDTO.middleCategoryId(entity.getMiddleCategoryId());
goodsDTO.unitId(entity.getUnitId());
goodsDTO.name(entity.getName());
goodsDTO.otherGoodsCode(entity.getOtherGoodsCode());
// 한 필드의 validation 검증
if(Objects.isNull(entity.getOrderLeadTime())) {
goodsDTO.orderLeadTime(0);
}
// 입력 값을 원하는 다른 값으로 커스터마이징 가능
goodsDTO.useYn(YesNo.TEST);
goodsDTO.register(entity.getRegister());
goodsDTO.modifier(entity.getModifier());
goodsDTO.registerDate(entity.getRegisterDate());
goodsDTO.modifierDate(entity.getModifierDate());
goodsDTO.standard(entity.getStandard());
goodsDTO.origin(entity.getOrigin());
goodsDTO.releaseType(entity.getReleaseType());
goodsDTO.weekdayClosingTime(entity.getWeekdayClosingTime());
goodsDTO.weekendClosingTime(entity.getWeekendClosingTime());
goodsDTO.packingQuantity(entity.getPackingQuantity());
goodsDTO.moq(entity.getMoq());
goodsDTO.dotYn(entity.getDotYn());
goodsDTO.safetyStockYn(entity.getSafetyStockYn());
goodsDTO.imageId(entity.getImageId());
goodsDTO.multipleYn(entity.getMultipleYn());
goodsDTO.multiple(entity.getMultiple());
goodsDTO.domesticYn(entity.getDomesticYn());
return goodsDTO.build();
}
// GoodsDTO toDto(Goods entity);
//List 형식의 Entities를 DTOs로 변환
List<GoodsDTO> toDtos(List<Goods> entities);
//DTO를 Entity로 변환
Goods toEntity(GoodsDTO dto);
//List 형식의 DTOs를 Entities로 변환
List<Goods> toEntities(List<GoodsDTO> dtos);
}
7. 위와 같이 설정하면 설정이 완료되고 빌드 시, 작성한 interface를 바탕으로 클래스 파일이 만들어진다(예 - GoodsMapperImpl.class)
- 실제로는 이를 기반으로 DTO ↔︎ Entity 간의 변환이 이루어진다.
- 아래 예시에는 dto 에서는 Y나 N으로 값을 받았지만, 실제로 DB에는 1 또는 0의 값이 들어가게 된다(default 접근제어자로 설정을 하였으므로 - default boolean map(YesNo value))
8. goodsMapper.toEntity 테스트 결과
@Test
@DisplayName("상품 테이블 조회 테스트")
void findById() {
GoodsDTO goodsDTO = new GoodsDTO();
goodsDTO.setName("좋은상품");
goodsDTO.setWeekdayClosingTime(2000);
goodsDTO.setSellerId(1);
goodsDTO.setOrderLeadTime(null);
goodsDTO.setUseYn(YesNo.Y);
//toEntity로 DTO를 Entity로 한번에 변환
Goods goodsEntity = goodsMapper.toEntity(goodsDTO);
goodsRepository.save(goodsEntity);
Goods goodsResult = goodsRepository.findByName("좋은상품");
assertThat(goodsResult.getId()).isGreaterThanOrEqualTo(1);
assertThat(goodsResult.getName()).isEqualTo("좋은상품");
assertThat(goodsResult.getWeekdayClosingTime()).isEqualTo(2000);
}
- 추가 사항 - 혹시 …mapper.class 파일을 찾지 못해서 에러가 발생했을 때, 메뉴 중 빌드에서 프로젝트 다시 빌드를 수행하면 generated Class가 생성되며 정상적으로 프로젝트가 빌드가 된다.
728x90
'Spring > 초기 setting' 카테고리의 다른 글
9) 비밀번호 암/복호화(Encrypt/Decrypt) 설정 (0) | 2023.04.20 |
---|---|
8) API Response(@ControllerAdvice) 설정 (0) | 2023.04.20 |
6) 스프링 시큐리티(spring-security) 적용 (0) | 2023.04.05 |
5) 스프링 시큐리티(spring-security) 개요 (0) | 2023.04.05 |
4) Querydsl 설정 (2) | 2023.03.20 |