mapstruct对象复制&转换

mapstruct对象复制&转换

  • mapstruct对象复制&转换
    • 简介
    • 使用步骤简述
      • 第一步:引入相关依赖
      • 第二步:定义Mapper转换器
      • 第三步:使用Mapper转换器
    • 获取Mapper实例的方式
      • default模式
      • spring模式
      • cdi模式和jsr330模式
    • 常用知识点
      • target是新对象
      • target是已有对象
      • 字段名不同时
      • 指定默认值
      • 常量值
      • 多个字段映射
      • 多级字段定位
      • 多个source
      • 字段大小写不同时
      • 忽略字段
      • 枚举与字符串
      • 枚举与枚举
      • 隐式类型(自动)转换
      • 复用spring-bean进行转换支持
      • 自定义字段转换逻辑 - expression指定静态方法
      • 自定义字段转换逻辑 - expression指定spring-bean的实例方法
      • 自定义字段转换逻辑 - 提供已存在的转换方法
    • 相关资料

简介

从功能上来讲,mapstruct是一款类似于BeanUtils.copyProperties(Object source, Object target)一样,实现对象属性值复制的;从实现上来讲,mapstruct是一款类似于lombok,基于你给出的方法入参出参模型及方法、类上的相关辅助注解,直接在编译时生成对应的属性值转换实现类。因为mapstruct是使用自动生成的代码实现的对象属性值赋值(而不是像BeanUtils一样采用反射获取值赋值),所以性能更快、效率更高。更多详见官网。

使用步骤简述

第一步:引入相关依赖

...
<properties>
    <org.mapstruct.version>1.4.2.Finalorg.mapstruct.version>
properties>
...
<dependencies>
    <dependency>
        <groupId>org.mapstructgroupId>
        <artifactId>mapstructartifactId>
        <version>${org.mapstruct.version}version>
    dependency>
dependencies>
...
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.pluginsgroupId>
            <artifactId>maven-compiler-pluginartifactId>
            <version>3.8.1version>
            <configuration>
                <source>1.8source>
                <target>1.8target>
                <annotationProcessorPaths>
                    <path>
                        
                        <groupId>org.projectlombokgroupId>
                        <artifactId>lombokartifactId>
                        <version>${lombok.version}version>
                    path>
                    <path>
                        <groupId>org.mapstructgroupId>
                        
                        <artifactId>mapstruct-processorartifactId>
                        <version>${org.mapstruct.version}version>
                    path>
                annotationProcessorPaths>
                
                <showWarnings>trueshowWarnings>
                <compilerArgs>
                    <arg>
                        -Amapstruct.verbose=true
                    arg>
                compilerArgs>
                
            configuration>
        plugin>
    plugins>
build>
...

第二步:定义Mapper转换器

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper
public interface CarMapper {
    
    @Mapping(target = "seatCount", source = "numberOfSeats")
    CarDto carToCarDto(Car car);
}

@Mapper
public abstract class CarMapper {
    
    @Mapping(target = "seatCount", source = "numberOfSeats")
    public abstract CarDto carToCarDto(Car car);
}

第三步:使用Mapper转换器

public static void main(String[] args) throws IOException {
    // source对象
    Car car = new Car();
    car.setNumberOfSeats(123);
    car.setColor("白色");

    // 获取mapper实例
    CarMapper mapper = Mappers.getMapper(CarMapper.class);
    
    // 调用对应方法,实现转换
    CarDto carDto = mapper.carToCarDto(car);
    
    // 输出: CarDto(seatCount=123, color=白色)
    System.out.println(carDto);
}

获取Mapper实例的方式

注:获取Mapper实例的方式,取决于@Mapper(componentModel=xxx)中,componentModel的模式:

  • default:the mapper uses no component model, instances are typically retrieved via Mappers.getMapper(Class)
  • cdi模式:the generated mapper is an application-scoped CDI bean and can be retrieved via @Inject
  • spring模式:the generated mapper is a Spring bean and can be retrieved via @Autowired
  • jsr330模式:the generated mapper is annotated with @javax.inject.Named and @Singleton, and can be retrieved via @Inject

default模式

default时,可通过 Mappers.getMapper(Class)获取实例

@Mapper
//等价于@Mapper(componentModel = "default")
public interface CarMapper {
    
    @Mapping(target = "seatCount", source = "numberOfSeats")
    CarDto carToCarDto(Car car);
}

