前言
MapStruct是一个Java注释处理器,用于生成类型安全的bean映射类。
您要做的就是定义一个映射器接口,该接口声明任何必需的映射方法。在编译期间,MapStruct将生成此接口的实现。此实现使用简单的Java方法调用在源对象和目标对象之间进行映射,即没有反射或类似内容。
- 通过使用普通方法调用(settter/getter)而不是反射来快速执行
- 编译时类型安全性:只能映射相互映射的对象和属性,不能将order实体意外映射到customer DTO等。
- 如果有如下问题,编译时会抛出异常
3.1 映射不完整(并非所有目标属性都被映射)
3.2 映射不正确(找不到正确的映射方法或类型转换) - 可以通过freemarker定制化开发
1. 设置
MapStruct是基于JSR 269的Java注释处理器,因此可以在命令行构建(javac,Ant,Maven等)以及您的IDE中使用。
它包含以下工件:
- org.mapstruct:mapstruct:包含必需的注释,例如@Mapping
- org.mapstruct:mapstruct-processor:包含注释处理器,该注释处理器生成映射器实现
1.1 Maven
对于基于Maven的项目,将以下内容添加到您的POM文件中以使用MapStruct:
<dependency> <groupId>org.mapstruct
groupId> <artifactId>mapstruct
artifactId> <version>1.5.0.Beta1
version>
dependency>
<dependency> <groupId>org.mapstruct
groupId> <artifactId>mapstruct-processor
artifactId> <version>1.5.0.Beta1
version>
dependency>
Lombok依赖:(版本最好在1.16.16以上,否则会出现问题)通常是和lombok一起使用
的
<dependency> <groupid>org.projectlombok
groupid> <artifactid>lombok
artifactid> <version>${lombok.version}
version> // 版本号 1.18.12
dependency>
2. 定义一个映射器
2.1 基本映射
在生成的方法实现中,源类型(例如Person)的所有可读属性都将被复制到目标类型(例如PersonDTO)的相应属性中:
- 当一个属性与其目标实体对应的名称相同时,它将被隐式映射。
- 当属性在目标实体中具有不同的名称时,可以通过@Mapping注释指定其名称。
如果不指定@Mapping,默认映射name相同的field
如果映射的对象field name不一样,通过 @Mapping 指定。
忽略字段加@Mapping#ignore() = true
@Data public class Person {
String describe; private String id; private String name; private int age; private BigDecimal source; private double height; private Date createTime; } @Data public class PersonDTO {
String describe; private Long id; private String personName; private String age; private String source; private String height; } // mapper @Mapper public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class); @Mapping(target = "name", source = "personName") @Mapping(target = "id", ignore = true) // 忽略id,不进行映射 PersonDTO conver(Person person); }
生成的实现类:
public class PersonMapperImpl implements PersonMapper {
public PersonMapperImpl() {
} public PersonDTO conver(Person person) {
if (person == null) {
return null; } else {
PersonDTO personDTO = new PersonDTO(); personDTO.setDescribe(person.getDescribe()); if (person.getId() != null) {
personDTO.setId(Long.parseLong(person.getId())); } personDTO.setPersonName(person.getName()); personDTO.setAge(String.valueOf(person.getAge())); if (person.getSource() != null) {
personDTO.setSource(person.getSource().toString()); } personDTO.setHeight(String.valueOf(person.getHeight())); return personDTO; } } }
测试:
@Test public void test(){
Person person = new Person(); person.setDescribe("测试"); person.setAge(18); person.setName("张三"); person.setHeight(170.5); person.setSource(new BigDecimal("100")); PersonDTO dto = PersonMapper.INSTANCT.conver(person); System.out.println(dto); // PersonDTO(describe=测试, id=null, personName=张三, age=18, source=100, height=170.5) }
2.2 指定默认值
@Mapping(target = "describe", source = "describe", defaultValue = "默认值") PersonDTO conver(Person person);
生成的impl:
... if (person.getDescribe() != null) {
personDTO.setDescribe(person.getDescribe()); } else {
personDTO.setDescribe("默认值"); } ...
测试:
@Test public void test(){
Person person = new Person(); //person.setDescribe("测试"); person.setAge(18); person.setName("张三"); person.setHeight(170.5); person.setSource(new BigDecimal("100")); PersonDTO dto = PersonMapper.INSTANCT.conver(person); System.out.println(dto); // PersonDTO(describe=默认值, id=null, name=张三, age=18, source=100, height=170.5) }
2.3 使用表达式
@Mapping(target = "describe", source = "describe", defaultValue = "默认值") @Mapping(target = "createTime",expression = "java(new java.util.Date())") PersonDTO conver(Person person);
测试:
@Test public void test(){
Person person = new Person(); //person.setDescribe("测试"); person.setAge(18); person.setName("张三"); person.setHeight(170.5); person.setSource(new BigDecimal("100")); PersonDTO dto = PersonMapper.INSTANCT.conver(person); System.out.println(dto); // PersonDTO(describe=默认值, id=null, name=张三, age=18, source=100, height=170.5, createTime=Fri Dec 11 23:21:31 GMT+08:00 2020) }
默认表达式@Mapping#defaultExpression()是默认值和表达式的组合。仅当source属性为null时才使用它们
2.4 dateFormat()
如果属性从字符串映射到日期,则该格式字符串可由SimpleDateFormat处理,反之亦然。当映射枚举常量时,将忽略所有其他属性类型。
.... @Mapping(target = "createTime" ,source = "createTime", dateFormat = "yyyy-MM-dd") PersonDTO conver(Person person); ...
impl:
try {
if (person.getCreateTime() != null) {
personDTO.setCreateTime((new SimpleDateFormat("yyyy-MM-dd")).parse(person.getCreateTime())); } } catch (ParseException var4) {
throw new RuntimeException(var4); }
2.5 组合映射
2.5.1 多个源对象
@Data public class BasicEntity {
private Date createTime; private String createBy; private Date updateTime; private String updateBy; private int _ROW; } @Mapper(uses =DateFormtUtil.class) public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class); @Mapping(target = "personName",source = "name") PersonDTO conver(Person person); @Mapping(target = "createTime",source = "basicEntity.createTime") PersonDTO combinationConver(Person personC, BasicEntity basicEntity); }
2.5.2 使用其他的值
... @Mapping(target = "id", source = "id") PersonDTO mapTo(Person person, String id); ...
虽然Person和Person有相同的id字段,但是映射器会使用mapTo方法里面的id参数。
2.6 嵌套映射
@Data public class Person {
... private Child personChild; ... } @Data public class PersonDTO {
... private Child child; ... } // mapper @Mapper(uses =DateFormtUtil.class) public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class); @Mapping(target = "child", source = "personChild") PersonDTO conver(Person person); }
如果field name一样则不需要指定@Mapping
2.7 numberFormat()
使用numberFormat()之后DecimalFormat格式转换,还是会抛出NFE异常
// mapper .... @Mapping(target = "age",source = "age", numberFormat = "#0.00") PersonDTO conver(Person person); ... // imppl personDTO.setAge((new DecimalFormat("#0.00")).format((long)person.getAge()));
2.8 逆映射
在双向映射的情况下,例如从实体到DTO以及从DTO到实体,前向方法和反向方法的映射规则通常是相似的,并且可以通过切换source和来简单地反转target。
使用注释@InheritInverseConfiguration表示方法应继承相应反向方法的反向配置
.... @Mapping(target = "age",source = "age", numberFormat = "#0.00") PersonDTO conver(Person person); @InheritInverseConfiguration Person conver(PersonDTO dto); ...
2.9 继承与共享配置
2.9.1 继承配置
方法级配置注解,例如@Mapping,@BeanMapping,@IterableMapping,等等,都可以继承从一个映射方法的类似使用注释方法@InheritConfiguration:
@Mapper public interface CarMapper {
@Mapping(target = "numberOfSeats", source = "seatCount") Car carDtoToCar(CarDto car); @InheritConfiguration void carDtoIntoCar(CarDto carDto, @MappingTarget Car car); }
上面的示例声明了一种carDtoToCar()具有配置的映射方法,该配置定义了应如何映射numberOfSeats类型中的属性Car。在现有Instance实例上执行映射的update方法Car需要相同的配置才能成功映射所有属性。通过声明@InheritConfiguration该方法,MapStruct可以搜索继承候选,以应用继承自该方法的注释。
如果所有类型的A(源类型和结果类型)都可以分配给B的相应类型,则一个方法A可以从另一种方法B继承配置。
如果可以使用多个方法作为继承的源,则必须在注释中指定方法名称:@InheritConfiguration( name = “carDtoToCar” )。
一种方法,可以使用==@InheritConfiguration==和覆盖或通过另外施加修改的配置@Mapping,@BeanMapping等等。
2.9.2 共享配置
MapStruct提供了通过指向带注释的中央接口来定义共享配置的可能性@MapperConfig。为了使映射器使用共享配置,需要在@Mapper#config属性中定义配置接口。
该@MapperConfig注释具有相同的属性@Mapper注释。任何未通过via指定的属性@Mapper都将从共享配置中继承。中指定@Mapper的属性优先于通过引用的配置类指定的属性。列表属性例如uses可以简单组合:
@MapperConfig(unmappedTargetPolicy = ReportingPolicy.ERROR ) public interface CentralConfig {
} @Mapper(config = CentralConfig.class } ) // Effective configuration: // @Mapper(uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class }, // unmappedTargetPolicy = ReportingPolicy.ERROR // ) public interface SourceTargetMapper {
... }
- unmappedSourcePolicy()、unmappedTargetPolicy() : 源或者目标没有标注映射的属性怎么报告
- typeConversionPolicy() :应该报告如何进行有损(缩小)转换,例如:long到integer的转换。
- collectionMappingStrategy(): 集合类型映射策略
其他的,请阅读源码
3. 使用自定义方法
3.1 自定义类型转换方法
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 ); } } }
mapper:
@Mapper(uses=DateMapper.class) public interface PersonMapper{
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class); PersonDTO conver(Person person); }
impl:
public class PersonMapperImpl implements PersonMapper {
private final DateMapper dateMapper = new DateMapper(); public PersonMapperImpl() {
} public PersonDTO conver(Person person) {
.... personDTO.setCreateTime(this.dateMapper.asDate(person.getCreateTime())); ... return personDTO; } }
3.2 使用@Qualifier
@Qualifier标记的自定义注解标记的方法,必须有输入, 否则编译时会抛出异常
public class DateFormtUtil {
@DateFormat public static String dateToString(Date date){
return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date); } @Qualifier @Target(ElementType.METHOD) @Retention(RetentionPolicy.CLASS) public @interface DateFormat{
} }
mapper:
@Mapper(uses =DateFormtUtil.class) public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class); @Mapping(target = "createTime",source = "createTime",qualifiedBy = DateFormat.class) PersonDTO conver(Person person); }
3.3 使用@namd
public class DateFormtUtil {
@Named("dateToString") public static String dateToString(Date date){
return date == null ? "": new SimpleDateFormat("yyyy-MM-dd").format(date); } }
mapper:
@Mapper(uses =DateFormtUtil.class) public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class); @Mapping(target = "createTime",source = "createTime",qualifiedByName = "dateToString") PersonDTO conver(Person person); }
效果跟@Qualifier是一样的
4.集合映射
MapStruct有CollectionMappingStrategy,与可能的值:ACCESSOR_ONLY,SETTER_PREFERRED,ADDER_PREFERRED和TARGET_IMMUTABLE。
在下表中,破折号-表示属性名称。接下来,尾部s表示复数形式。该表解释了这些选项以及它们是如何施加到存在/不存在的set-s,add-s和/或get-s在目标对象上的方法:
| 选项 | 仅目标set-s可用 | 仅目标add-可用 | 既可以set-s/add- | 没有set-s/add- | 现有目标(@TargetType) |
|---|---|---|---|---|---|
ACCESSOR_ONLY |
set-s | get-s | set-s | get-s | get-s |
SETTER_PREFERRED |
set-s | add- | set-s | get-s | get-s |
ADDER_PREFERRED |
set-s | add- | add- | get-s | get-s |
TARGET_IMMUTABLE |
set-s | exception | set-s | exception | set-s |
5.集成到 spring
在@Mapper#componentModel 中指定依赖注入框架
@Mapper(componentModel = "spring") public interface ModelMapper {
ModelMapper INSTANT = Mappers.getMapper(ModelMapper.class); ModelVO conver(Model model); } // 直接在类中使用Autowired注入就行了 @RestController class MapperSpringController {
@Autowired ModelMapper modelMapper; @GetMapping("/get") ModelVO getModle(){
Model model = new Model(); model.setId(""); model.setName("张三"); model.setCreate(new Date()); return modelMapper.conver(model); } }
6 高级运用
6.1 spi的运用
官方文档 关于spi的运用描述
6.2 freemarker生成代码
github链接
参考博客
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/209245.html原文链接:https://javaforall.net
