Compile 사이클에 인터페이스에 선언 된 어노테이션을 기반으로 매핑 클래스를 Spring Bean 으로 만든다.
- 사용하는 이유는 Model 클래스 사이의 맵핑을 간단하게 구현하기 위함이다.
- Spring Bean 의 SingleTon 스코프로 자원 사용을 줄인다.
- 간단한 어노테이션으로도 매핑 구현클래스를 생성해낸다.
- 싱글톤이라는 점, 자동 주입이 가능하다는 점이 강점으로 보인다.
필요의존
- org.mapstruct:mapstruct:${version}
- org.mapstruct:mapstruct-processor:${version}
- test,compile 시점에 어노테이션 프로세서를 사용해야 함
ext{
mapstructVersion="1.4.1.Final"
}
implementation "org.mapstruct:mapstruct:${mapstructVersion}"
compileOnly "org.mapstruct:mapstruct-processor:${mapstructVersion}"
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
매핑예시
- Api 클래스 , Enity 클래스로 나뉜 상태
- 인터페이스로 MapStruct 가 구현할 클래스의 맵핑 방법을 어노테이션으로 기술
@AllArgsConstructor
@Getter
@Setter
public class Recommendation {
private int productId;
private int recommendationId;
private String author;
private int rate;
private String content;
private String serviceAddress;
public Recommendation() {
this.productId = 0;
this.recommendationId = 0;
this.author = null;
this.rate = 0;
this.content = null;
this.serviceAddress = null;
}
}
// Entity
@Document(collection = "recommendations")
@CompoundIndex(name = "prod-rec-id",unique = true,def = "{'productId':1, 'recommendationId':1}")
@Getter
@Setter
@NoArgsConstructor
@RequiredArgsConstructor
public class RecommendationEntity {
@Id
private String id;
@Version
private Integer version;
@NonNull
private int productId;
@NonNull
private int recommendationId;
@NonNull
private String author;
@NonNull
private int rating;
@NonNull
private String content;
}
MapStruct 예시
- @Mapper 의 componentModel 을 spring 으로 잡으면 SpringBean 을 생성
- @Mappings 로 매핑 주의사항을 기술
- target = 리턴필드
- ignore = 금지 여부
- source = target 필드에 매핑시킬 필드 ( 매개변수로 접근 )
- 이후 JavaCompile 사이클 실행
@Mapper(componentModel = "spring")
public interface RecommendationMapper {
@Mappings({
@Mapping(target = "rate",source = "entity.rating"),
@Mapping(target = "serviceAddress", ignore = true)
})
Recommendation entityToApi(RecommendationEntity entity);
@Mappings({
@Mapping(target = "rating", source = "api.rate"),
@Mapping(target = "id", ignore = true),
@Mapping(target = "version", ignore = true)
})
RecommendationEntity apiToEntity(Recommendation api);
List<Recommendation> entityListToApiList(List<RecommendationEntity> entityList);
List<RecommendationEntity> apiListToEntityList(List<Recommendation> api);
}
MapStruct 가 만든 구현 클래스
- List 맵핑은 기본적으로 지정해놓은 맵핑 메서드를 내부적으로 호출하고 있음.
- 가만 보았을 때에는 메서드 시그니쳐를 따라가는 듯 함
@Component
public class RecommendationMapperImpl implements RecommendationMapper {
public RecommendationMapperImpl() {
}
public Recommendation entityToApi(RecommendationEntity entity) {
if (entity == null) {
return null;
} else {
Recommendation recommendation = new Recommendation();
recommendation.setRate(entity.getRating());
recommendation.setProductId(entity.getProductId());
recommendation.setRecommendationId(entity.getRecommendationId());
recommendation.setAuthor(entity.getAuthor());
recommendation.setContent(entity.getContent());
return recommendation;
}
}
public RecommendationEntity apiToEntity(Recommendation api) {
if (api == null) {
return null;
} else {
RecommendationEntity recommendationEntity = new RecommendationEntity();
recommendationEntity.setRating(api.getRate());
recommendationEntity.setProductId(api.getProductId());
recommendationEntity.setRecommendationId(api.getRecommendationId());
recommendationEntity.setAuthor(api.getAuthor());
recommendationEntity.setContent(api.getContent());
return recommendationEntity;
}
}
public List<Recommendation> entityListToApiList(List<RecommendationEntity> entityList) {
if (entityList == null) {
return null;
} else {
List<Recommendation> list = new ArrayList(entityList.size());
Iterator var3 = entityList.iterator();
while(var3.hasNext()) {
RecommendationEntity recommendationEntity = (RecommendationEntity)var3.next();
list.add(this.entityToApi(recommendationEntity));
}
return list;
}
}
public List<RecommendationEntity> apiListToEntityList(List<Recommendation> api) {
if (api == null) {
return null;
} else {
List<RecommendationEntity> list = new ArrayList(api.size());
Iterator var3 = api.iterator();
while(var3.hasNext()) {
Recommendation recommendation = (Recommendation)var3.next();
list.add(this.apiToEntity(recommendation));
}
return list;
}
}
}
사용하는 API 서비스
- 두 모델 클래스 간의 바인딩이 굉장히 수월하게 진행 됨.
@RestController
@Slf4j
public class RecommendationServiceImpl implements RecommendationService {
private final ServiceUtil serviceUtil;
private final RecommendationRepository repository;
private final RecommendationMapper mapper;
@Autowired
public RecommendationServiceImpl(ServiceUtil serviceUtil, RecommendationRepository repository, RecommendationMapper mapper) {
this.serviceUtil = serviceUtil;
this.repository = repository;
this.mapper = mapper;
}
@Override
public List<Recommendation> getRecommendations(int productId) {
if(productId < 1) throw new InvalidInputException("Invalid productId: "+productId);
List<RecommendationEntity> entityList = repository.findByProductId(productId);
List<Recommendation> list = mapper.entityListToApiList(entityList);
list.forEach(r -> r.setServiceAddress(serviceUtil.getServiceAddress()));
log.debug("getRecommendations: response size: {}",list.size());
return list;
}
'작업하면서 배우는 것들' 카테고리의 다른 글
ReactiveMongoRepository 를 위한 작업 (0) | 2021.01.21 |
---|---|
SpringFox:Swagger3 사용 (0) | 2021.01.05 |
QueryDsl Left Outer Join, Tuple, Projections (0) | 2020.12.27 |
QueryDsl 과 커스텀 리포지토리를 이용한 쿼리문 사용 (0) | 2020.12.27 |
JPA Projection FindAll (0) | 2020.12.24 |