获取实例

// 获取mapper实例
CarMapper mapper1 = Mappers.getMapper(CarMapper.class);
// 输出: com.szlaozicl.mybatisplusdemo.tmp.CarMapperImpl@69e308c6
System.out.println(mapper1);

CarMapper mapper2 = Mappers.getMapper(CarMapper.class);
// 输出: com.szlaozicl.mybatisplusdemo.tmp.CarMapperImpl@1a1ed4e5
System.out.println(mapper2);

注:Mappers.getMapper(Class)获取实例时,每次都是获取到一个新的实例。所以如果非要使用Mappers.getMapper(Class)的话,需要尽量避免重复创建,可以使用下述方式:

@Mapper
public interface CarMapper {
 
 /** 全局使用这一个对象 */
 CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
 
 @Mapping(target = "seatCount", source = "numberOfSeats")
 CarDto carToCarDto(Car car);
}

或者

@Mapper
public abstract class BusMapper {
 
 /** 全局使用这一个对象 */
 public final BusMapper INSTANCE = Mappers.getMapper(BusMapper.class);
 
 @Mapping(target = "seatCount", source = "numberOfSeats")
 public abstract CarDto carToCarDto(Car car);
}

spring模式

spring模式时,可通过 Mappers.getMapper(Class)@Autowired@Resource等方式获取实例

@Mapper(componentModel = "spring")
public interface CarMapper {
    
    @Mapping(target = "seatCount", source = "numberOfSeats")
    CarDto carToCarDto(Car car);
}

获取实例

@SpringBootApplication
public class SpringBootDemoApplication implements ApplicationRunner {
    
    @Autowired
    private CarMapper carMapper1;
    
    @Resource
    private CarMapper carMapper2;
    
    public static void main(String[] args) throws IOException {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 获取mapper实例
        CarMapper mapper = Mappers.getMapper(CarMapper.class);
        // 输出: com.szlaozicl.mybatisplusdemo.tmp.CarMapperImpl@f2c488
        System.out.println(mapper);
        
        // 输出: com.szlaozicl.mybatisplusdemo.tmp.CarMapperImpl@54acff7d
        System.err.println(carMapper1);
        
        // 输出: com.szlaozicl.mybatisplusdemo.tmp.CarMapperImpl@54acff7d
        System.err.println(carMapper2);
    }
}

cdi模式和jsr330模式

使用较少,不作介绍,详见官网。

常用知识点

source是转化源,target是转化目标

target是新对象

@Mapper(componentModel = "spring")
public interface CarMapper {
    
    /** Car为source, CarDto为target */
    CarDto carToCarDto(Car car);
}

注:target是一个新创建的对象。

target是已有对象

@Mapper(componentModel = "spring")
public interface CarMapper {
    
    /** Car为source, CarDto为target */
    void  carToCarDto(Car car, @MappingTarget CarDto carDto);
}

字段名不同时

通过@Mappingsourcetarget指定字段名映射

@Mapper(componentModel = "spring")
public interface CarMapper {
    
    /** 指定不同字段名间的映射 */
    @Mapping(target = "seatCount", source = "numberOfSeats")
    CarDto carToCarDto(Car car);
}

注:默认的,mapstruct只会转换字段名称相同的字段。

指定默认值

@Mapper
public interface CarMapper {
    
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    
    /**
     * 当目标字段值最后为null前, 设置其默认值为100
     */
    @Mapping(target = "seatCount", defaultValue = "100")
    CarDto carToCarDto(Car car);
}

常量值

@Mapper
public interface CarMapper {
    
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    
    /**
     * 指定常量值
     */
    @Mapping(target = "seatCount", constant = "100")
    CarDto carToCarDto(Car car);
}

多个字段映射

@Mapper
public interface CarMapper {
    
    @Mapping(target = "length", source = "carLength")
    @Mapping(target = "seatCount", source = "numberOfSeats")
    CarDto carToCarDto(Car car);
}

@Mapper
public interface CarMapper {
    
    @Mappings(value = {
            @Mapping(target = "length", source = "carLength"),
            @Mapping(target = "seatCount", source = "numberOfSeats")
    })
    CarDto carToCarDto(Car car);
}

多级字段定位

可通过{字段名}.{字段名}.{字段名}的形式进行多级定位

public class multiLevel_field {
    
    public static void main(String[] args) {
        Car car = new Car();
        car.setColor("yellow");
        car.setNumberOfSeats(10);
        car.setPerson(new Person("张三", 28));
        
        // 输出:multiLevel_field.CarDto(color=yellow, seatCount=10, personName=张三, personAge=28)
        CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
        System.out.println(carDto);
    
        // 输出:multiLevel_field.Car(color=yellow, numberOfSeats=10, person=multiLevel_field.Person(name=张三, age=28))
        car = CarMapper.INSTANCE.carDtoToCar(carDto);
        System.out.println(car);
    }
    
    
    @Mapper
    public interface CarMapper {
        
        CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
        
        @Mapping(target = "seatCount", source = "numberOfSeats")
        @Mapping(target = "personName", source = "person.name")
        @Mapping(target = "personAge", source = "person.age")
        CarDto carToCarDto(Car c);
    
        @Mapping(target = "numberOfSeats", source = "seatCount")
        @Mapping(target = "person.name", source = "personName")
        @Mapping(target = "person.age", source = "personAge")
        Car carDtoToCar(CarDto c);
    }

    @Data
    public static class CarDto {
        
        private String color;
        
        private Integer seatCount;
        
        private String personName;
        
        private Integer personAge;
    }
    
    @Data
    public static  class Car {
        
        private String color;
        
        private Integer numberOfSeats;
        
        private Person person;
    }
    
    @Data
    @AllArgsConstructor
    public static  class Person {
        
        private String name;
        
        private Integer age;
    }
}

多个source

通过多级定位,mapstruct可以实现多个对象转一个对象

public class multi_to_one {
    
    public static void main(String[] args) {
        Car car = new Car();
        car.setColor("green");
        car.setNumberOfSeats(10);
        Person person = new Person("张三", 28);
    
        // 输出:multi_to_one.CarDto(color=green, seatCount=10, personName=张三, personAge=28)
        CarDto carDto1 = CarMapper.INSTANCE.generateCarDto1(car, person);
        System.out.println(carDto1);
    
        // 输出:multi_to_one.CarDto(color=green, seatCount=10, personName=张三, personAge=28)
        CarDto carDto2 = new CarDto();
        CarMapper.INSTANCE.generateCarDto2(car, person, carDto2);
        System.out.println(carDto2);
    }
    
    
    @Mapper
    public interface CarMapper {
        
        CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
        
        /**
         * 因为Car和Person中所有的字段里,只有一个与CarDto#color匹配,所以这里可以省略该@Mapping。
         * 但如果有多个同事匹配时,需要指定@Mapping;否则编译时mapstruct会报错
         */
        @Mapping(target = "seatCount", source = "c.numberOfSeats")
        @Mapping(target = "personName", source = "p.name")
        @Mapping(target = "personAge", source = "p.age")
        CarDto generateCarDto1(Car c, Person p);
    
    
        /**
         * 等价于
         */
        @Mapping(target = "seatCount", source = "c.numberOfSeats")
        @Mapping(target = "personName", source = "p.name")
        @Mapping(target = "personAge", source = "p.age")
        void generateCarDto2(Car c, Person p, @MappingTarget CarDto carDto);
    }


    @Data
    public static class CarDto {
        
        private String color;
        
        private Integer seatCount;
        
        private String personName;
        
        private Integer personAge;
    }
    
    @Data
    public static  class Car {
        
        private String color;
        
        private Integer numberOfSeats;
    }
    
    @Data
    @AllArgsConstructor
    public static  class Person {
        
        private String name;
        
        private Integer age;
    }
}

字段大小写不同时

mapstruct是基于getter/setter方法读写字段,因为java getter/setter是小驼峰式命名,所以对于字段的首字母的大小写不敏感,能赋值成功,如下面的color与Color,但是对于其它位置的大小写敏感,不能赋值成功,如下面的size与siZe;对于is打头的boolean型字段,lombok生成的getter/setter是回保留原有的is的,所以mapstruct解析后girl与isGirl是不匹配的,除非你自己额外添加对应的getter/setter,如下面的boy与isBoy的getter/setter都是getBoy/setBoy。

public class case_sensitive {
    
    public static void main(String[] args) {
        Car car = new Car();
        car.setColor("yellow");
        car.setSiZe(10);
        car.setBoy(true);
        car.setIsGirl(false);
        CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
        // 输出 case_sensitive.CarDto(color=yellow, size=null, boy=true, girl=null)
        System.out.println(carDto);
    }
    
    @Mapper
    public interface CarMapper {
        
        CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
        
        CarDto carToCarDto(Car car);
    }

    @Data
    public static class CarDto {
        
        private String color;
        
        private Integer size;
        
        private Boolean boy;
    
        /** lombok生成的getter/setter 是 getGirl/setGirl */
        private Boolean girl;
    
        public Boolean getBoy() {
            return boy;
        }
    
        public void setBoy(Boolean boy) {
            this.boy = boy;
        }
    }
    
    @Data
    public static  class Car {
        
        private String Color;
        
        private Integer siZe;
    
        private Boolean isBoy;
    
        /** lombok生成的getter/setter 是 getIsGirl/setIsGirl */
        private Boolean isGirl;
        
        public Boolean getBoy() {
            return isBoy;
        }
    
        public void setBoy(Boolean boy) {
            this.isBoy = boy;
        }
    }
}

忽略字段

@Mapper
public interface CarMapper {
    
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    
    /**
     * 忽略字段 num
     */
    @Mapping(target = "num", ignore = true)
    CarDto carCarDto(Car c);
}

枚举与字符串

public class enum_string {
    
    public static void main(String[] args) {
        Car car = new Car();
        car.setColor(ColorEnum.RED);
        CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
        // 输出:enum_string.CarDto(color=RED)
        System.out.println(carDto);
    
        carDto = new CarDto();
        carDto.setColor("GREEN");
        car = CarMapper.INSTANCE.carDtoToCar(carDto);
        // 输出:enum_string.Car(color=GREEN)
        System.out.println(car);
    }
    
    
    @Mapper
    public interface CarMapper {
        
        CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
        
        CarDto carToCarDto(Car car);
    
        Car carDtoToCar(CarDto car);
    }

    @Data
    public static class CarDto {
        private String color;
    }
    
    @Data
    public static  class Car {
        private ColorEnum color;
    }
    
    public static enum ColorEnum {
        RED,
        GREEN
    }
}

枚举与枚举

