mapstruct对象复制&转换

Posted justry_deng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于mapstruct对象复制&转换的主要内容,如果未能解决你的问题,请参考以下文章

MapStruct代码生成器实现对象转换

mapstruct对象转换工具

MapStruct在项目中封装使用

Bean 字段复制利器 MapStruct

优雅的转换Bean对象 mapstruct使用笔记详解

Spring Boot | 集成MapStruct实现不同类型Java对象间的自动转换