【Java】实体类转换框架 MapStruct

发布时间 2023-07-02 00:01:48作者: emdzz

简单尝试了下发现比Dozer还有BeanUtil还方便小巧

注解的作用是在生成字节码文件时实现具体GetterSetter方法,实际转换时就是赋值操作,嘎嘎快

 

参考文章:

https://juejin.cn/post/7140149801991012365

  

引入必须的依赖:

lombok一般项目都会添加,这里我就只放这两个

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.0.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.0.Final</version>
</dependency>

 

一、入门案例:

两个需要相互转换的实体类,演示用:

DTO

package cn.cloud9.server.test.mapstruct;

import lombok.*;
import lombok.experimental.Accessors;

@Data
@Builder
@ToString
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class DemoDTO {
    private Integer fieldA;
    private Boolean fieldB;
    private String fieldC;
}

Entity

package cn.cloud9.server.test.mapstruct;

import lombok.*;
import lombok.experimental.Accessors;

@Data
@Builder
@ToString
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class DemoEntity {
    private Integer fieldD;
    private Boolean fieldE;
    private String fieldF;
}

  

转换器接口编写:

如果需要转换更多的实体类,都可以在接口声明方法,然后声明映射的字段

package cn.cloud9.server.test.mapstruct;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;


/**
 * 转换器接口
 */
@Mapper
public interface DemoConverter {
    DemoConverter INSTANCE = Mappers.getMapper(DemoConverter.class);

    /* DTO -> ENTITY */
    @Mappings(value = {
        @Mapping(source = "fieldA", target = "fieldD"),
        @Mapping(source = "fieldB", target = "fieldE"),
        @Mapping(source = "fieldC", target = "fieldF"),
    })
    DemoEntity dto2entity(DemoDTO demoDTO);

    /* ENTITY -> DTO  */
    @Mappings(value = {
            @Mapping(source = "fieldD", target = "fieldA"),
            @Mapping(source = "fieldE", target = "fieldB"),
            @Mapping(source = "fieldF", target = "fieldC"),
    })
    DemoDTO entity2dto(DemoEntity demoEntity);
}

  

测试方法:

    @Test
    public void converterTest() {
        DemoDTO build = DemoDTO.builder()
                .fieldA(1001)
                .fieldB(Boolean.TRUE)
                .fieldC("TEST")
                .build();

        DemoEntity demoEntity = DemoConverter.INSTANCE.dto2entity(build);
        DemoDTO demoDTO = DemoConverter.INSTANCE.entity2dto(demoEntity);
        log.info("demoEntity -> {}", demoEntity);
        log.info("demoDTO -> {}", demoDTO);
    }

  

执行结果:

22:52:20.079 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoEntity -> DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST)
22:52:20.083 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoDTO -> DemoDTO(fieldA=1001, fieldB=true, fieldC=TEST)

Process finished with exit code 0

 

二、集合转换支持

也是根据参数类型推断,自动生成了迭代集合

    /* List<ENTITY -> DTO>  */
    @Mappings(value = {
            @Mapping(source = "fieldD", target = "fieldA"),
            @Mapping(source = "fieldE", target = "fieldB"),
            @Mapping(source = "fieldF", target = "fieldC"),
    })
    List<DemoDTO> entity2dto(List<DemoEntity> demoEntity);

    /* List<DTO -> ENTITY> */
    @Mappings(value = {
            @Mapping(source = "fieldA", target = "fieldD"),
            @Mapping(source = "fieldB", target = "fieldE"),
            @Mapping(source = "fieldC", target = "fieldF"),
    })
    List<DemoEntity> dto2entity(List<DemoDTO> demoDTO);

 测试方法:

ArrayList<DemoDTO> objects = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    objects.add(DemoDTO.builder()
            .fieldA(1001)
            .fieldB(Boolean.TRUE)
            .fieldC("TEST")
            .build());
}
List<DemoEntity> demoEntityList = DemoConverter.INSTANCE.dto2entity(objects);
demoEntityList.forEach(d -> log.info("de {}", d));

执行结果:

23:16:13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST)
23:16:13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST)
23:16:13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST)
23:16:13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST)
23:16:13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST)
23:16:13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST)
23:16:13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST)
23:16:13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST)
23:16:13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST)
23:16:13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST)

  

三、映射配置继承与反向继承

只需要提供一份映射注解信息即可,其他方法通过继承注解获取映射信息

同理,要反向转换时,提供了一个反向注解:

两个注解都需要你提供方法名来查找映射配置

package cn.cloud9.server.test.mapstruct;

import org.mapstruct.*;
import org.mapstruct.factory.Mappers;

import java.util.List;


/**
 * 转换器接口
 */
@Mapper
public interface DemoConverter {
    DemoConverter INSTANCE = Mappers.getMapper(DemoConverter.class);

    /* DTO -> ENTITY */
    @Mappings(value = {
        @Mapping(source = "fieldA", target = "fieldD"),
        @Mapping(source = "fieldB", target = "fieldE"),
        @Mapping(source = "fieldC", target = "fieldF"),
    })
    DemoEntity dto2entity(DemoDTO demoDTO);

    /* ENTITY -> DTO  */
    // @Mappings(value = {
    //         @Mapping(source = "fieldD", target = "fieldA"),
    //         @Mapping(source = "fieldE", target = "fieldB"),
    //         @Mapping(source = "fieldF", target = "fieldC"),
    // })
    // DemoDTO entity2dto(DemoEntity demoEntity);
    @InheritInverseConfiguration(name = "dto2entity")
    DemoDTO entity2dto(DemoEntity demoEntity);

    /* List<ENTITY -> DTO>  */
    // @Mappings(value = {
    //         @Mapping(source = "fieldD", target = "fieldA"),
    //         @Mapping(source = "fieldE", target = "fieldB"),
    //         @Mapping(source = "fieldF", target = "fieldC"),
    // })
    @InheritConfiguration(name = "entity2dto")
    List<DemoDTO> entity2dto(List<DemoEntity> demoEntity);

    /* List<DTO -> ENTITY> */
    // @Mappings(value = {
    //         @Mapping(source = "fieldA", target = "fieldD"),
    //         @Mapping(source = "fieldB", target = "fieldE"),
    //         @Mapping(source = "fieldC", target = "fieldF"),
    // })
    @InheritInverseConfiguration(name = "entity2dto")
    List<DemoEntity> dto2entity(List<DemoDTO> demoDTO);
}

  

 四、是否阻止默认映射

默认同名同类型字段直接映射

这里对两个实体类新加了同一个字段:

private Long filedG;

 

测试时添加该属性:

@Test
public void converterTest() {
    DemoDTO build = DemoDTO.builder()
            .fieldA(1001)
            .fieldB(Boolean.TRUE)
            .fieldC("TEST")
            .filedG(3003L)
            .build();
    DemoEntity demoEntity = DemoConverter.INSTANCE.dto2entity(build);
    DemoDTO demoDTO = DemoConverter.INSTANCE.entity2dto(demoEntity);
    log.info("demoEntity -> {}", demoEntity);
    log.info("demoDTO -> {}", demoDTO);
}

执行结果可以发现,默认赋值上去了

23:34:51.780 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoEntity -> DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST, filedG=3003)
23:34:51.787 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoDTO -> DemoDTO(fieldA=1001, fieldB=true, fieldC=TEST, filedG=3003)

Process finished with exit code 0

  

使用@BeanMapping注解可以阻止这种赋值行为:

/**
 * 转换器接口
 */
@Mapper
public interface DemoConverter {
    DemoConverter INSTANCE = Mappers.getMapper(DemoConverter.class);


    /* @BeanMapping(ignoreByDefault = true) 阻止MapStruct默认同名字段赋值行为 */
    @BeanMapping(ignoreByDefault = true)
    /* DTO -> ENTITY */
    @Mappings(value = {
        @Mapping(source = "fieldA", target = "fieldD"),
        @Mapping(source = "fieldB", target = "fieldE"),
        @Mapping(source = "fieldC", target = "fieldF"),
    })
    DemoEntity dto2entity(DemoDTO demoDTO);