@ValueMapping指定不同枚举值(即:Enum#name())之间的转换

public class enum_enum {
    
    public static void main(String[] args) {
        Car car = new Car();
        car.setColor1(ColorBEnum.RED);
        car.setColor2(ColorBEnum.GREEN);
        car.setColor3(ColorBEnum.VIOLET);
        
        CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
        // 输出:enum_enum.CarDto(color1=RED, color2=GREEN, color3=PURPLE)
        System.out.println(carDto);
        
    }
    
    
    @Mapper
    public interface CarMapper {
        
        CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
        
        /**
         * - 相同的可以不指定,如这里的RED就可以不指定
         * - 不同的枚举值需要指定,如这里的 PURPLE与VIOLET
         */
        @ValueMapping(target = "RED", source = "RED")
        @ValueMapping(target = "PURPLE", source = "VIOLET")
        CarDto carToCarDto(Car car);
    }

    @Data
    public static class CarDto {
        private ColorAEnum color1;
        private ColorAEnum color2;
        private ColorAEnum color3;
    }
    
    @Data
    public static  class Car {
        private ColorBEnum color1;
        private ColorBEnum color2;
        private ColorBEnum color3;
    }
    
    public static enum ColorAEnum {
        RED,
        PURPLE,
        GREEN
    }
    public static enum ColorBEnum {
        RED,
        VIOLET,
        GREEN
    }
}

隐式类型(自动)转换

提示:这里只罗列了常见的自动转换方式,实际上mapstruct内置支持了很多自动转换,不限于jdk内的类

提示:放心大胆的用即可,若两个类型之间不支持转换,那么在项目编译时mapstruct会报错提示的

  • 基础类型 <=> 包装类型,会自动转换

    • 当 包装类型 => 基础类型 时,若包装类型为null,则在mapstruct转换时(即:程序编译时)会报错
  • 数值类型(int、Integer、long、Long等等)之间,会自动转换

    • 在大数转化为小数时,可能导致精度丢失
    • 当大的数据类型转化为小的数据类型时,若大的数据类型的值超过了小的数据类型的上限,那么可能转化失准,如:long类型的Long.MAX_VALUE值,转化为int类型时,得到的int的值为-1
  • 基础类型(包括它们的包装类型)和 String 之间,会自动转换

    • 数值 => String时,可以指定格式(格式规则同java.text.DecimalFormat),如:
    public static void main(String[] args) {
     List<String> prices = CarMapper.INSTANCE.prices1(Lists.newArrayList(1, 2, 3));
     // 输出:[$1.00, $2.00, $3.00]
     System.out.println(prices);
     
     prices = CarMapper.INSTANCE.prices2(Lists.newArrayList(1, 2, 3));
     // 输出:[1E0, 2E0, 3E0]
     System.out.println(prices);
    }
    
    
    @Mapper
    public interface CarMapper {
     
     CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
     
     @IterableMapping(numberFormat = "$#.00")
     List<String> prices1(List<Integer> prices);
     
     @IterableMapping(numberFormat = "#.##E0")
     List<String> prices2(List<Integer> prices);
    }
    
  • 枚举 <=> 字符串,会自动转换

    • enum => string,通过方法Enum#name
    • string => enum,通过方法Enum#valueOf
  • 大数据类型 <=> 基础类型(包括它们的包装类型) <=> String

    • 大数据类型包括java.math.BigIntegerjava.math.BigDecimal
    • 当 包装类型 => 基础类型 时,若包装类型为null,则在mapstruct转换时(即:程序编译时)会报错
    • 在大数转化为小数时,可能导致精度丢失
    • 当大的数据类型转化为小的数据类型时,若大的数据类型的值超过了小的数据类型的上限,那么可能转化失准,如:long类型的Long.MAX_VALUE值,转化为int类型时,得到的int的值为-1
    • 数值 => String时,可以指定格式(格式规则同java.text.DecimalFormat
  • java.util.Calendar <=> java.sql.*<=> java.util.Date <=> String <=> java.time.*

    • java.time.*包括
      • java.time.ZonedDateTime
      • java.time.LocalDateTime
      • java.time.LocalDate
      • java.time.LocalTime
      • java.time.Instant
      • java.time.Duration
      • java.time.Period
    • java.sql.*包括
      • java.sql.Time
      • java.sql.Date
      • java.sql.Timestamp
    • 指定格式的方式,同java.text.SimpleDateFormat
    public static void main(String[] args) {
       List<String> prices = CarMapper.INSTANCE.convert(Lists.newArrayList(new Date()));
       // 输出:[2022-03-28]
       System.out.println(prices);
    }
    
    
    @Mapper
    public interface CarMapper {
       
       CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
       
       @IterableMapping(dateFormat = "yyyy-MM-dd")
       List<String> convert(List<Date> dates);
    }
    
  • java.util.Currency <=> String

    货币 <=> 字符串

  • list <=> set、set <=> set、list <=> list等集合之间可进行相互转换

    • 可相互转换的集合有
    接口类型 mapstruct选择作为实现的类
    Iterable ArrayList
    Collection ArrayList
    List ArrayList
    Set HashSet
    SortedSet TreeSet
    NavigableSet TreeSet
    Map HashMap
    SortedMap TreeMap
    NavigableMap TreeMap
    ConcurrentMap ConcurrentHashMap
    ConcurrentNavigableMap ConcurrentSkipListMap
    • 简单示例list <=> set
    public static void main(String[] args) {
     // 输出:[1, 2, 3]
     System.out.println(CarMapper.INSTANCE.convert1(Sets.newHashSet(1, 2, 3)));
     // 输出:[1, 2, 3]
     System.out.println(CarMapper.INSTANCE.convert2(Lists.newArrayList(1, 2, 3, 1)));
    }
    
    
    @Mapper
    public interface CarMapper {
     
     CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
     
     List<String> convert1(Set<Integer> items);
     
     Set<String> convert2(List<Integer> items);
    }
    
  • 更多详见官网

复用spring-bean进行转换支持

@Mapperuses可以指定复用spring容器中已有的spring-bean。

@SpringBootApplication
@SuppressWarnings("all")
public class use_exist_mapper implements ApplicationRunner {
    
    public static void main(String[] args) {
        SpringApplication.run(use_exist_mapper.class, args);
    }
    
    @Autowired
    CarMapper carMapper;
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
        Car car = new Car();
        car.setColor("yellow");
        car.setNumberOfSeats(10);
        car.setPersonList(Lists.newArrayList(new Person(true), new Person(false)));
        CarDto carDto = carMapper.carToCarDto1(car);
        // 输出:use_exist_mapper.CarDto(color=yellow, seatCount=10, personList=[use_exist_mapper.PersonDto(isBoy=true), use_exist_mapper.PersonDto(isBoy=false)])
        System.out.println(carDto);
    
        carDto = carMapper.carToCarDto2(car);
        // 输出:use_exist_mapper.CarDto(color=yellow, seatCount=10, personList=[use_exist_mapper.PersonDto(isBoy=true), use_exist_mapper.PersonDto(isBoy=false)])
        System.out.println(carDto);
    }
    
    @Mapper(componentModel = "spring", uses = {StringMapperA.class})
    public interface CarMapper {
        
        /**
         * 方式一:直接默认使用匹配到的StringMapperA#personToPersonDto方法进行Person => PersonDto 的转换
         */
        @Mapping(target = "seatCount", source = "numberOfSeats")
        CarDto carToCarDto1(Car car);
    
        /**
         * 方式二:直接指定转换方法.
         * 
* 注:此方式部分ban本又bug,目前可能不够通用。 */
@Mapping(target = "seatCount", source = "numberOfSeats") @Mapping(target = "personList", expression = "java(stringMapperA.xyz(car123.getPersonList()))") CarDto carToCarDto2(Car car123); } @Component public static class StringMapperA { public PersonDto personToPersonDto(Person person) { return new PersonDto(person.getBoy()); } public List<PersonDto> xyz(List<Person> list) { return list.stream().map(p -> new PersonDto(p.getBoy())).collect(Collectors.toList()); } } @Data public static class CarDto { private String color; private Integer seatCount; private List<PersonDto> personList; } @Data public static class Car { private String color; private Integer numberOfSeats; private List<Person> personList; } @Data @AllArgsConstructor public static class PersonDto { private Boolean isBoy; } @Data @AllArgsConstructor public static class Person { private Boolean boy; } }

自定义字段转换逻辑 - expression指定静态方法

格式为:

@Mapping(target = "目标字段名", expression = "java({全类名}.{静态方法名}({形参名}.{getter方法})")
public class expression_point_static_method {
    
    public static void main(String[] args) {
        Car car = new Car();
        car.setColor("yellow");
        car.setNumberOfSeats(1);
        CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
        // 输出:expression_point_static_method.CarDto(color=yellow, seatCount=2)
        System.out.println(carDto);
    }
    
    
    @Mapper
    public static abstract class CarMapper {
        
        public static final CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
        
        /** {@link CarMapper}全类名 */
        public static final String CLASS_LONG_NAME = "com.example.mapstruct.obtain_instance_test.expression_point_static_method.CarMapper";
        
        @Mapping(target = "seatCount",
                expression = "java(" + CLASS_LONG_NAME + ".addOne(car123.getNumberOfSeats()))")
        public abstract CarDto carToCarDto(Car car123);
        
        public static Integer addOne(Integer a) {
            return ++a;
        }
    }
    
    @Data
    public static class CarDto {
        
        private String color;
        
        private Integer seatCount;
    }
    
    @Data
    public static  class Car {
        
        private String color;
        
        private Integer numberOfSeats;
    }
}

优化: 直接写全类名,后期不好维护,可以采用@Mapper的import能力,这样一来,expression中只需要写简类名即可,如:

@Mapper(imports = {CarMapper2.class})
public static abstract class CarMapper2 {

    public static final CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

    @Mapping(target = "seatCount",
             expression = "java(CarMapper2.addOne(car123.getNumberOfSeats()))")
    public abstract CarDto carToCarDto(Car car123);

    public static Integer addOne(Integer a) {
        return ++a;
    }
}

自定义字段转换逻辑 - expression指定spring-bean的实例方法

提示:mapstruct的@Mapper(uses = {xxx.class})也能达到注入xxx依赖的效果,但是部分版本有bug,使用不稳定,所以可以直接利用下面抽象类的方式使用

@SpringBootApplication
@SuppressWarnings("all")
public class expression_point_instance_method implements ApplicationRunner {
    
    public static void main(String[] args) {
        SpringApplication.run(expression_point_instance_method.class, args);
    }
    
    @Autowired
    CarMapper123 carMapper123;
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
        Car car = new Car();
        car.setColor("yellow");
        car.setNumberOfSeats(10);
        car.setPersonList(Lists.newArrayList(new Person(true), new Person(false)));
        
        CarDto carDto = carMapper123.carToCarDto2(car);
        // 输出:expression_point_instance_method.CarDto(color=yellow, seatCount=10, personList=[expression_point_instance_method.PersonDto(isBoy=true), expression_point_instance_method.PersonDto(isBoy=false)])
        System.out.println(carDto);
    }
    
    @Mapper(componentModel = "spring")
    public static abstract class CarMapper123 {

        @Autowired
        protected StringMapperB stringMapperB;
        
        /**
         * 直接指定转换方法
         * 
* 注:如果调用的xyz是this里面的方法,那么还可以简写:@Mapping(target = "personList", expression = "java(xyz(car123.getPersonList()))") */
@Mapping(target = "seatCount", source = "numberOfSeats") @Mapping(target = "personList", expression = "java(stringMapperB.xyz(car123.getPersonList()))") public abstract CarDto carToCarDto2(Car car123); } @Component public static class StringMapperB { public List<PersonDto> xyz(List<Person> list) { return list.stream().map(p -> new PersonDto(p.getBoy())).collect(Collectors.toList()); } } @Data public static class CarDto { private String color; private Integer seatCount; private List<PersonDto> personList; } @Data public static class Car { private String color; private Integer numberOfSeats; private List<Person> personList; } @Data @AllArgsConstructor public static class PersonDto { private Boolean isBoy; } @Data @AllArgsConstructor public static class Person { private Boolean boy; } }

