MapStruct
bean 转换工具 MapStruct 使用笔记
官方入门示例
public class Car {
private String make;
private int numberOfSeats;
private CarType type;
//constructor, getters, setters etc.
}
public class CarDto {
private String make;
private int seatCount;
private String type;
//constructor, getters, setters etc.
}
将 Car 转换为 CarDto 的转换器如下
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto carToCarDto(Car car);
}
@Mapper
标识这是一个转换器carToCarDto()
是实际转换的方法,将入参 Car 转换为返回类型 CarDto, 方法名任意。
对于在原类型和目标类型中名字不同的字段,使用 @Mapping
注解进行映射
通过 Mappers.getMapper()
可以获取一个 mapper 实例
使用
CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
获取 Mapper
componentModel 属性
componentModel 属性用于指定自动生成的接口实现类的组件类型,这个属性支持四个值:default
这是默认的情况,mapstruct 不使用任何组件类型, 可以通过 Mappers.getMapper(Class)
方式获取自动生成的实例对象。cdi
the generated mapper is an application-scoped CDI bean and can be retrieved via @Inject
spring
生成的实现类上面会自动添加一个 @Component
注解,可以通过 Spring 的 @Autowired
方式进行注入jsr330
生成的实现类上会添加 @javax.inject.Named
和 @Singleton
注解,可以通过 @Inject
注解获取
mappingControl = DeepClone.class 深拷贝
普通的 Mapper 只是浅拷贝,增加 mappingControl = DeepClone.class
可实现深拷贝
@Mapper(mappingControl = DeepClone.class)
public interface CloningMapper {
CloningMapper INSTANCE = Mappers.getMapper( CloningMapper.class );
FridgeDTO clone(FridgeDTO in);
}
定义 Mapper
MapStruct 的类型转换规则
1、如果 source 和 target 属性类型相同,直接将 source 的值拷贝到 target,如果 source 是集合,将集合的 copy 放到 target 中。
2、如果 source 和 target 属性类型不同,寻找是否有以 source 类型为入参、 target 类型为返回值的方法,如果有,则会自动调用次方法进行类型转换。
3、查看是否可以进行内置类型转换
5.2. Mapping object references
https://mapstruct.org/documentation/stable/reference/html/#mapping-object-references
隐式类型转换
MapStruct 可自动处理下面的类型转换:
1、全部 Java 原子类型和其对应包装类型之间都可自动转换,例如 int 和 Integer 之间,将包装类型转换为原子类型时,会自动检测处理 null。
2、全部 Java 原子数据类型和包装类型,例如 int, long, byte 和 Integer 之间。
3、全部 Java 原子类型(包括启包装类型)和 String 之间可自动转换。
等等
5.1. Implicit type conversions
https://mapstruct.org/documentation/stable/reference/html/#implicit-type-conversions
嵌套字段映射
可通过 .
指定嵌套字段间的映射
@Mapper
public interface FishTankMapper {
@Mapping(target = "fish.kind", source = "fish.type")
@Mapping(target = "fish.name", ignore = true)
@Mapping(target = "ornament", source = "interior.ornament")
@Mapping(target = "material.materialType", source = "material")
@Mapping(target = "quality.report.organisation.name", source = "quality.report.organisationName")
FishTankDto map(FishTank source );
}
5.3. Controlling nested bean mappings
https://mapstruct.org/documentation/stable/reference/html/#controlling-nested-bean-mappings
ignore 忽略字段
@Mapping(target = "id", ignore = true)
@Mapping(target = "ruleId", source = "rulesDO.id")
MessageDO voRule2Do(MessageVO messageVO, RulesDO rulesDO);
uses 引用其他 Mapper
通过 @Mapper(uses = OtherMapper.class)
引用其他 Mapper 中的映射方法,可自动找到引用的 Mapper 中的自定义方法或 MapStruct 自动生成的方法。
例如定义一个 Date 自动转换 Mapper
public class DateMapper {
public String asString(Date date) {
return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ).format( date ) : null;
}
public Date asDate(String date) {
try {
return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ).parse( date ) : null;
} catch ( ParseException e ) {
throw new RuntimeException( e );
}
}
}
在 CarMapper 中通过 uses 引用 DateMapper,然后 MapStruct 生成的转换方法中会自动调用 DateMapper 的 String 和 Date 互转方法。
@Mapper(uses = DateMapper.class)
public interface CarMapper {
CarDto carToCarDto(Car car);
}
qualifiedByName 自定义类型转换方法
自定义类型转换方法,加 @Named
注解,@Mapping
中通过 qualifiedByName
指定自定义方法名。
@Mapper
public interface MovieMapper {
@Mapping(target = "title", qualifiedByName = { "EnglishToGerman" } )
@Mapping(target = "dictCatalogs", source = "dictConfig", qualifiedByName = "dictConfig2Catalogs")
GermanRelease toGerman(OriginalRelease movies);
@Named("EnglishToGerman")
static String translateTitleEG(String title) {
// some mapping logic
}
@Named("dictConfig2Catalogs")
default List<CatalogVO> dictConfig2Catalogs(String json) {
return Optional.ofNullable(json)
.filter(StringUtils::isNotBlank)
.map(j -> {
try {
return new ObjectMapper().readValue(j, new TypeReference<List<CatalogVO>>() {
});
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
})
.orElse(List.of());
}
}
qualifiedByName 指定的方法的入参类型就是 source 字段的类型,如果没有 source,默认就是和 target 同名字段的类型。
@Context 传递额外的上下文参数
可用于给 qualifiedByName 指定的方法传入多个参数
但是注意标注了 @Context 的参数不能再作为 source 参数。
constant 常数
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target = "stringConstant", constant = "Constant Value")
@Mapping(target = "integerConstant", constant = "14")
@Mapping(target = "longWrapperConstant", constant = "3001")
@Mapping(target = "dateConstant", dateFormat = "dd-MM-yyyy", constant = "09-01-2014")
Target sourceToTarget(Source s);
}
target 的 stringConstant 字段被设为固定常数 “Constant Value”
target 的 integerConstant 和 longWrapperConstant 都被设为固定常数,MapStruct 会自动转换为对应的数字类型。
target 的 dateConstant 被设为固定的日期常数,通过 dateFormat 指定日期格式
defaultValue 默认值
当 source 对象的对应字段为 null 时,defaultValue 指定的默认值会被放入 target 的对应字段。
通过字符串类型指定默认值,MapStruct 会自动转换基础类型以及基础类型的包装类型,其他类型会自动寻找是否存在匹配的类型转换方法。
比如可以写一个 Request2DO 的转换方法,直接通过 Mapper 上的 defaultValue 来指定接口入参的默认值
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")
@Mapping(target = "longProperty", source = "longProp", defaultValue = "-1")
@Mapping(target = "booleanProperty", defaultValue = "false")
Target sourceToTarget(Source s);
}
如果 s.getStringProp() == null
则 target 的 stringProperty 会被设置为 undefined
如果 s.getLongProp() == null
则 target 的 longProperty 会被设置为-1
如果 s.getBooleanProperty() == null
则 target 的 booleanProperty 字段会被设为 false
@MappingTarget 更新已存在的target对象
如果不想自动生成一个新的 target 实例,而是更新参数传入的 target 实例,可以给 target 增加 @MappingTarget
注解,此时返回的是传入的 target 对象
@Mapper
interface DeliveryAddressMapper {
@Mapping(source = "address.postalcode", target = "postalcode")
@Mapping(source = "address.county", target = "county")
DeliveryAddress updateAddress(@MappingTarget DeliveryAddress deliveryAddress, Address address);
}
3.6. Updating existing bean instances
https://mapstruct.org/documentation/stable/reference/html/#updating-bean-instances
Using Multiple Source Objects with MapStruct
https://www.baeldung.com/mapstruct-multiple-source-objects
expression java表达式求值
使用 expression 可以写脚本表达式,目前只支持 java 语言,在表达式中可以对 source 对象进行操作。
注意必须保证 expression 表达式中 java 代码的正确性,MapStruct 生成时不对代码进行检查。
示例:
enabled 字段的值使用 source 的 enabled() 方法结果
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mapping(target = "nodes", expression = "java(new java.util.ArrayList())")
@Mapping(target = "enabled", expression = "java(s.enabled())")
@Mapping(target = "timeAndFormat", expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )")
Target sourceToTarget(Source s);
}
问题
MapStruct结合lombok @Builder导致丢失父类字段
目标 DO 类如下:
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "user")
public class UserDO extends DefaultIdDO {
private String name;
}
UserDO 继承的 DefaultIdDO 中有个 protected Long id;
公共属性, UserDO 的 builder 中并不会有这个属性(除非父子类都加 @SuperBuilder
才会有)。
MapStruct 产生的映射方法如下,使用了 lombok @Builder 产生的 builder,丢了 UserDO 继承的 id 字段。
UserDO updateReq2Do(UserUpdateRequest userUpdateRequest);
@Override
public UserDO updateReq2Do(UserUpdateRequest userUpdateRequest) {
if ( userUpdateRequest == null ) {
return null;
}
UserDOBuilder userDO = UserDO.builder();
userDO.name( userUpdateRequest.getName() );
return userDO.build();
}
解决:
1、去掉实体上的 @Builder , 迫使 MapStruct 使用 gettr/setter 实现映射。
2、目标父子类都加 @SuperBuilder
, 使 lombok 自动生成的 builder 中有父类中的 id 字段。但如果父类是三方库中的则无法改动。
3、@Mapper
可以配置是否使用 builder 属性,关闭即可,关闭后会使用 gettr/setter 实现映射。(推荐)
@Mapper(builder = @Builder(disableBuilder = true))
public interface UserConverter {
}
Mapstruct中使用lombok@Builder的坑
https://www.jianshu.com/p/4f7b4f4bf689
3.8. Using builders
https://mapstruct.org/documentation/stable/reference/html/#mapping-with-builders
上一篇 Spring-Utils
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: