利用mapStruct 进行pojo类的转换

522 阅读5分钟

利用mapStruct 进行pojo类的转换

介绍

what

MapStruct 是一个代码生成器,它极大地简化了基于约定而不是配置方法的 Java Bean 类型之间映射的实现。生成的映射代码使用普通方法调用,因此快速、类型安全且易于理解。

why

多层应用程序通常需要在不同的对象模型(例如实体和 DTO)之间进行映射。编写此类映射代码是一项繁琐且容易出错的任务。MapStruct旨在通过尽可能地自动化来简化这项工作。对比BeanUtils ,该框架使用get、set方式实现,相较于反射效率更高,同时,支持特殊的映射关系,比BeanUtils更加灵活。

与其他映射框架相比,MapStruct 在编译时生成 Bean 映射,从而确保高性能,允许快速的开发人员反馈和彻底的错误检查。

how

MapStruct是一个注释处理器,它插入到Java编译器中,可以在命令行构建(Maven,Gradle等)以及IDE中使用。

官网:MapStruct – Java bean mappings, the easy way!

添加依赖

方案一 直接引入

      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>org.mapstruct</groupId>
          <artifactId>mapstruct</artifactId>
          <version>1.4.2.Final</version>
      </dependency>
      <dependency>
          <groupId>org.mapstruct</groupId>
          <artifactId>mapstruct-processor</artifactId>
          <option>true</option>
          <version>1.4.2.Final</version>
      </dependency>

Notice:

  <dependency>
      <groupId>org.mapstruct</groupId>
      <artifactId>mapstruct-jdk8</artifactId>
      <version>${mapstruct.version}</version>
  </dependency>

很多博客上写的 mapstruct-jdk8 坐标已在新版本中废弃


方案二 通过插件引入(官网)

注意: 新版本中 引入loombook需要同时添加 lombok-mapstruct-binding

  <properties>
      <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
  </properties>
  ...
  <dependencies>
      <dependency>
          <groupId>org.mapstruct</groupId>
          <artifactId>mapstruct</artifactId>
          <version>${org.mapstruct.version}</version>
      </dependency>
  </dependencies>
  ...
  <build>
      <plugins>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.8.1</version>
              <configuration>
                  <source>1.8</source> <!-- depending on your project -->
                  <target>1.8</target> <!-- depending on your project -->
                  <annotationProcessorPaths>
                      <path>
                          <groupId>org.mapstruct</groupId>
                          <artifactId>mapstruct-processor</artifactId>
                          <version>${org.mapstruct.version}</version>
                      </path>
                      <!-- 引用loombook需要额外引入的 -->
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${org.projectlombok.version}</version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok-mapstruct-binding</artifactId>
                            <version>0.2.0</version>
                        </path>
                  </annotationProcessorPaths>
              </configuration>
          </plugin>
      </plugins>
  </build>

IDE配置

官网地址:IDE Support – MapStruct

另外 idea可以考虑安装MapStructSupport插件,在设置mapping的时候很好用

image-20220511181318198.png

使用方法及DEMO

1.创建POJO

  @Data
  @NoArgsConstructor
  @AllArgsConstructor
  @Builder
  @ApiModel("用户卡新增修改")
  public class AddModifyUserCardVo implements Serializable {
      private static final long serialVersionUID = -7504607649930524455L;
  •
      @ApiModelProperty(value = "密码", example = "123456", required = true, position = -1, notes = "传加密后的密文,默认MD5(加密后密文是32位)加密方式")
      private String password;
  •
      @ApiModelProperty(value = "", example = "风之诗", required = false)
      private String tradeAccountName;
  •
      @ApiModelProperty(value = "电话号码", example = "15770634444", required = false)
      private String phoneNo;
  •
      @ApiModelProperty(value = "通讯地址", example = "xx省xx市xx区xxx", required = false)
      private String adddress;
  }
  /**
   * 新增卡信息
   */
  @ApiModel("客户卡信息")
  @Data
  public class CustUserParam implements Serializable {
  •
      private static final long serialVersionUID = -5708253734967403586L;
      @ApiModelProperty(value = "密码", example = "123456", required = true, position = -1, notes = "传加密后的密文")
      private String password;
  •
      @ApiModelProperty(value = "", example = "风之诗", required = false)
      private String userName;
  •
      @ApiModelProperty(value = "电话号码", example = "15770634444", required = false)
      private String phoneNo;
  •
      @ApiModelProperty(value = "通讯地址", example = "xx省xx市xx区xxx", required = false)
      private String adddress;
  }

2.创建转换Interface

  @Mapper
  public interface BankCardMapper {
      BankCardMapper INSTANCT = Mappers.getMapper(BankCardMapper.class);
  }

3.将Interface注入Spring


  @Mapper(componentModel="spring") // 接口注入Spring,相当于加了一个@Component
  public interface BankCardMapper {
      /**
       * 接口实现类的实例
       */
      BankCardMapper INSTANCT = Mappers.getMapper(BankCardMapper.class);
      CustBankParam addCardParamConvert(AddModifyBankCardVo addModifyBankCardVo);
  }

