MapStruct 的使用

关于微服务之间的数据分层问题,主要分为三种:

  • DO

  • DTO

  • VO

DO:即 Data Object,是和数据库一一对应的 Java 实体,在 mapper 层传递

DTO:即 Data Transfer Object,删掉了 DO 中一些不需要的字段,在 service 层或 manager 层传输。比如在 service 层中,接收来自 mapper 层的 DO 数据,返回给别的 service 层 DTO 数据,返回给 controller 层 VO 数据。

VO:即 View Object,专门展示给前端的数据对象,是 service 层传给 controller 层的数据,controller 层再给前端的数据。

使用这三种类型的而不是简单得只用 DO ,可以有效得隔离字段,解耦清晰,和友好展示。在学习阶段或小项目中,一般不引入 DTO,在 service 层中,也可只使用 DO ,返回给别的 service 层以 DO ,但依旧返回给 controller 层 VO。

使用这几个数据,势必需要进行转换,通常情况下,有三种常见的转换方式:

  1. 手动转换

  2. 使用 BeanUtils 转换

  3. 使用 MapStruct 转换

第一种,手动转换,类似于:

public UserVO convertToVO(UserDO userDO) {
   UserVO vo = new UserVO();
   vo.setId(userDO.getId());
   vo.setUsername(userDO.getUsername());
   vo.setEmail(userDO.getEmail());
   return vo;
}

优先就是字段可以灵活按照自己的想法去处理,缺点也很明显,就是代码繁杂,容易出错、难维护,所以平时也不太会用这种。

第二种,使用 BeanUtils ,这是 Spring 自带的对象转换的工具,样例代码如:

import org.springframework.beans.BeanUtils;

UserVO vo = new UserVO();
BeanUtils.copyProperties(userDO, vo);

优点是快速、简单,代码量少,也不用创建新的转换类。缺点是这种方法只能自动拷贝同名且同数据类型的字段,要想有特殊的处理,只能手动去处理。

第三种,使用 MapStruct 转换,是目前在 Java 微服务中最主流的转换方式之一,优点是支持不同字段、不同类型的复杂映射,性能高、易维护,在编译期生成代码,运行时不会反射影响性能。

下面就来详细介绍这个 MapStruct 的使用。

  1. 引入依赖

    这里的依赖可不少,主要是因为不能单独引入 MapStruct ,还要引入 Lombok 依赖,并且把他们两个绑定,不然 MapStruct 识别不了 Lombok 自动生成的 set/get 方法。


    <dependency>
       <groupId>org.mapstructgroupId>
       <artifactId>mapstructartifactId>
       <version>1.5.5.Finalversion>
    dependency>


    <dependency>
       <groupId>org.projectlombokgroupId>
       <artifactId>lombokartifactId>
    dependency>


    <dependency>
       <groupId>org.projectlombokgroupId>
       <artifactId>lombok-mapstruct-bindingartifactId>
       <version>0.2.0version>
       <scope>providedscope>
    dependency>


    <dependency>
       <groupId>org.mapstructgroupId>
       <artifactId>mapstruct-processorartifactId>
       <version>1.5.5.Finalversion>
       <scope>providedscope>
    dependency>

    除了依赖以外,还要在 pom 文件的依赖后面,加入 maven-compiler-plugin ,直接赋值下面的代码就好,注意 标签是和 标签同级的,而且要在它的下面。

    <build>
       <plugins>
           <plugin>
               <groupId>org.apache.maven.pluginsgroupId>
               <artifactId>maven-compiler-pluginartifactId>
               <version>3.11.0version>
               <configuration>
                   <source>21source>
                   <target>21target>
                   <annotationProcessorPaths>
                       <path>
                           <groupId>org.projectlombokgroupId>
                           <artifactId>lombokartifactId>
                           <version>${lombok.version}version>
                       path>
                       <path>
                           <groupId>org.mapstructgroupId>
                           <artifactId>mapstruct-processorartifactId>
                           <version>1.5.5.Finalversion>
                       path>
                       <path>
                           <groupId>org.projectlombokgroupId>
                           <artifactId>lombok-mapstruct-bindingartifactId>
                           <version>0.2.0version>
                       path>
                   annotationProcessorPaths>
               configuration>
           plugin>
       plugins>
    build>
  2. 创建转换器接口

    // 我们目前是 SpringBoot 项目,交由 spring 管理,这样才能在后面注入
    @Mapper(componentModel = "spring")
    public interface CategoryConverter {
       /**
        * DO → VO
        */
       CategoryVo doToVo(CategoryDo categoryDo);
    }
  3. 使用

    @SpringBootTest
    public class ConverterTest {
       @Resource
       private CategoryConverter categoryConverter;

       @Test
       public void testDoToVo(){
           CategoryDo categoryDo = new CategoryDo();
           categoryDo.setCatId(1L);
           // 调用方法转化
           CategoryVo categoryVo = categoryConverter.doToVo(categoryDo);
    // 打印
           System.out.println(categoryVo);
      }
    }
  4. 小技巧1:忽略字段映射

    某些字段,并不希望给他赋值,那么在转换器对应的方法上,加入如下代码:

    @Mapper(componentModel = "spring")
    public interface CategoryConverter {
       // 加入 Mapping 注解,忽略字段,这里是:忽略 CategoryVo 对象的 id 字段,不赋值。
       @Mapping(target = "id", ignore = true)
       CategoryVo doToVo(CategoryDo categoryDo);
    }

    target 表示目标对象,ignore 表示忽略赋值。

  5. 小技巧2:字段名不同的赋值映射

    这个技巧,可以在两个要转换的对象之间,即使名字不一样,也可以转换。

    @Mapper(componentModel = "spring")
    public interface CategoryConverter {
       // 加入 Mapping 注解,这里是:将 source(CategoryDo)中的 catId,映射到 target(CategoryVo)中的 id。
       @Mapping(target = "id",source = "catId")
       CategoryVo doToVo(CategoryDo categoryDo);
    }
  6. 小技巧3:直接设置特定值

    • 若源属性值为null,设立一个默认值

      对应方法上加入如下注解:

      // 意思是,source(CategoryDo)中的 catId 若为空,则将 target(CategoryVo)中的 id 设置为 10086
      @Mapping(target = "id", source = "catId", defaultValue = "10086")
    • 若源属性值不为null,永远等于一个值

      对应方法上加入如下注解:

      // 意思是,将 target(CategoryVo)中的 id 设置为固定值:96211
      @Mapping(target = "id", constant = "96211")

至此,这些小技巧足以应对大部分的情况了,还有 表达式映射、自定义映射等,可查阅 官方文档 查看。


  • 微信
  • 赶快加我聊天吧
  • QQ
  • 赶快加我聊天吧
  • weinxin
三桂

发表评论 取消回复 您未登录,登录后才能评论,前往登录