mapstruct详解

  • 没有提供对应的对象,自己实现,提高认识
  • 学习方式:最好是对class进行一个反编译,看看他生成的代码。如果发现一些类型没设置成功也可以通过反编译查看。
  • 反编译工具:java-decompiler
<!--导入的版本,参考官网-->
<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct</artifactId>
  <version>${org.mapstruct.version}</version>
</dependency>
<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct-processor</artifactId>
  <version>${org.mapstruct.version}</version>
</dependency>

映射器定义

// 测试方法,后续都是这样测试
@Test
void test() {
  BasicMapper instance = BasicMapper.INSTANCE;
	BasicUserDTO convert = instance.convert(user);
}

基本映射

  • 如果两个字段名称相同会自动映射。
  • 如果两个字段名称不相同则需要使用@Mapping进行映射,参考 - 从多个源对象映射
@Mapper
public interface BasicMapper {
  // 使用入口
  BasicMapper INSTANCE = Mappers.getMapper(BasicMapper.class);
  BasicUserDTO convert(BasicUser user);
}

自定义映射方法

// 接口方式
@Mapper
public interface BasicMapper {
  BasicMapper INSTANCE = Mappers.getMapper(BasicMapper.class);
  BasicUserDTO convert(BasicUser user);
  default PersonDTO convertCustom(BasicUser user) {
    return PersonDTO
             .builder()
             .id(String.valueOf(user.getId()))
             .firstName(user.getName().substring(0, user.getName().indexOf(" ")))
             .lastName(user.getName().substring(user.getName().indexOf(" ") + 1))
             .build();
  }
}

// 抽象类方式,好处:可以直接在映射器类中声明附加字段
@Mapper
public abstract class BasicMapper {

  public abstract BasicUserDTO convert(BasicUser user);

  public PersonDTO convertCustom(BasicUser user) {
    return PersonDTO
             .builder()
             .id(String.valueOf(user.getId()))
             .firstName(user.getName().substring(0, user.getName().indexOf(" ")))
             .lastName(user.getName().substring(user.getName().indexOf(" ") + 1))
             .build();
  }
}

从多个源对象映射

  • 当入参和返回的参数不匹配时,或者 有多个入参对象时,可以通过该方式指定需要映射到哪个字段
  • source:传入进来的参数
  • target:返回的参数
@Mapping(source = "user.id", target = "id")
@Mapping(source = "user.name", target = "firstName")
@Mapping(source = "education.degreeName", target = "educationalQualification")
@Mapping(source = "address.city", target = "residentialCity")
@Mapping(source = "address.country", target = "residentialCountry")
PersonDTO convert(BasicUser user, Education education, Address address);

映射嵌套对象

  • 当有嵌套对象时,可以采用@Mapper的uses指定一个嵌套对象对应的映射类
@Data
@Builder
@ToString
public class BasicUser {
  private int id;
  private String name;
  // 嵌套对象
  private List<Manager> managerList;
}

// 指定一个嵌套对象的映射类
@Mapper(uses = {ManagerMapper.class})
public interface UserMapper {
  UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
  
  @Mapping(source = "user.id", target = "id")
  @Mapping(source = "user.name", target = "firstName")
  @Mapping(source = "education.degreeName", target = "educationalQualification")
  @Mapping(source = "address.city", target = "residentialCity")
  @Mapping(source = "address.country", target = "residentialCountry")
  PersonDTO convert(BasicUser user, Education education, Address address);
}

  • 在生成方法时,会通过指定的嵌套类对这个嵌套对象进行映射,如下所示
// 生成了一个managerListToManagerDTOList对嵌套类进行解析

更新现有实例

  • 使用映射更新现有的 DTO
  • 对需要更新的映射添加一个 @MappingTarget注解,就会对其进行更新
@Mapping(source = "address.city", target = "residentialCity")
@Mapping(source = "address.country", target = "residentialCountry")
void updateExisting(Address address, @MappingTarget PersonDTO personDTO);
// 生成了一个DTO
PersonDTO personDTO = UserMapper.INSTANCE.convert(address);
// 对这个personDTO进行了更新
UserMapper.INSTANCE.updateExisting(address,personDTO);

继承配置

  • 对于重复的配置,使用 @InheritConfiguration,MapStruct 会查找已配置的方法,并且进行应用
@Mapper
public interface ManagerMapper {
  ManagerMapper INSTANCE = Mappers.getMapper(ManagerMapper.class);
  // 故意吧这两个顺序弄反,在测试看是否生效
  @Mapping(source = "address.city", target = "residentialCountry")
	@Mapping(source = "address.country", target = "residentialCity")
  ManagerDTO convert(Manager manager);

  @InheritConfiguration
  void updateExisting(Manager manager, @MappingTarget ManagerDTO managerDTO);
}

逆映射

  • 想定义一个双向映射
    如:

    • Entity 映射 DTO
    • DTO 映射 Entity
  • 使用 @InheritInverseConfiguration 会自动的反转配

    @Mapper
    public interface UserMapper {
     UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
     // mapping的配置效果,也会被下面的反转映射所使用
     BasicUserDTO convert(BasicUser user);
    
     @InheritInverseConfiguration // 反转映射
     BasicUser convert(BasicUserDTO userDTO);
    }
    
    

