【Java生态圈技术总结】之深度剖析MapStruct对象拷贝工具(下)

简介: 【Java生态圈技术总结】之深度剖析MapStruct对象拷贝工具(下)

正文


2.2.6 集合属性映射


如果需要转换的Car对象中的某个属性不是基本数据类型,而是一个集合类型该怎么处理?


public class Car {
    private String brand;
    private Double price;
    private Boolean onMarket;
    private List<Person> ownerList;
    // setters + getters + toString
}

同2.2.5内容。


2.2.7 枚举映射


枚举映射的工作方式与字段映射相同。MapStruct会对具有相同名称的枚举进行映射。


public enum PayType {
    CASH,
    ALIPAY,
    WEPAY,
    DIGITAL_CASH,
    CARD_VISA,
    CARD_CREDIT;
}
public enum PayTypeNew {
    CASH,
    ALIPAY,
    WEPAY,
    DIGITAL_CASH,
    CARD_VISA,
    CARD_CREDIT;
}
@Mapper
public interface PayTypeMapper {
    PayTypeMapper INSTANCE = Mappers.getMapper(PayTypeMapper.class);
    PayTypeNew payTypeToPayTypeNew(PayType payType);
}
@Test
public void test2(){
    PayType p1 = PayType.ALIPAY;
    PayTypeNew p2 = PayTypeMapper.INSTANCE.payTypeToPayTypeNew(p1);
    // 结果为:ALIPAY
    System.out.println("结果为:" + p2);
}

但是在更多的场景下,源枚举和目标枚举并不是一一对应的,比如目标枚举如下:

public enum PayTypeNew {
    CASH,
    NETWORK,
    CARD;
}

此时,我们就需要手动指定源枚举和目标枚举之间的对应关系:

@Mapper
public interface PayTypeMapper {
    PayTypeMapper INSTANCE = Mappers.getMapper(PayTypeMapper.class);
    @ValueMappings({
            @ValueMapping(source = "ALIPAY", target = "NETWORK"),
            @ValueMapping(source = "WEPAY", target = "NETWORK"),
            @ValueMapping(source = "DIGITAL_CASH", target = "CASH"),
            @ValueMapping(source = "CARD_VISA", target = "CARD"),
            @ValueMapping(source = "CARD_CREDIT", target = "CARD")
    })
    PayTypeNew payTypeToPayTypeNew(PayType payType);
}

如果对应CARD的场景比较多,手动一个个地对应会比较繁琐,因此还有一种方式能实现相同的效果,而且比较简洁:

@Mapper
public interface PayTypeMapper {
    PayTypeMapper INSTANCE = Mappers.getMapper(PayTypeMapper.class);
    @ValueMappings({
            @ValueMapping(source = "ALIPAY", target = "NETWORK"),
            @ValueMapping(source = "WEPAY", target = "NETWORK"),
            @ValueMapping(source = "DIGITAL_CASH", target = "CASH"),
            @ValueMapping(source = MappingConstants.ANY_REMAINING, target = "CARD")
    })
    PayTypeNew payTypeToPayTypeNew(PayType payType);
}

MappingConstants.ANY_REMAINING表示剩下其它的源枚举和目标枚举对应不上的全部映射为指定的枚举对象。


还有一种方式,使用MappingConstants.ANY_UNMAPPED表示所有未显示指定目标枚举的都会被映射为CARD:

@Mapper
public interface PayTypeMapper {
    PayTypeMapper INSTANCE = Mappers.getMapper(PayTypeMapper.class);
    @ValueMappings({
            @ValueMapping(source = "ALIPAY", target = "NETWORK"),
            @ValueMapping(source = "WEPAY", target = "NETWORK"),
            @ValueMapping(source = "DIGITAL_CASH", target = "CASH"),
            @ValueMapping(source = MappingConstants.ANY_UNMAPPED, target = "CARD")
    })
    PayTypeNew payTypeToPayTypeNew(PayType payType);
}


2.2.8 集合映射


如果源对象和目标对象都是集合,且对象中的属性都是基本数据类型,则映射方法和之前类似,映射接口改为如下即可:


@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    List<CarDTO> carToCarDTOList(List<Car> carList);
    Set<CarDTO> carToCarDTOSet(Set<Car> carSet);
    Map<String, CarDTO> carToCarDTOMap(Map<String, Car> carMap);
}

如果对象中属性不仅是基本数据类型,还有对象类型或对象类型的集合类型的话,mapstruct也是支持映射的,详情参考官网文档,此处不再赘述。


2.3 转换


2.3.1 类型转换


mapstruct提供了基本数据类型和包装数据类型、一些常见场景下的自动转换;


  • 基本类型及其对应的包装类型之间的转换;比如int和Integer、float和Float、long和Long、boolean和Boolean等;
  • 任意基本类型和任意包装类型之间的转换;比如int和long、byte和Integer等;
  • 任意基本类型、包转类型和String之间的转换;比如boolean和String、Integer和String;
  • 枚举和String;
  • 大数类型(BigInteger、BigDecimal)、基本类型、基本类型包装类型、String之间的相互转换;
  • 其它一些场景,参考MapStruct 1.4.2.Final Reference Guide;


2.3.2 格式转换


mapstruct可以对源对象的属性值进行格式化之后拷贝给目标对象的属性;


日期格式转换