    /* ENTITY -> DTO  */
    @InheritInverseConfiguration(name = "dto2entity")
    DemoDTO entity2dto(DemoEntity demoEntity);

    /* List<ENTITY -> DTO>  */
    @InheritConfiguration(name = "entity2dto")
    List<DemoDTO> entity2dto(List<DemoEntity> demoEntity);

    /* List<DTO -> ENTITY> */
    @InheritInverseConfiguration(name = "entity2dto")
    List<DemoEntity> dto2entity(List<DemoDTO> demoDTO);
}

  

再次执行发现不再能够进行赋值处理了:

23:38:03.011 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoEntity -> DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST, filedG=null)
23:38:03.015 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoDTO -> DemoDTO(fieldA=1001, fieldB=true, fieldC=TEST, filedG=null)

  

是否不继承@BeanMapping忽略默认映射的配置?

https://www.bilibili.com/video/BV1E5411n7HR?p=9

在视频中提到是不继承的,这里我加上了字段的赋值,并且调用反向转换的方法:

DemoDTO build = DemoDTO.builder()
        .fieldA(1001)
        .fieldB(Boolean.TRUE)
        .fieldC("TEST")
        .fieldG(3003L)
        .build();
DemoEntity demoEntity = DemoConverter.INSTANCE.dto2entity(build);
demoEntity.setFieldG(3003L);
DemoDTO demoDTO = DemoConverter.INSTANCE.entity2dto(demoEntity);
log.info("demoEntity -> {}", demoEntity);
log.info("demoDTO -> {}", demoDTO);

 可以从执行结果发现,实际上是没有赋值的,说明是继承了@BeanMapping的效果了

23:41:02.989 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoEntity -> DemoEntity(fieldD=1001, fieldE=true, fieldF=TEST, fieldG=3003)
23:41:02.994 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoDTO -> DemoDTO(fieldA=1001, fieldB=true, fieldC=TEST, fieldG=null)

Process finished with exit code 0

  

五、注册成SpringBean

在前面的用法中都是直接声明一个单例使用,这里可以交给Spring处理:

@Mapper(componentModel = "spring")

package cn.cloud9.server.test.mapstruct;

import org.mapstruct.*;
import org.mapstruct.factory.Mappers;

import java.util.List;


/**
 * 转换器接口
 */
@Mapper(componentModel = "spring")
public interface DemoConverter {
    DemoConverter INSTANCE = Mappers.getMapper(DemoConverter.class);


    /* @BeanMapping(ignoreByDefault = true) 阻止MapStruct默认同名字段赋值行为 */
    // @BeanMapping(ignoreByDefault = true)
    /* DTO -> ENTITY */
    @Mappings(value = {
        @Mapping(source = "fieldA", target = "fieldD"),
        @Mapping(source = "fieldB", target = "fieldE"),
        @Mapping(source = "fieldC", target = "fieldF"),
    })
    DemoEntity dto2entity(DemoDTO demoDTO);

    /* ENTITY -> DTO  */
    @InheritInverseConfiguration(name = "dto2entity")
    DemoDTO entity2dto(DemoEntity demoEntity);

    /* List<ENTITY -> DTO>  */
    @InheritConfiguration(name = "entity2dto")
    List<DemoDTO> entity2dto(List<DemoEntity> demoEntity);

    /* List<DTO -> ENTITY> */
    @InheritInverseConfiguration(name = "entity2dto")
    List<DemoEntity> dto2entity(List<DemoDTO> demoDTO);
}

  

在需要调用的地方声明即可:

package cn.cloud9.server.test.controller;

import cn.cloud9.server.test.mapstruct.DemoConverter;
import cn.cloud9.server.test.mapstruct.DemoDTO;
import cn.cloud9.server.test.mapstruct.DemoEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/test/map-struct")
public class MapStructController {

    @Resource
    private DemoConverter demoConverter;

    @GetMapping("/get")
    public DemoEntity converterTest() {
        DemoDTO build = DemoDTO.builder()
                .fieldA(1001)
                .fieldB(Boolean.TRUE)
                .fieldC("TEST")
                .fieldG(3003L)
                .build();
        return demoConverter.dto2entity(build);
    }
}

 

测试结果: