Mapstruct 使用教程

Mapstruct 使用教程Mapstruct 版本 1 5 0 Beta1 官方文档案例 github 前言 MapStruct 是一个 Java 注释处理器 用于生成类型安全的 bean 映射类 您要做的就是定义一个映射器接口 该接口声明任何必需的映射方法 在编译期间 MapStruct 将生成此接口的实现 此实现使用简单的 Java 方法调用在源对象和目标对象之间进行映射 即没有反射或类似内容 与手动编写映射代码相比 MapStruct 通过生成繁琐且易于出错的代码来节省时间 遵循配置方法上的约定 MapStruct 使用合理的默认值 但在配置

前言

MapStruct是一个Java注释处理器,用于生成类型安全的bean映射类。

您要做的就是定义一个映射器接口,该接口声明任何必需的映射方法。在编译期间,MapStruct将生成此接口的实现。此实现使用简单的Java方法调用在源对象和目标对象之间进行映射,即没有反射或类似内容。

  1. 通过使用普通方法调用(settter/getter)而不是反射来快速执行
  2. 编译时类型安全性:只能映射相互映射的对象和属性,不能将order实体意外映射到customer DTO等。
  3. 如果有如下问题,编译时会抛出异常
    3.1 映射不完整(并非所有目标属性都被映射)
    3.2 映射不正确(找不到正确的映射方法或类型转换)




  4. 可以通过freemarker定制化开发

1. 设置

MapStruct是基于JSR 269的Java注释处理器,因此可以在命令行构建(javac,Ant,Maven等)以及您的IDE中使用。

它包含以下工件:

  1. org.mapstruct:mapstruct:包含必需的注释,例如@Mapping
  2. 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)的相应属性中:

  1. 当一个属性与其目标实体对应的名称相同时,它将被隐式映射。
  2. 当属性在目标实体中具有不同的名称时,可以通过@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 { 
    ... } 
  1. unmappedSourcePolicy()、unmappedTargetPolicy() : 源或者目标没有标注映射的属性怎么报告
  2. typeConversionPolicy() :应该报告如何进行有损(缩小)转换,例如:long到integer的转换。
  3. 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.集合映射

MapStructCollectionMappingStrategy,与可能的值: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

(0)
上一篇 2026年3月19日 上午9:42
下一篇 2026年3月19日 上午9:42


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号