public class Car {
    private String brand;
    private Double price;
    private LocalDate marketDate;
    // setters + getters + toString
}
public class CarDTO {
    private String brand;
    private Double price;
    private String saleDate;
    // setters + getters + toString
}
@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    @Mapping(source = "marketDate", target = "saleDate", dateFormat = "dd/MM/yyyy")
    CarDTO carToCarDTO(Car car);
}
@Test
public void test1(){
    Car wuling = new Car();
    wuling.setBrand("wuling");
    wuling.setPrice(6666.66);
    wuling.setMarketDate(LocalDate.now());
    // 转换前为:Car{brand='wuling', price=6666.66, marketDate=2022-01-19}
    System.out.println("转换前为:" + wuling);
    CarDTO wulingDTO = CarMapper.INSTANCE.carToCarDTO(wuling);
    // 结果为:CarDTO{brand='wuling', price=6666.66, saleDate='19/01/2022'}
    System.out.println("结果为:" + wulingDTO);
}

数字格式转换

@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    @Mapping(source = "price", target = "price", numberFormat = "$#.00")
    CarDTO carToCarDTO(Car car);
}


2.4 高级特性


2.4.1 依赖注入


在前面例子中的Mapper映射接口中,我们都需要CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);来创建一个实例,如果我们是Spring工程的话,就可以把这个实例托管给Spring进行管理。


@Mapper(componentModel = "spring")
public interface CarMapper {
    List<CarDTO> carToCarDTOList(List<Car> carList);
}

然后使用的时候,从Spring中自动注入接口对象即可:


@SpringBootTest
public class TestMapper1 {
    @Autowired
    private CarMapper carMapper;
    @Test
    public void test1(){
        Car wuling = new Car();
        wuling.setBrand("wuling");
        wuling.setPrice(6666.66);
        wuling.setMarketDate(LocalDate.now());
        Car changan = new Car();
        changan.setBrand("changan");
        changan.setPrice(7777.77);
        changan.setMarketDate(LocalDate.now());
        List<Car> carList = new ArrayList<>();
        carList.add(wuling);
        carList.add(changan);
        List<CarDTO> carDTOList = carMapper.carToCarDTOList(carList);
        System.out.println("结果为:" + carDTOList);
    }
}


2.4.2 设置默认值


常量默认值

无论源对象的属性字段值是什么,目标对象的该字段都是给定的常量值。

@Mapper(componentModel = "spring")
public interface PersonMapper {
    @Mapping(target = "name", constant = "zhangsan")
    PersonDTO personToPersonDTO(Person person);
}


空值默认值

如果源对象的属性字段值为空,那么就使用指定的默认值。

@Mapper(componentModel = "spring")
public interface PersonMapper {
    @Mapping(source = "name", target = "name", defaultValue = "unknown")
    PersonDTO personToPersonDTO(Person person);
}


2.4.3 使用表达式


@Mapper(componentModel = "spring", imports = {UUID.class, LocalDateTime.class})
public interface PersonMapper {
    @Mapping(target = "id", expression = "java(UUID.randomUUID().toString())")
    @Mapping(source = "birthdate", target = "birthdate", defaultExpression = "java(LocalDateTime.now())")
    PersonDTO personToPersonDTO(Person person);
}

或者等价写法为:

@Mapper(componentModel = "spring")
public interface PersonMapper {
    @Mapping(target = "id", expression = "java(java.util.UUID.randomUUID().toString())")
    @Mapping(source = "birthdate", target = "birthdate", defaultExpression = "java(java.time.LocalDateTime.now())")
    PersonDTO personToPersonDTO(Person person);
}

2.4.4 前置及后置方法


@Mapper(componentModel = "spring")
public abstract class PersonMapper {
    @BeforeMapping
    public void before(Person person){
        System.out.println("前置处理!!!");
        if(ObjectUtils.isEmpty(person.getName())){
            System.out.println("Person的name不能为空!");
            return;
        }
    }
    @Mapping(target = "id", expression = "java(java.util.UUID.randomUUID().toString())")
    @Mapping(source = "birthdate", target = "birthdate", defaultExpression = "java(java.time.LocalDateTime.now())")
    public abstract PersonDTO personToPersonDTO(Person person);
    @AfterMapping
    public void after(@MappingTarget PersonDTO personDTO){
        System.out.println("后置处理:" + personDTO.getName() + "!!!");
    }
}


三、参考文献


https://mapstruct.org/documentation/stable/reference/html/#basic-mappings


https://zhuanlan.zhihu.com/p/368731266


https://zhuanlan.zhihu.com/p/366178118


四、补充填坑


在正文的实例中,我们对于Bean对象都是使用手动写getter、setter、toString方法的,但是在真实开发中,大家都是采用了Lombok插件,如果不做特殊配置,就会出现mapstruct运行时lombok不生效的问题,只需要在pom配置中增加如下内容:

<annotationProcessorPaths>
  <path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
   </path>                    
</annotationProcessorPaths>


相关文章
|
16小时前
|
Java C语言
Java输出技术详解
Java输出技术详解
|
16小时前
|
存储 Java
Java输入技术详解
Java输入技术详解
|
17小时前
|
算法 Java
Java循环嵌套技术深入探索
Java循环嵌套技术深入探索
|
17小时前
|
存储 Java 索引
Java二维数组的引用与操作技术详解
Java二维数组的引用与操作技术详解
|
23小时前
|
存储 Java 索引
Java中的数组复制技术详解
Java中的数组复制技术详解
|
23小时前
|
存储 搜索推荐 算法
Java中的数组排序技术详解
Java中的数组排序技术详解
|
23小时前
|
存储 Java API
Java数组元素的填充与替换技术详解
Java数组元素的填充与替换技术详解
|
23小时前
|
存储 Java 编译器
Java一维数组的初始化技术详解
Java一维数组的初始化技术详解
|
23小时前
|
存储 Java 索引
Java一维数组元素的引用与操作技术详解
Java一维数组元素的引用与操作技术详解
|
1天前
|
存储 Java
Java中带返回值方法的定义与调用技术
Java中带返回值方法的定义与调用技术