componentModel 有四个可选项:

default: 默认 不注入,使用 Mappers.getMapper获取实例对象

spring:(常用)实现类上添加了@Component

cdi:

jsr330:生成的实现类上会添加@javax.inject.Named 和@Singleton,可以通过 @Inject注解获取

4.添加转换接口

  @Mapper(componentModel="spring") // 
  public interface BankCardMapper {
      CustBankParam addCardParamConvert(AddModifyBankCardVo addModifyBankCardVo);
  }
      

5.在Servce层使用接口

注入mapper;

  @Autowired
  private BankCardMapper bankCardMapper;
  public void dealService(AddModifyBankCardVo addModifyBankCardVo){
       // ... 业务逻辑
  CustBankParam custBankParam = bankCardMapper.addCardParamConvert(addModifyBankCardVo);
      // ... 业务逻辑
  }

6. 特殊转换

6.1 使用Mapping对属性进行特殊处理

 @Mappings({
             @Mapping(source = "tradeAccountName",target = "cusName"),// 不同名称之间转换
             @Mapping(target = "password", ignore=true),//忽略密码,不进行映射
             @Mapping(target = "createDt", expression = "java(new java.util.Date())"),//支持表达式 
             // 注意: 这个属性不能与source()、defaultValue()、defaultExpression()(可与source共同使用,当source的值为null的时候使用该表达式)、qualifiedBy()、qualifiedByName()或constant()一起使用。
             
             @Mapping(target = "updateDt", dateFormat=”yyyy-MM-dd“),//时间戳转换
             @Mapping(target = "captalMode", defaultValue="1"),//设置默认值,可设置source
         })
 CustBankParam addCardParamConvert(AddModifyBankCardVo addModifyBankCardVo);

6.2 逆映射

在双向映射的情况下,例如从实体到DTO以及从DTO到实体,前向方法和反向方法的映射规则通常是相似的,并且可以通过切换source和来简单地反转target。

使用注释 @InheritInverseConfiguration 表示方法应继承相应反向方法的反向配置

     CustBankParam addCardParamConvert(AddModifyBankCardVo addModifyBankCardVo);
 
     @InheritInverseConfiguration
     AddModifyBankCardVo addModifyBankCardVoConvert(CustBankParam addModifyBankCardVo);

1)有一个注解@InheritConfiguration(下文提到)与该注解长得很像,请注意区分,虽然他们的功能有点相似,但是他们是不同的注解; 2)该注解虽然可以实现反转赋值,但是有一种情况需要手动加强——原映射的Mapping中没有source(典型的使用了expression、constant属性的都属于)。对于有这种情况的属性,原映射与新映射都需要指定出来;

3)该注解只有一个属性name,它的值是原映射名。你可以把它理解为原映射对应的方法的名字,即方法的名字就是映射名。如上面的例子,规则名是"addCardParamConvert"。那该注解可以这样写@InheritInverseConfiguration(name="addCardParamConvert")

6.3利用已有的对象映射更新对象 @InheritConfiguration

使用方法与6.2类似,但功能上有些有差异,上面已经介绍,这里注意区分即可。

6.4(补充)公共属性的映射

对于我们平时新增、修改等操作,需要对修改create_time、update_time 等字段,有人说在修改数据库的时候使用mybatis-plus的自动填充即可,这也是个比较好的方案。可惜的是该框架不在我们公司的技术选型范围内,所以打算在类的转换上想办法了,如果在mapstruct上配置,这些字段需要对每个接口都配置一次,这显得很麻烦。能不能配置一个公共的方法就显得很重要。于是翻阅文档,发现了一个比较好玩的实验性功能: mapstruct.org/documentati…

上代码DEMO:

 /**
  * 新增时使用注解,入参需要添加 LoginUser loginUser
  */
 @Retention(RetentionPolicy.CLASS)
 @Mappings({
         @Mapping(target = "createdTime",expression = "java(new java.sql.Timestamp(System.currentTimeMillis()))"),
         @Mapping(target = "updatedTime",expression = "java(new java.sql.Timestamp(System.currentTimeMillis()))"),
         @Mapping(target = "createdBy",source = "loginUser.unionUserId"),
         @Mapping(target = "updatedBy",source = "loginUser.unionUserId"),
         @Mapping(target = "tenantId",source = "loginUser.tenantId"),
         @Mapping(target = "revision",expression = "java(new Integer(1))")
 })
 public @interface InsertMapper {
 }

使用:

 Mapper(componentModel = "spring")
 public interface EduActivityMapper {

     @InsertMapper
     EduActivity insertEduActivity(EduActivityReq eduActivityReq, LoginUser loginUser);

后记

mapstruct的使用比起传统的BeanUtils更加灵活效率更高,但是每次转换都要新增一个接口,这是一个缺点吧,后续在使用中有什么心得,还会继续补充 不过最重要的还是要经常翻阅一下API吧