MapStruct【vaynexiao】

简介

在一个成熟的工程中,尤其是现在的分布式系统中,应用与应用之间,还有单独的应用细分模块之后,DO 一般不会让外部依赖,这时候需要在提供对外接口的模块里放 DTO 用于对象传输,也即是 DO 对象对内,DTO对象对外,DTO 可以根据业务需要变更,并不需要映射 DO 的全部属性。

这种 对象与对象之间的互相转换,就需要有一个专门用来解决转换问题的工具,毕竟每一个字段都 get/set 会很麻烦。MapStruct 就是为了解决这个 “写起来读起来很麻烦的事情” 的一个属性映射工具,只需定义Mapper 接口,MapStruct 就会自动实现这个接口,避免了复杂繁琐的映射实现。

BeanUtils 深入对比最后发现,BeanUtilscopyProperties 就是一个大老粗,只能同属性映射,或者在属性相同的情况下,允许被映射的对象属性少;但当遇到被映射的属性数据类型被修改或者被映射的字段名被修改,则会导致映射失败。而 mapstruct 就是一个巧媳妇儿了,她心思细腻,把我们可能会遇到的情况都给考虑到了
在这里插入图片描述
官方文档:https://mapstruct.org/documentation/stable/reference/html/
视频教程:https://www.bilibili.com/video/BV1E5411n7HR?p=1

pom

<properties>
    <mapstruct.version>1.2.0.Final</mapstruct.version>
</properties>

<dependencies>
    <dependency>
      <groupId>org.mapstruct</groupId>
      <artifactId>mapstruct-jdk8</artifactId>
      <version>${mapstruct.version}</version>
    </dependency>
    <dependency>
      <groupId>org.mapstruct</groupId>
      <artifactId>mapstruct-processor</artifactId>
      <version>${mapstruct.version}</version>
    </dependency>
    <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.16.18</version>
          <scope>provided</scope>
    </dependency>
</dependencies>

映射

一对一(包括List)

写一个 Mapper 接口 PersonConverter,其中两个方法,一个是单实体映射,另一个是List映射

若源对象属性与目标对象属性名字一致,会自动映射对应属性,不一样的需要指定,也可以用 format 转成自己想要的类型,也支持表达式的方式,可以看到像 id、name、email这些名词一致的我并没有指定 source-target,而birthday-birth指定了,转换格式的 birthDateFormat 加了dateFormat 或者 birthExpressionFormat 加了 expression,如果某个属性你不想映射,可以加个 ignore=true

@Mapper
public interface PersonConverter {
    
    
    PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
    @Mappings({
    
    
        @Mapping(source = "birthday", target = "birth"),
        @Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
        @Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),\"yyyy-MM-dd HH:mm:ss\"))"),
        @Mapping(source = "user.age", target = "age"),
        @Mapping(target = "email", ignore = true)
    })
    PersonDTO domain2dto(Person person);

    List<PersonDTO> domain2dto(List<Person> people);
}

编译MapStruct之后,手工编译或者启动 IDE 的时候 IDE 也会帮我们编译,
会自动在 target/classes 下生成对应的实现类PersonConverterImpl

手工编译命令 mvn compile

Test.java

public class PersonConverterTest {
    
    
    @Test
    public void test() {
    
    
        Person person = new Person(1L,"zhige","[email protected]",new Date(),new User(1));
        PersonDTO personDTO = PersonConverter.INSTANCE.domain2dto(person);
        String format = DateFormatUtils.format(personDTO.getBirth(), "yyyy-MM-dd HH:mm:ss");

        List<Person> people = new ArrayList<>();
        people.add(person);
        List<PersonDTO> personDTOs = PersonConverter.INSTANCE.domain2dto(people);
    }
}

多对一

MapStruct 可以将几种类型的对象映射为另外一种类型,比如将多个 DO 对象转换为 DTO

  • 两个 DO 对象 Item 和 Sku,一个 DTO 对象 SkuDTO
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Item {
    
    
    private Long id;
    private String title;
}

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Sku {
    
    
    private Long id;
    private String code;
    private Integer price;
}

@NoArgsConstructor
@AllArgsConstructor
@Data
public class SkuDTO {
    
    
    private Long skuId;
    private String skuCode;
    private Integer skuPrice;
    private Long itemId;
    private String itemName;
}

ItemConverter

@Mapper
public interface ItemConverter {
    
    
    ItemConverter INSTANCE = Mappers.getMapper(ItemConverter.class);

    @Mappings({
    
    
            @Mapping(source = "sku.id",target = "skuId"),
            @Mapping(source = "sku.code",target = "skuCode"),
            @Mapping(source = "sku.price",target = "skuPrice"),
            @Mapping(source = "item.id",target = "itemId"),
            @Mapping(source = "item.title",target = "itemName")
    })
    SkuDTO domain2dto(Item item, Sku sku);
}
  • 创建测试类,讲 Item 和 Sku 两个 DO对象,映射成一个 DTO 对象 SkuDTO
public class ItemConverterTest {
    
    
    @Test
    public void test() {
    
    
        Item item = new Item(1L, "iPhone X");
        Sku sku = new Sku(2L, "phone12345", 1000000);
        SkuDTO skuDTO = ItemConverter.INSTANCE.domain2dto(item, sku);
    }
}

Spring 注入

// 刚才一直写的例子是默认的方式
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);

还有一种常用的方式,是和常用的框架 Spring 结合,在 @Mapper 后面加入 componentModel="spring"

@Mapper(componentModel="spring")
public interface PersonConverter {
    
    
    @Mappings({
    
    
        @Mapping(source = "birthday", target = "birth"),
        @Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
        @Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),\"yyyy-MM-dd HH:mm:ss\"))"),
        @Mapping(source = "user.age", target = "age"),
        @Mapping(target = "email", ignore = true)
    })
    PersonDTO domain2dto(Person person);
}

这时候测试类改一下,我用的 spring boot 的形式

@RunWith(SpringRunner.class)
@SpringBootTest(classes = BaseTestConfiguration.class)
public class PersonConverterTest {
    
    
    //这里把转换器装配进来
    @Autowired
    private PersonConverter personConverter;
    @Test
    public void test() {
    
    
        Person person = new Person(1L,"zhige","[email protected]",new Date(),new User(1));
        PersonDTO personDTO = personConverter.domain2dto(person);

        String format = DateFormatUtils.format(personDTO.getBirth(), "yyyy-MM-dd HH:mm:ss");
    }
}

配置类

@EnableAutoConfiguration
@Configuration
@ComponentScan
public class BaseTestConfiguration {
    
    
}

注解和关键词

@Mapper 只有在接口加上这个注解, MapStruct 才会去实现该接口
    @Mapper 里有个 componentModel 属性,主要是指定实现类的类型,一般用到两个
    default:默认,可以通过 Mappers.getMapper(Class) 方式获取实例对象
    spring:在接口的实现类上自动添加注解 @Component,可通过 @Autowired 方式注入
@Mappings:配置多个@Mapping
@Mapping:属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性
    source:源属性
    target:目标属性
    dateFormat:String到Date日期之间相互转换,通过SimpleDateFormat,该值为SimpleDateFormat的日期格式
    ignore: 忽略这个字段

@MappingTarget 用于更新已有对象
@InheritConfiguration 用于继承配置

猜你喜欢

转载自blog.csdn.net/vayne_xiao/article/details/113807749
今日推荐