映射期间的异常处理

  • 自定义校检规则,映射期间如果发现跟校检的要求不匹配,则抛出异常(自定义)
  • 步骤:
    • 自定义异常

      public class ValidationException extends RuntimeException {
      
        public ValidationException(String message, Throwable cause) {
          super(message, cause);
        }
      
        public ValidationException(String message) {
          super(message);
        }
      }
      
    • 自定义校检规则

      • 方法名要求:validate字段名(字段类型)
      • 注意事项:校检的字段类型要和形参以及异常进行匹配,否则的话匹配不到则不会生效
      public class Validator {
        public int validateId(int id) throws ValidationException {
          if(id < 0){
            throw new ValidationException("Invalid ID value");
          }
          return id;
        }
      }
      
      
    • 使用

      // 导入校检规则
      @Mapper(uses = { Validator.class})
      public interface UserMapper {
        UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
        // 抛出对应的异常
        BasicUserDTO convert(BasicUser user) throws ValidationException;
      
      }
      
      

数据类型转换

隐式类型转换

  • 数值

    @Mapping(source = "employment.salary",
             target = "salary",
             numberFormat = "$#.00") 
    PersonDTO convert(BasicUser user,
                      Education education,
                      Address address,
                      Employment employment);
    // 会自动转换,如下:
    personDTO.setSalary( new DecimalFormat( "$#.00" ).format(
                    employment.getSalary() ) );
    
    
  • 日期

    @Mapping(source = "dateOfBirth",
             target = "dateOfBirth",
             dateFormat = "dd/MMM/yyyy") 
    ManagerDTO convert(Manager manager);
    
    // 会自动转换,如下:
    managerDTO.setDateOfBirth(
        new SimpleDateFormat( "dd/MMM/yyyy" )
        .parse( manager.getDateOfBirth() ) );
    
    // 如果没自定义转换, 则生成如下:
    managerDTO.setDateOfBirth( new SimpleDateFormat().parse(
        manager.getDateOfBirth() ) );
    
    

映射集合

  • 通过循环遍历,进行映射。

  • 如果使用了@Mapping的uses则会自动调用此对应的映射方法来执行元素转换。

  • 简单使用

    @Mapper
    public interface CollectionMapper {
      CollectionMapper INSTANCE = Mappers.getMapper(CollectionMapper.class);
    
      Set<String> convert(Set<Long> ids);
      
      Set<EmploymentDTO> convertEmployment(Set<Employment> employmentSet);
    }
    
    
  • 需要对实体进行自定义映射,则需要先定义实体之间的转换方法。

    @Mapper
    public interface CollectionMapper {
      CollectionMapper INSTANCE = Mappers.getMapper(CollectionMapper.class);
    	
      // 自定义映射
      @Mapping(source = "degreeName", target = "degree")
      @Mapping(source = "institute", target = "college")
      @Mapping(source = "yearOfPassing", target = "passingYear")
      EducationDTO convert(Education education);
      // 会去匹配自定义映射进行转换
      List<EducationDTO> convert(List<Education> educationList);
    }
    
    // 会生成如下代码:
    educationDTO.degree( education.getDegreeName() );
    educationDTO.college( education.getInstitute() );
    educationDTO.passingYear( education.getYearOfPassing() );
    
    
  • 对map进行映射

    • 可以通过 keyNumberFormat 和 valueDateFormat 对转入的键值做一个转换
      @Mapper
      public interface CollectionMapper {
        CollectionMapper INSTANCE = Mappers.getMapper(CollectionMapper.class);
      
        @MapMapping(keyNumberFormat = "#L", valueDateFormat = "dd.MM.yyyy")
        Map<String, String> map(Map<Long, Date> dateMap);
      }
      
      // 生成如下代码:
      String key = new DecimalFormat( "#L" ).format( entry.getKey() );
      String value = new SimpleDateFormat( "dd.MM.yyyy" ).format( entry.getValue() );
      
      

映射策略

  • 默认值为 ACCESSOR_ONLY
    // 使用ADDER_PREFERRED策略
    @Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
    public interface PersonMapperAdderPreferred {
      PersonDTO map(Person person);
    }
    

映射流

  • 和映射集合相同,只是 Stream会从提供的返回 Iterable
    @Mapper
    public interface CollectionMapper {
      CollectionMapper INSTANCE = Mappers.getMapper(CollectionMapper.class);
    
      Set<String> convertStream(Stream<Long> ids);
    
      @Mapping(source = "degreeName", target = "degree")
      @Mapping(source = "institute", target = "college")
      @Mapping(source = "yearOfPassing", target = "passingYear")
      EducationDTO convert(Education education);
      List<EducationDTO> convert(Stream<Education> educationStream);
    }
    
    // 生成如下:
    return ids.map( long1 -> String.valueOf( long1 ) )
      .collect( Collectors.toCollection( HashSet<String>::new ) );
    
    

