映射结构:按超类排除所有字段



我有标准实体和dto。

实体:

public class Type extends DefaultEntity {
int id;
String name
...
}

public class Attribute extends DefaultEntity{
int id;
String name; 
Type type;
...
}

和 DTO 的

public class TypeDto extends DefaultDto {
int id;
String name;
...
}

public class AttributeDto extends DefaultDto{
int id;
String name; 
TypeDto type;
....
}

当我创建此Mapper时:

@Mapper
public interface AttributeDtoConverter {
AttributeDto convert(Attribute source);
}

Mapstruct 生成:

public class AttributeDtoConverterImpl implements AttributeDtoConverter {

@Override
public AttributeDto convert(Attribute source) {
if ( source == null ) {
return null;
}
AttributeDto attributeDto = new AttributeDto();
attributeDto.setId( source.getId() );
attributeDto.setName( source.getName() );
attributeDto.setCode( source.getCode() );
attributeDto.setType( typeToTypeDto( source.getType() ) );
return attributeDto;
}
// Here is the problem
protected TypeDto typeToTypeDto(Type type) {
if ( type== null ) {
return null;
}
TypeDto dictionaryDto = new TypeDto();
typeDto.setId( type.getId() );
typeDto.setName( type.getName() );
return typeDto;
}
}

如果目标对象是DefaultDto的子类,我想从映射中排除它们(如果它们是DefaultEntity的子类,则排除源对象的所有属性)。

我知道我可以按名称@Mapping(target="type", ignore=true)排除每个属性,但是对于具有更多依赖项的大型类,这将非常烦人。用这种方法改变模型变成了地狱。

我可以以某种方式通过其超类排除所有属性吗?

尝试@Condition更新:

@Mapper
public interface AttributeDtoConverter {
AttributeDto convert(Attribute source);
@Condition
default boolean needToMap(Object value){
return ! (value instanceof DefaultDto || value instanceof DefaultEntity);
}
}

它可以工作,但我不喜欢生成的代码的样子:

public class AttributeDtoConverterImpl implements AttributeDtoConverter {

@Override
public AttributeDto convert(Attribute source) {
if ( source == null ) {
return null;
}
AttributeDto attributeDto = new AttributeDto();
if ( needToMap( attributeDto.getId() ) ) {
attributeDto.setId( source.getId() );
}

if ( needToMap( attributeDto.getName() ) ) {
attributeDto.setName( source.getName() );
}
if ( needToMap( attributeDto.getCode() ) ) {
attributeDto.setCode( source.getCode() );
}
if ( needToMap( attributeDto.getType() ) ) {
attributeDto.setType( typeToTypeDto( source.getType() ) );
}
return attributeDto;
}
...
}

我不知道它是否适用于您的用例,因为该解决方案是全局的,而不是特定于某个映射,但可能一个好方法是使用自定义MappingExclusionProvider

Mapstruct 文档指出:

MapStruct提供了覆盖MappingExclusionProvider的可能性 通过服务提供商接口 (SPI)。一个很好的例子是不允许 MapStruct 为某种类型创建自动子映射,即 MapStruct 不会尝试为 排除的类型。

例如:

import java.util.regex.Pattern;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import org.mapstruct.ap.spi.MappingExclusionProvider;
public class CustomMappingExclusionProvider implements MappingExclusionProvider {
private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\..*" );
@Override
public boolean isExcluded(TypeElement typeElement) {
Name name = typeElement.getQualifiedName();
return name.length() != 0 && ( JAVA_JAVAX_PACKAGE.matcher( name ).matches() ||
name.toString().equals( "your.pkg.DefaultDto" )||
name.toString().equals( "your.pkg.DefaultEntity" ) );
}
}

这里应该指出两件事:

  • 首先,作为 Mapstruct 默认实现,自定义排除映射器应排除javajavax包下的所有类型。如文档中所示,这意味着 MapStruct 不会尝试在某些自定义类型和 Java 类库中声明的某些类型之间生成自动子映射方法。
  • 其次,正如文档中再次解释的那样,要注册服务,您需要:

要使用自定义 SPI 实现,它必须位于单独的 JAR 中 文件以及以 SPI 命名的文件(例如org.mapstruct.ap.spi.AccessorNamingStrategy) 在META-INF/services/与 作为内容的自定义实现的完全限定名称(例如org.mapstruct.example.CustomAccessorNamingStrategy)。此 JAR 文件需要 要添加到注释处理器类路径中(即将其添加到 添加mapstruct-processor罐的地方)。

在您的情况下,这意味着使用自定义MappingExclusionProvider的完全限定名称定义一个名为org.mapstruct.ap.spi.MappingExclusionProvider的文件META-INF/services/下。

使用@BeforeMapping@AfterMapping钩子怎么样?无论如何,映射都会完成,所以也许不是最好的解决方案,但由于排除的 mapstruct 功能以及您对仅适用于某些转换器的要求,似乎不可能排除,但映射可以使用非冲突完成,即如果这些值在任何钩子中被修改,则为 null 或空值。这里有一个例子,来源于mapstruct doc:https://mapstruct.org/documentation/stable/reference/html/#customizing-mappings-with-before-and-after

@Mapper
public abstract class VehicleMapper {
@BeforeMapping
protected void flushEntity(AbstractVehicle vehicle) {
}
@AfterMapping
protected void fillTank(AbstractVehicle vehicle, @MappingTarget AbstractVehicleDto result) {
result.fuelUp( new Fuel( vehicle.getTankCapacity(), vehicle.getFuelType() ) );
}
public abstract CarDto toCarDto(Car car);
}
// Generates something like this:
public class VehicleMapperImpl extends VehicleMapper {
public CarDto toCarDto(Car car) {
flushEntity( car );
if ( car == null ) {
return null;
}
CarDto carDto = new CarDto();
// attributes mapping ...
fillTank( car, carDto );
return carDto;
}
}

最新更新