mapstruct对象复制&转换

mapstruct对象复制&转换


简介

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

使用步骤简述

第一步:引入相关依赖

...
<properties>
    <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
</properties>
...
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>
...
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <!-- 如果项目中还是用到了lombok,那么也需要加上lombok处理器声明 -->
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>${lombok.version}</version>
                    </path>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <!-- 此处理器用于 在编译时生成具体的Mapper实现 -->
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
                <!-- 编译时输出mapstruct的详细信息 start -->
                <showWarnings>true</showWarnings>
                <compilerArgs>
                    <arg>
                        -Amapstruct.verbose=true
                    </arg>
                </compilerArgs>
                <!-- 编译时输出mapstruct的详细信息 end -->
            </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选择作为实现的类
    IterableArrayList
    CollectionArrayList
    ListArrayList
    SetHashSet
    SortedSetTreeSet
    NavigableSetTreeSet
    MapHashMap
    SortedMapTreeMap
    NavigableMapTreeMap
    ConcurrentMapConcurrentHashMap
    ConcurrentNavigableMapConcurrentSkipListMap
    • 简单示例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);
    
        /**
         * 方式二:直接指定转换方法.
         * <br/>
         * 注:此方式部分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;
        
        /**
         * 直接指定转换方法
         * <br/>
         * 注:如果调用的xyz是this里面的方法,那么还可以简写:<code>@Mapping(target = "personList", expression = "java(xyz(car123.getPersonList()))")</code>
         */
        @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<Person> => CarDto中的List<PersonDto>
         * 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入门知识学习完毕 !


相关资料

  • 3
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MapStruct是一个Java的代码生成器,用于从一个对象映射到另一个对象。 要使用MapStruct创建转换,您需要定义源对象和目标对象之间的映射关系。这可以通过创建一个接口,并使用MapStruct注解来完成。 例如,假设您有一个源对象,称为“PersonDTO”,它有一个名称和年龄字段,以及一个目标对象,称为“PersonEntity”,它有一个名字和年龄字段。 您可以创建以下MapStruct转换接口: ``` @Mapper public interface PersonMapper { PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class); @Mapping(source = "name", target = "name") @Mapping(source = "age", target = "age") PersonEntity toEntity(PersonDTO personDTO); @Mapping(source = "name", target = "name") @Mapping(source = "age", target = "age") PersonDTO toDto(PersonEntity personEntity); } ``` 然后,您可以使用此接口将PersonDTO对象转换为PersonEntity对象,如下所示: ``` PersonDTO personDTO = new PersonDTO(); personDTO.setName("John Doe"); personDTO.setAge(30); PersonEntity personEntity = PersonMapper.INSTANCE.toEntity(personDTO); ``` 相反,您也可以使用该接口将PersonEntity对象转换为PersonDTO对象: ``` PersonEntity personEntity = new PersonEntity(); personEntity.setName("John Doe"); personEntity.setAge(30); PersonDTO personDTO = PersonMapper.INSTANCE.toDto(personEntity); ``` 这就是如何使用MapStruct进行对象转换的简单示例。 ### 回答2: MapStruct 是一个用于 Java 对象之间映射转换的代码生成器。下面我将举一个简单的例子来说明如何使用 MapStruct 进行转换。 假设我们有两个类:Person 和 PersonDTO。Person 类包含了人的基本信息,如姓名、年龄和性别,而 PersonDTO 类则是 Person 类的数据传输对象,它只包含姓名和年龄两个属性。 首先,我们需要在 pom.xml 文件中添加 MapStruct 依赖: ``` <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.4.2.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.4.2.Final</version> <scope>provided</scope> </dependency> ``` 接下来,我们需要创建一个转换器接口,该接口使用了 MapStruct 的注解来定义转换规则: ```java @Mapper public interface PersonMapper { PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class); @Mapping(source = "name", target = "name") @Mapping(source = "age", target = "age") PersonDTO personToDto(Person person); } ``` 在上述代码中,@Mapper 注解表示这是一个 MapStruct转换器接口,通过 @Mapping 注解来指定属性的映射关系。 最后,在使用转换器进行对象转换的地方调用相应的方法即可: ```java Person person = new Person(); person.setName("张三"); person.setAge(25); PersonDTO personDTO = PersonMapper.INSTANCE.personToDto(person); System.out.println(personDTO.getName()); System.out.println(personDTO.getAge()); ``` 上述代码中,我们先创建一个 Person 对象,然后使用转换器的 personToDto 方法将其转换为 PersonDTO 对象,最后打印出 PersonDTO 对象属性值。 这就是使用 MapStruct 进行对象之间映射转换的简单例子。当然,MapStruct 还提供了更多高级的功能和配置选项,可以根据具体需求进行深入学习和使用。 ### 回答3: MapStruct是一个开源的Java注解处理器,用于从一个Java对象转换为另一个Java对象。它通过通过使用注解来定义转换规则和生成转换代码。 以下是一个简单的MapStruct转换示例: 1. 首先,定义需要转换的源对象和目标对象的类。例如,我们有一个名为`Person`的源对象类和一个名为`PersonDto`的目标对象类。 ```java public class Person { private String name; private int age; // getters and setters } public class PersonDto { private String fullName; private int age; // getters and setters } ``` 2. 接下来,使用MapStruct的注解在源对象和目标对象之间创建映射规则。创建一个名为`PersonMapper`的接口,并使用`@Mapper`注解来指定映射规则。 ```java @Mapper public interface PersonMapper { PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class); @Mapping(source = "name", target = "fullName") PersonDto personToPersonDto(Person person); } ``` 3. 最后,通过调用生成的映射方法将源对象转换为目标对象。 ```java Person person = new Person(); person.setName("John"); person.setAge(30); PersonDto personDto = PersonMapper.INSTANCE.personToPersonDto(person); ``` 在上面的示例中,`personToPersonDto`方法将从`Person`对象的`name`属性映射到`PersonDto`对象的`fullName`属性,并将`age`属性直接复制到目标对象中。 这就是一个简单的MapStruct转换示例。MapStruct提供了更丰富的功能,例如支持集合、自定义转换逻辑等。通过编写合适的映射规则和使用MapStruct的注解,您可以轻松地完成对象之间的转换
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值