映射枚举

  • 名字相同,则直接映射即可
  • 名字不相同,使用 @ValueMapping进行映射,无法识别源值,抛出 IllegalStateExceptio
  • 如果有前缀,则使用如下4个属性进行映射
    • suffix- 在源枚举上应用后缀
    • stripSuffix- 从源枚举中去除后缀
    • prefix- 在源枚举上应用前缀
    • stripPrefix- 从源枚举中去除前缀
    public enum DegreeStream {MATHS}
    public enum DegreeStreamPrefix {MSC_MATHS}
    
    
    @Mapper
    public interface UserMapper {
      UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
            
      @EnumMapping(nameTransformationStrategy = "prefix", configuration = "MSC_")
      DegreeStreamPrefix convert(DegreeStream degreeStream);
    
      @EnumMapping(nameTransformationStrategy = "stripPrefix", configuration = "MSC_")
      DegreeStream convert(DegreeStreamPrefix degreeStreamPrefix);
    }
    
    

定义默认值或常量

  • defaultValue:当值不存在时,则使用默认值
  • constant:映射到目标枚举类型中具有相同名称的常量
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
        uses = {CollectionMapper.class, ManagerMapper.class, Validator.class},
        imports = UUID.class )
public interface UserMapper {
  UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

  @Mapping(source = "education.yearOfPassing", target = "education.passingYear",
           defaultValue = "2001")
  @Mapping(source = "employment", target = ".")
  @Mapping(target = "residentialCountry", constant = "US")
  PersonDTO convert(BasicUser user,
                    Education education,
                    Address address,
                    Employment employment);
}    

定义默认表达式

  • 用于使用java表达式
  • 在源属性为 null时使用,才会触发。
  • 还需要导入对应的类
    @Mapper( imports = UUID.class )
    public interface UserMapper {
      UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    
      @Mapping(source = "user.id", target = "id",
               defaultExpression = "java( UUID.randomUUID().toString() )")
      PersonDTO convert(BasicUser user,
                        Education education,
                        Address address,
                        Employment employment);
    }
    
    

映射器检索策略

  • 不使用依赖注入框架,使用 Mappers获取映射器实例
    @Mapper
    public interface UserMapper {
      UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    }
    // 使用
    PersonDTO personDTO = UserMapper.INSTANCE.convert(user);
    
  • 使用 @componentModel注解导入依赖注入
    • 支持CDI和Spring框架
      @Mapper(componentModel = "spring")
      public interface UserMapper {}
      
      // 生成如下:
      @Component
      public class UserMapperImpl implements UserMapper {}
      
      // 使用
      @Controller
      public class UserController() {
        @Autowired
        private UserMapper userMapper;
      }
      
      

映射定制

  • 使用装饰器模式,进行自定义
  • 使用 @BeforeMapping / @AfterMapping,进行通用的设置

装饰器

  • 定义一个Decorator类,在使用 @DecoratedWith使其生效
  • 对需要自定义映射的方法进行实现,其他的方法用默认实现生成对原始映射器的委托。
public abstract class UserMapperDecorator implements UserMapper {

  private final UserMapper delegate;

  protected UserMapperDecorator (UserMapper delegate) {
      this.delegate = delegate;
  }

  @Override
  public PersonDTO convert(BasicUser user,
                           Education education,
                           Address address,
                           Employment employment) {
    // 委托
    PersonDTO dto = delegate.convert(user, education, address, employment);
    if (user.getName().split("\\w+").length > 1) {
       dto.setFirstName(user.getName().substring(0, user.getName().lastIndexOf(' ')));
       dto.setLastName(user.getName().substring(user.getName().lastIndexOf(" ") + 1));
     }
     else {
        dto.setFirstName(user.getName());
     }
     return dto;
  }
}

// 使用
@Mapper
@DecoratedWith(UserMapperDecorator.class)
public interface UserMapper {
  UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    
  PersonDTO convert(BasicUser user, Education education, Address address, Employment employment);
}

@BeforeMapping和@AfterMapping

  • @BeforeMapping用于执行前,运行指定的逻辑
  • @AfterMapping用于执行后,运行指定的逻辑
@Mapper
@DecoratedWith(UserMapperDecorator.class)
public interface UserMapper {
  UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

  // 执行前, 如果manager为null则设置一个空集合
  @BeforeMapping
  default void validateMangers(BasicUser user) {
    if (Objects.isNull(user.getManagerList())) {
       user.setManagerList(new ArrayList<>());
    }
  }

  @Mapping(target = "residentialCountry", constant = "US")
  void updateExisting( Address address );

  // 执行后,对firstName和LastName进行一个字符转换
  @AfterMapping
  default void updateResult(BasicUser user, 
                            @MappingTarget PersonDTO personDTO) {
      personDTO.setFirstName(personDTO.getFirstName().toUpperCase());
      personDTO.setLastName(personDTO.getLastName().toUpperCase());
  }
}

reflectoring.io-> 文章
官网

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值