Table of Content
MapStruct 사용하기
MapStruct
를 이용하면 DTO – Entity 간 매핑을 간편하게 할 수 있고, 더불어 Entity 업데이트 코드를 매우 간단하게 줄일 수 있다.
annotationProcessor
의 순서가 중요하다. mapstruct-processor
가 나중에 있어야 정상 작동한다.
다른 사람들은 mapstruct-processor
가 먼저 있어야 한다고 하는데 본인은 아래 설정이 작동했다.
의존성 추가
dependencies {
// ......
implementation 'org.mapstruct:mapstruct:1.4.1.Final'
compileOnly 'org.projectlombok:lombok'
compileOnly 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.1.Final'
// ......
}
DTO – Entity 생성
DTO 와 Entity 의 필드명을 1:1 로 일치시켜야 코딩이 편해집니다.
@Setter
, @Getter
, @NoArgsConstructor
가 추가되어 있는지 항상 확인한다. 에러의 95% 가 에너테이션 누락이다.
@Setter
@Getter
@NoArgsConstructor
@Entity
public class Brands extends BaseTimeEntity implements Persistable<String> {
@Id
private String brandId;
@NotNull
@Column(length = 32)
private String partnerId;
@NotNull
@Column(length = 128)
private String brandName;
@NotNull
@ColumnDefault("Y")
@Column(length = 1)
private String useyn = "Y";
public Brands(String brandId) {
this.brandId = brandId;
}
@Override
public String getId() {
return brandId;
}
@Override
public boolean isNew() {
return getRegdate() == null;
}
}
@Getter
@Setter
@NoArgsConstructor
public class BrandsDto extends BaseTimeDto implements Serializable {
@ApiModelProperty(example = "conitale")
private String brandId;
@ApiModelProperty(example = "conitale")
private String partnerId;
@ApiModelProperty(example = "코니테일")
private String brandName;
@ApiModelProperty(example = "Y")
private String useyn;
}
GenericMapper 생성
DTO – Entity 간 필드명을 동일하게 생성한 경우 수동 매핑을 아예 안해도 된다.
updateFromDto()
는 Entity 업데이트시 값이 null 이 아닌 값만 업데이트 하도록 할 수 있다.
리스트에 대해서도 매핑이 작동한다.
public interface GenericMapper<D, E> {
D toDto(E e);
E toEntity(D d);
List<D> toDto(List<E> e);
List<E> toEntity(List<D> d);
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void updateFromDto(D dto, @MappingTarget E entity);
}
Mapper 생성
상속만으로 코딩이 끝난다. 필드명이 다르면 하나 하나 수동으로 매핑해야 한다.
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface BrandsMapper extends GenericMapper<BrandsDto, Brands> {
// @Override
// @Mapping(source = "name", target = "modelName")
// @Mapping(source = "color", target = "modelColor")
// BrandsDto toDto(Brands brands);
}
Service 생성
update
가 매우 간단해진다.
@Service
public class BrandsService {
private final BrandsMapper mapper = Mappers.getMapper(BrandsMapper.class);
// ......
public void update(String id, BrandsDto d) {
Brands e = get(id, true);
updateFromDto(d, e);
repository.save(e);
}
public D select(String id) {
return toDto(get(id, true));
}
protected BrandsDto toDto(Brands brands) {
return mapper.toDto(brands);
}
protected Brands toEntity(BrandsDto e) {
return mapper.toEntity(e);
}
protected void updateFromDto(BrandsDto dto, Brands brands) {
mapper.updateFromDto(dto, brands);
}
}
뜬금없는 오류발생
이따금 Mapper 관련 오류가 뜬금없이 발생합니다.
이럴땐 grable build clean 후 다시 실행하면 정상적으로 실행됩니다.
enum
public enum DeliveryType {
// ......
public static DeliveryType fromCode(Integer code) {
return Arrays.stream(values())
.filter((deliveryType) -> deliveryType.getCode().equals(code))
.findFirst()
.orElseThrow(NoSuchElementException::new);
}
public Integer getCode() {
return this.code;
}
// ......
}
변수중 enum <-> code
변환이 필요한 경우,
아래처럼 추가해주면 된다.
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = "spring")
public interface ItemBasicInfoMapper extends GenericMapper<ItemBasicInfoDto, ItemBasicInfo> {
default DeliveryType toDto(Integer deliveryType) {
return DeliveryType.fromCode(deliveryType);
}
default Integer toEntity(DeliveryType deliveryType) {
return deliveryType.getCode();
}
}