• mapstruct学习及使用较全



    注:

    • 没有提供对应的对象,自己实现,提高认识
    • 学习方式:最好是对class进行一个反编译,看看他生成的代码。如果发现一些类型没设置成功也可以通过反编译查看。
    • 反编译工具:java-decompiler
    
    <dependency>
      <groupId>org.mapstructgroupId>
      <artifactId>mapstructartifactId>
      <version>${org.mapstruct.version}version>
    dependency>
    <dependency>
      <groupId>org.mapstructgroupId>
      <artifactId>mapstruct-processorartifactId>
      <version>${org.mapstruct.version}version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    映射器定义

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

    基本映射

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

    自定义映射方法

    // 接口方式
    @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();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    从多个源对象映射

    • 当入参和返回的参数不匹配时,或者 有多个入参对象时,可以通过该方式指定需要映射到哪个字段
    • 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    映射嵌套对象

    • 当有嵌套对象时,可以采用@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);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    • 在生成方法时,会通过指定的嵌套类对这个嵌套对象进行映射,如下所示

      // 生成了一个managerListToManagerDTOList对嵌套类进行解析
      
      • 1

    更新现有实例

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

    继承配置

    • 对于重复的配置,使用@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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    逆映射

    • 想定义一个双向映射

      如:

      • 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);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

    映射期间的异常处理

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

    • 步骤:

      • 自定义异常

        public class ValidationException extends RuntimeException {
        
          public ValidationException(String message, Throwable cause) {
            super(message, cause);
          }
        
          public ValidationException(String message) {
            super(message);
          }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
      • 自定义校检规则

        • 方法名要求:validate字段名(字段类型)

        • 注意事项:校检的字段类型要和形参以及异常进行匹配,否则的话匹配不到则不会生效

        public class Validator {
          public int validateId(int id) throws ValidationException {
            if(id < 0){
              throw new ValidationException("Invalid ID value");
            }
            return id;
          }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
      • 使用

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

    数据类型转换

    隐式类型转换

    • 数值

      @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() ) );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    • 日期

      @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() ) );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

    映射集合

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

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

    • 简单使用

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

      @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() );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    • 对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() );
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

    映射策略

    • 默认值为ACCESSOR_ONLY

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4eI45Pl-1660918092350)(images/mapstruct - 集合映射策略.png)]

    // 使用ADDER_PREFERRED策略
    @Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
    public interface PersonMapperAdderPreferred {
      PersonDTO map(Person person);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    映射流

    • 和映射集合相同,只是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 ) );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    映射枚举

    • 名字相同,则直接映射即可

    • 名字不相同,使用@ValueMapping进行映射

      无法识别源值,抛出 IllegalStateException。

      public enum DesignationCode {CEO}
      public enum DesignationConstant {CHIEF_EXECUTIVE_OFFICER}
      
      @Mapper
      public interface UserMapper {    
        UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
        
        @ValueMappings({
                @ValueMapping(source = "CEO", target = "CHIEF_EXECUTIVE_OFFICER"),
        })
        DesignationConstant convertDesignation(DesignationCode code);
      }    
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 如果有前缀,则使用如下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);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

    定义默认值或常量

    • 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);
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    定义默认表达式

    • 用于使用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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    映射器检索策略

    • 不使用依赖注入框架,使用Mappers获取映射器实例

      @Mapper
      public interface UserMapper {
        UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
      }
      // 使用
      PersonDTO personDTO = UserMapper.INSTANCE.convert(user);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 使用@componentModel注解导入依赖注入

      • 支持CDI和Spring框架
      @Mapper(componentModel = "spring")
      public interface UserMapper {}
      
      // 生成如下:
      @Component
      public class UserMapperImpl implements UserMapper {}
      
      // 使用
      @Controller
      public class UserController() {
        @Autowired
        private UserMapper userMapper;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

    映射定制

    • 使用装饰器模式,进行自定义
    • 使用@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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    @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());
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    参考文献

  • 相关阅读:
    wordpress搭建自己的博客详细过程以及踩坑
    【iOS开发】(六)react Native 路由嵌套传参与框架原理(完)20240423
    FCOS目标检测 paper笔记
    5-羧基四甲基罗丹明标记磁性二氧化硅纳米粒TMR-PEG-SiO2
    Java8实战[第6章]用流收集数据groupingBy、partitioningBy、collectingAndThen、max、min、sum
    java计算机毕业设计个人网站设计源程序+mysql+系统+lw文档+远程调试
    Linux 的性能调优的思路
    sqlalchemy查询数据为空,查询范围对应的数据在数据库真实存在
    DRM全解析 —— CRTC详解(4)
    API接口文档1688阿里巴巴获取跨境属性数据
  • 原文地址:https://blog.csdn.net/qq_42191033/article/details/126432621