mapstruct学习及使用详解

2023-05-16

  • 映射器定义
    • 基本映射
    • 自定义映射方法
    • 从多个源对象映射
    • 映射嵌套对象
    • 更新现有实例
    • 继承配置
    • 逆映射
    • 映射期间的异常处理
  • 数据类型转换
    • 隐式类型转换
    • 映射集合
    • 映射策略
    • 映射流
    • 映射枚举
    • 定义默认值或常量
    • 定义默认表达式
  • 映射器检索策略
  • 映射定制
    • 装饰器
    • @BeforeMapping和@AfterMapping
  • 参考文献

注:

  • 没有提供对应的对象,自己实现,提高认识
  • 学习方式:最好是对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

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

// 使用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进行映射

    无法识别源值,抛出 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);
    }    
    
  • 如果有前缀,则使用如下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-文章
  • 官网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

mapstruct学习及使用详解 的相关文章

随机推荐

  • 关于汉字转拼音并排序解决方案

    使用方法 xff1a 写一个静态帮助类 span class token keyword public span span class token keyword static span span class token keyword c
  • .NET Core6 中使用AutoMapper

    1 引入AutoMapper包 2 新建一个类 xff1a MappingProfile xff0c 类名自定义 xff0c 但是必须要继承 Profile类 用于创建映射规则 如图 xff1a Student为源数据 xff08 我这里是
  • 使用Python调用百度OCR

    使用Python调用百度OCR 注册 登录百度智能云创建应用安装python SDK接口说明代码实现 xff08 本地图片 xff09 代码实现 xff08 使用url上的图片并使用可选参数 xff09 注册 登录百度智能云 注册请点击 登
  • 001 超全C语言程序设计概念

    前言 此笔记主要参考自赵海英老师的C语言课程 xff0c 此笔记是在考研重新学习C语言的情况下进行的整理 xff0c 主要用于后续的C语言概念温故知新 第一章 基础知识 1 数制及转换 四种数制 xff1a 二进制 十进制 八进制 十六进制
  • 使用@Autowired注解警告Field injection is not recommended

    问题 xff1a 在使用变量方式依赖注入时 xff0c 提示Field injection is not recommended 64 Autowired LogService logService 虽然变量方式注入非常简洁 xff0c 但
  • mybatis动态数据源,分页插件失效

    mybatis动态数据源 xff0c 分页插件失效 发表于 xff1a 2020 08 18 20 42 47 阅读量 xff1a 9 作者 xff1a 黄叶 原因 xff1a 使用动态数据源 xff1a 数据正常但是total为0 解决
  • mybatis动态数据源配置使用事务不生效

    原因 xff1a 因为我使用的是配置的方式来加载数据源 xff0c 因此我们还需要对事务管理器进行一个配置 解决 xff1a 在代码中添加 配置事物 64 param dataSource 64 return 64 Bean public
  • Caffeine cache实现本地缓存(简单又清楚)

    Caffeine cache实现本地缓存题 缓存填充策略 手动加载 介绍 xff1a 使用方式 xff1a 同步加载 介绍 xff1a 使用方式 xff1a 异步加载 介绍 xff1a 注意 xff1a 异步和同步使用方式相似 这里的话主要
  • 商城后台系统 — 新手练习 —毕业设计

    商城后台系统 新手练习 毕业设计 业务功能介绍项目地址 xff1a 一 商品管理1 商品列表 描述 效果 2 添加商品 描述 效果 3 商品分类 描述 效果 4 商品类型 描述 效果 二 订单管理1 订单列表 描述 效果 2 订单设置 描述
  • CASE WHEN函数@sql学习

    mysql中可以使用CASE WHEN函数完成数据分组 CASE WHEN函数用来对数据进行判断和分组 来自MySQL触发器里的流程控制语句 知识 CASE WHEN是SQL编程中常用的条件控制语句 CASE WHEN的功能 xff1a 新
  • @Autowired注入为null — 4种常见情况

    64 Autowired注入为null 情况一 使用过滤器 原因解决 情况二 没有添加注解 原因解决 情况三 xff08 没有被扫描到 xff09 原因解决 情况四 xff08 手动new xff09 原因解决 情况一 使用过滤器 原因 因
  • TDD项目实战-命令行参数解析

    认识1 基本规则2 三步骤3 任务分解法总结 项目1命令行参数解析01 任务分解法与整体工作流程1 API 构思与组件划分2 功能分解与任务列表3 红绿灯循环 02 识别坏味道与代码重构1 引入多态接口2 使用 抽象工厂 模式的变体来替换分
  • mapper扫描问题(Invalid bound statement (not found))

    分析 xff1a 通常来说这种情况是mybatis没有配置好 但是还有一种可能是你的mapperscan扫描问题 解决 xff1a 使用这个的时候应该扫描的是mapper层 如果我们用成全局的扫描 xff08 根目录 xff09 xff0c
  • 找不到org.springframework.cloud.client.loadbalancer.reactive.OnNoRibbonDefaultCondition

    原因 xff1a 该类存在于spring cloud commons jar 引用的jar包存在冲突 新版本的spring cloud commons中取消了OnNoRibbonDefaultCondition类 解决 xff1a 引入依赖
  • Parameter 0 of method modifyRequestBodyGatewayFilterFactory in org.springfra..问题

    原因 xff1a 依赖冲突 解决 xff1a 例如我的是spring cloud starter gateway和spring boot starter web和spring boot starter webflux依赖冲突排除 sprin
  • 重构 - 消除重复的new创建

    如下 xff1a 有时会遇到这种重复的new创建 span class token keyword public span span class token keyword class span span class token class
  • IDEA快捷键-重构

    文章目录 重构项目案例参考重构技巧1 消除重复new创建重构技巧2 提炼函数 xff0c 消除重复计算 提炼函数提炼变量搬移函数inlIne使用 xff08 内联 xff09 inLine重构局部变量inLine重构方法 重构重构菜单栏ID
  • 重构 - 提炼函数,消除重复代码

    一 参考资料二 重构步骤 以提炼重复计算函数为例子演示代码具体步骤1 提取重复new创建2 提取会变化的信息3 使用抽取的共有信息 xff0c 并删除原有信息4 提取计算函数5 使用卫语句 xff0c 简化代码逻辑 一 参考资料 重构 2
  • 模板方法 + 工厂变体消除重复if else

    模板方法 43 工厂消除重复if else 1 将重复代码 xff0c 抽取到抽象类中2 子类实现抽象类3 使用工厂获取对象 思维导图 xff1a 示例代码 xff1a 1 将重复代码 xff0c 抽取到抽象类中 span class to
  • mapstruct学习及使用详解

    映射器定义基本映射自定义映射方法从多个源对象映射映射嵌套对象更新现有实例继承配置逆映射映射期间的异常处理 数据类型转换隐式类型转换映射集合映射策略映射流映射枚举定义默认值或常量定义默认表达式 映射器检索策略映射定制装饰器 64 Before