自定义字段转换逻辑 - 提供已存在的转换方法

当mapstruct要进行Type1 => Type2转换时,首先会先去寻找是否已存在现成的Type1 => Type2的方法,如果已存在,那么直接利用该方法进行对应类型的转换;如果不存在,则自动生成。

判断一个方法是否是Type1 => Type2的转换方法,只需满足:

  1. 该方法的入参类型要为Type1,出参类型要为Type2
  2. 该方法的方法名.equalsIgnoreCase(“Type1ToType2”)

而提供已存在的转换方法又可分为两种:

  • 第一种:已存在的其他mapper方法(由mapstruct生成)
  • 第二种:已存在的自己写的mapper方法(由程序员自己写)
public class handWritten_mapping_logic {
    public static void main(String[] args) {
        Car car = new Car();
        car.setPersonList(Lists.newArrayList(new Person(true), new Person(false)));
        CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
        // {"color":"yellow","personList":[{"isBoy":true},{"isBoy":false}]}
        System.out.println(JSON.toJSONString(carDto));
        
        carDto = CarMapper2.INSTANCE.carToCarDto(car);
        // {"color":"yellow","personList":[{"isBoy":true},{"isBoy":false}]}
        System.out.println(JSON.toJSONString(carDto));
    }
    
    
    @Mapper
    public interface CarMapper {
        
        CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
        
        /**
         * mapstruct推导过程:
         * 1. Car中的List => CarDto中的List
         * 2. 识别泛型,即:Person => PersonDto
         * 3. 发现存在方法 PersonDto personToCarPersonDto(Person person), 则直接调用该方法进行Person => PersonDto
         */
        CarDto carToCarDto(Car car);
    
        /**
         * 示例  第一种:已存在的其他mapper方法(由mapstruct生成)
         */
        @Mapping(source = "boy", target = "isBoy")
        PersonDto personToPersonDto1(Person person);
    }
    
    
    @Mapper
    public interface CarMapper2 {
    
        CarMapper2 INSTANCE = Mappers.getMapper(CarMapper2.class);
        
        CarDto carToCarDto(Car car);
    
        /**
         * 示例  第二种:已存在的自己写的mapper方法(由程序员自己写)
         */
        default PersonDto personToPersonDto1(Person person) {
            return new PersonDto(person.getBoy());
        }
    }
    
    @Data
    public static class CarDto {
        private List<PersonDto> personList;
    }
    
    @Data
    public static  class Car {
        private List<Person> personList;
    }
    
    @Data
    @AllArgsConstructor
    public static class PersonDto {
        private Boolean isBoy;
    }
    
    @Data
    @AllArgsConstructor
    public static  class Person {
        private Boolean boy;
    }
}

mapstruct入门知识学习完毕 !


相关资料

  • demo代码下载
  • 本文已被收录进《程序员成长笔记》 ,笔者JustryDeng
  • 官网

你可能感兴趣的:(字段copy,mapstruct,copyPropertie,属性复制,对象转换)