상속과 함께 MapStruct 및 Lombok 사용
내 페어링 파트너와 내가 이 사용 사례를 만났을 때 우리는 처음에 당황했습니다. 우리의 결합된 Google Fu 노력도 좋은 해결책을 찾지 못했습니다. 그러나 결국 문제를 발견했습니다a PR for MapStruct that seemed to be the solution! 하지만 물론 그렇게 간단했다면 지금 이 글을 쓰지 않았을 것입니다. 해당 PR에서 제공하는 SubClassMapping 주석은 매우 유용하지만 하위 클래스의 모든 필드에 대한 특정 Mapping 주석이 필요합니다. 우리는 보다 우아한 솔루션을 원했습니다.
추가 조사 후 Lombok에서 SuperBuilder 주석을 제공한다는 사실을 발견했습니다. 이를 부모 클래스와 자식 클래스 모두에 추가하면 부모 클래스와 자식 클래스에서 선언된 필드를 설정하는 데 사용할 수 있는 자식 클래스용 빌더가 생성됩니다. 그러나 이를 사용하여 하위 클래스의 인스턴스에서 상위 클래스의 필드를 설정할 수 있지만 자동 생성된 MapperImpl은 하위 클래스에서 빌더를 호출할 때만 상위 클래스 필드를 인식했습니다.
마지막으로 우리는 빌더를 비활성화하라는 BeanMapping 주석을 매퍼 메서드에 추가했습니다. 이것은 트릭을했다! 다음은 솔루션을 보여주는 몇 가지 예제 코드입니다.
차량Dto
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder
@NoArgsConstructor
public class VehicleDto {
private String vin;
private int numCylinders;
}
CarDto
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder
@EqualsAndHashCode(callSuper=true)
@NoArgsConstructor
public class CarDto extends VehicleDto {
private String make;
private String model;
private int numDoors;
}
차량 이벤트
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder
@NoArgsConstructor
public class VehicleEvent {
private String vin;
private int numCylinders;
}
자동차 이벤트
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder
@EqualsAndHashCode(callSuper=true)
@NoArgsConstructor
public class CarEvent extends VehicleEvent {
private String make;
private String model;
private int numDoors;
}
비히클맵퍼
import org.mapstruct.BeanMapping;
import org.mapstruct.Builder;
import org.mapstruct.Mapper;
import org.mapstruct.SubclassMapping;
@Mapper
public interface VehicleMapper {
@BeanMapping(builder = @Builder( disableBuilder = true ))
@SubclassMapping(source = CarDto.class, target = CarEvent.class)
VehicleEvent toEventData(VehicleDto vehicleDto);
}
VehicleMapperImpl(MapStruct에서 생성)
import javax.annotation.processing.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-09-21T17:00:25-0400",
comments = "version: 1.5.2.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-7.5.1.jar, environment: Java 11.0.15 (Amazon.com Inc.)"
)
public class VehicleMapperImpl implements VehicleMapper {
@Override
public VehicleEvent toEventData(VehicleDto vehicleDto) {
if ( vehicleDto == null ) {
return null;
}
if (vehicleDto instanceof CarDto) {
return carDtoToCarEvent( (CarDto) vehicleDto );
}
else {
VehicleEvent vehicleEvent = new VehicleEvent();
vehicleEvent.setVin( vehicleDto.getVin() );
vehicleEvent.setNumCylinders( vehicleDto.getNumCylinders() );
return vehicleEvent;
}
}
protected CarEvent carDtoToCarEvent(CarDto carDto) {
if ( carDto == null ) {
return null;
}
CarEvent carEvent = new CarEvent();
carEvent.setVin( carDto.getVin() );
carEvent.setNumCylinders( carDto.getNumCylinders() );
carEvent.setMake( carDto.getMake() );
carEvent.setModel( carDto.getModel() );
carEvent.setNumDoors( carDto.getNumDoors() );
return carEvent;
}
}
VehicleMapper 테스트
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class VehicleMapperTest {
@Test
void shouldMapParentAndChildFieldsWhenMappingChild() {
CarDto carDto = CarDto.builder()
.numCylinders(6)
.vin("2FTJW36M6LCA90573")
.make("Chevrolet")
.model("Malibu")
.numDoors(4)
.build();
VehicleMapper mapper = new VehicleMapperImpl();
CarEvent carEvent = (CarEvent) mapper.toEventData(carDto);
assertThat(carEvent.getNumCylinders()).isEqualTo(carDto.getNumCylinders());
assertThat(carEvent.getNumDoors()).isEqualTo(carDto.getNumDoors());
}
}
보시다시피 매퍼가 사용하지 않더라도 SuperBuilder 주석을 유지하기로 선택했습니다. 주로 이것은 쉬운 테스트를 허용합니다. 그러나 SuperBuilder 주석이 있는 경우 NoArgsConstructor 주석도 필요합니다. 그렇지 않으면 다음 코드를 생성합니다.
CarEvent.CarEventBuilder<?, ?> b = null;
CarEvent carEvent = new CarEvent( b );
null 빌더에서 NullPointerException이 발생합니다.
예제 코드에서와 같이 두 필드 집합이 채워지는지 확인하기 위해 부모 클래스와 자식 클래스에서 하나 이상의 값을 테스트하는 것이 좋습니다. 생성된 코드를 테스트하는 것은 약간 중복되는 것처럼 보이지만 이렇게 하면 변경 사항이 기존 기능을 중단하는지 빠르게 찾고 예상 동작을 문서화하는 것과 같은 테스트의 모든 일반적인 이점뿐만 아니라 주석의 올바른 조합이 있는지 확인할 수 있습니다.
Reference
이 문제에 관하여(상속과 함께 MapStruct 및 Lombok 사용), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/waregin/using-mapstruct-and-lombok-with-inheritance-249p텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)