Java 实体映射工具 mapstruct 使用

431 阅读3分钟

一、概述

MapStruct是一个用于生成类型安全、性能良好且无依赖关系的bean映射代码的注释处理器。

之前使用的是 BeanUtils.copyProperties() 方法进行实体类属性赋值,但是这个方法不灵活。对于不同字段名或不同属性值无法赋值,使用 MapStruct 可以自定义,更加灵活地进行bean映射。

官方文档:mapstruct.org/documentati…

二、依赖

添加依赖

  <properties>
        <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
    </properties>
​
。。。
​
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>${org.mapstruct.version}</version>
</dependency>
​
​
。。。
​
 <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source> <!-- depending on your project -->
                    <target>1.8</target> <!-- depending on your project -->
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.12</version>
                        </path>
                        <!-- other annotation processors -->
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>

三、使用

1、基础使用

创建实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Car {
 
    private String make;
    private int numberOfSeats;
    private CarType type;
    private Wheel wheel;
​
    //constructor, getters, setters etc.
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CarDto {
 
    private String make;
    private int seatCount;
    private String type;
    private String wheelColor;
    private String wheelPrice;
​
    //constructor, getters, setters etc.
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Wheel {
​
    private String price;
​
    private String color;
    
}
public enum CarType {
​
    AI001("ai", "车1"),
    AC002("ac", "车2");
    
    private String code;
    private String desc;
​
    CarType(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }
​
    public String getCode() {
        return code;
    }
​
    public String getDesc() {
        return desc;
    }
​
}

创建映射接口

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
​
@Mapper
public interface CarMapper {
​
    CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
​
    @Mapping(target = "seatCount", source = "numberOfSeats")
    @Mapping(target = "wheelColor", source = "wheel.color")
    @Mapping(target = "wheelPrice", source = "wheel.price")
    CarDto carToCarDto(Car car);
​
}

创建测试类

public class TestSpring {
    public static void main(String[] args) {
        Wheel wheel = new Wheel("1000","red");
        Car car = new Car("Morris" , 5 , CarType.AI001 , wheel);
        //when
        CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
        System.out.println(carDto.toString());
    }
​
}

输出为:CarDto(make=Morris, seatCount=5, type=AI001, wheelColor=red, wheelPrice=1000)

上面的例子可以用于大多数的情况,针对不同字段,通过@Mapping注解进行映射,source 就是转换的源,target 就是转换的目标。对于实体类内的对象也可以通过形如wheel.price这样的方式进行映射。

2、数字与字符串转换

在Car中添加属性

private Double price;

在CarDto中添加属性

private String price;

在CarMapper中添加方法

    @Mapping(source = "price",numberFormat = "#0.00",target = "price")
    CarDto carToCarDtoPrice(Car car);
​
    @IterableMapping(numberFormat = "#0.00")
    List<String> listIntegerFormat(List<Integer> prices);

进行测试:

public static void main(String[] args) {
    Wheel wheel = new Wheel("1000","red");
    Car car = new Car("Morris", 5, CarType.AI001,wheel,0.0);
    //when
    CarDto carDto = CarMapper.INSTANCE.carToCarDtoPrice(car);
    System.out.println("原始的car");
    System.out.println(car.toString());
    System.out.println("转换后的carDto");
    System.out.println(carDto.toString());
​
    List<Integer> sourceList = new ArrayList<>();
    IntStream.rangeClosed(0,10).forEach(i->sourceList.add(i));
    List<String> stringList = CarMapper.INSTANCE.listIntegerFormat(sourceList);
    System.out.println("原始的list");
    System.out.println(sourceList);
    System.out.println("转换后的list");
    System.out.println(stringList);
}

打印结果如下,可以看到对应的数字已经转换成字符串了,并且保留两位小数。

原始的car
Car(make=Morris, numberOfSeats=5, type=AI001, wheel=Wheel(price=1000, color=red), price=0.0)
转换后的carDto
CarDto(make=Morris, seatCount=0, type=AI001, wheelColor=null, wheelPrice=null, price=0.00)
原始的list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
转换后的list
[0.00, 1.00, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00, 9.00, 10.00]

需要注意的是,在官方文档中,使用的注解为:

@Mapping(source = "price",numberFormat = "$#.00",target = "price")。

$ 代表要在前面添加的字符,可以自定义。

#.00 代表要保留的小数位,但是如果不写成#0.00,那么如果为0转换成的字符串是.00。

3、日期与字符串转换

日期转换与数字转换相似,使用如下注解,具体就不再赘述。

@Mapper
public interface CarMapper {
  @Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy")
  CarDto carToCarDto(Car car);
  @IterableMapping(dateFormat = "dd.MM.yyyy")
  List<String> stringListToDateList(List<Date> dates);
  }

4、使用表达式

@Mapper
public interface SmsPassagewayMap {
​
    @Mapping(target = "accessMsg",
            expression = "java(com.jd.icity.tools.JsonHelper.object2Json(addPassagewayParam.getAccessMap()))"
    )
    SmsPassageway addPassagewayParam2SmsPassageway(AddPassagewayParam addPassagewayParam);
}

可以使用这样的方式,进行表达式注入。

5、更新属性

如果已经有了接收对象,更新目标对象

@InheritConfiguration(name = "carToCarDtoDate")
CarDto carToCarDtoInherit(Car car, @MappingTarget CarDto carDto);

InheritConfiguration 代表要继承的方法,name中的转换方法也会执行,MappingTarget表示映射的目标,只能声明一个参数为MappingTarget。 注意:作为映射目标传递的参数不能为空。

以上方法表示 car 来更新 carDto ,car 中的值会覆盖 carDto的值。

6、注入Spring方式

@Mapper(componentModel="spring")
public interface PersonConverter {
    @Mappings({
        @Mapping(source = "birthday", target = "birth"),
        @Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
        @Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),"yyyy-MM-dd HH:mm:ss"))"),
        @Mapping(source = "user.age", target = "age"),
        @Mapping(target = "email", ignore = true)
    })
    PersonDTO domain2dto(Person person);
}

测试使用:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = BaseTestConfiguration.class)
public class PersonConverterTest {
    //这里把转换器装配进来
    @Autowired
    private PersonConverter personConverter;
    @Test
    public void test() {
        Person person = new Person(1L,"zhige","zhige.me@gmail.com",new Date(),new User(1));
        PersonDTO personDTO = personConverter.domain2dto(person);
    }
}