本篇内容对应的是官网【10. Advanced mapping options】相关内容
1. 默认值和常量
这小节的内容简单来说就是当我们映射时可以通过Mapping给目标实例中的属性设置默认值或者设置一个常量。先看一个默认值的例子:
- @Data
- @AllArgsConstructor
- @ToString
- public class Car {
- private String name;
- }
- @Data
- @AllArgsConstructor
- @ToString
- public class CarDto {
- private String name;
- }
- @Mapper
- public interface CarMapper {
- CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
- @Mapping( source = "name",target = "name",defaultValue = "DEFAULT")
- CarDto carToCarDto(Car car);
- }
- public class Test {
- public static void main(String[] args) {
- Car car = new Car( null);//[1]
- CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
- System.out.println(carDto); //CarDto(name=DEFAULT)
- }
- }
看[1]处,通过构造函数给name属性赋值为null,当把属性值映射给CarDto实例时,如果属性为null,则会使用@Mapping中defaultValue设置的值。所以结果为DEFAULT
再来看一个设置常量的例子:
- @Data
- @AllArgsConstructor
- @ToString
- public class Car {
- private String name;
- }
- @Data
- @AllArgsConstructor
- @ToString
- public class CarDto {
- private String name;
- private String sex; //[1]
- }
- @Mapper
- public interface CarMapper {
- CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
- @Mapping( target = "sex",constant = "man")
- CarDto carToCarDto(Car car);
- }
- public class Test {
- public static void main(String[] args) {
- Car car = new Car( "focus");
- CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
- System.out.println(carDto); //CarDto(name=focus, sex=man)
- }
- }
看代码中CarDto有一个sex属性是Car没有的,这样在映射时是不会给这个sex赋值的,如果想给他付一个值,就可以在Mapping中使用constant来指定一个常量,这个常量可以是基本数据类型或者是包装类。
默认值和常量的使用场景:当Mapper中参数对象中的属性为null时,会使用默认值;当返回值的对象中的属性是在参数对象中没有时,就是用常量constant。
2.表达式
在Mapping中,我们也可以使用表达式生成一个值,赋值给目标对象中的属性。看例子
- @Data
- @AllArgsConstructor
- @ToString
- public class Car {
- private String name;
- public String createName(){
- return "通过expression生成的名字";
- }
- }
- @Data
- @AllArgsConstructor
- @ToString
- public class CarDto {
- private String name;
- }
-
- @Mapper
- public interface CarMapper {
- CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
- @Mapping( target = "name",expression = "java(car.createName())")//[2]
- CarDto carToCarDto(Car car);
- }
-
- public class Test {
- public static void main(String[] args) {
- Car car = new Car("focus");
- CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
- System.out.println(carDto); //CarDto(name=通过expression生成的名字)
- }
- }
观察[2]处的expression就是我们要说的表达式。他的作用就是通过一个java对象的方法生成一个值赋值给target中的属性。例子中我们就是调用Car类中的createName方法生成的值,而不是使用构造方法中传入的值了(其实是有进行了一次赋值)。
对照我写的,再去看一下官网理解下就明白了。很简单。
3.defaultExpression
除了expression外,还有一个defaultExpression,他们两个的区别是后者只有在属性为null时才会调用defaultExpression获取一个值。上面例子中如果构造方法这么写Car car = new Car(null),那么defaultExpression才会调用。代码如下:
- @Data
- @AllArgsConstructor
- @ToString
- public class Car {
- private String name;
- public String createName(){
- return "通过expression生成的名字";
- }
- }
- @Data
- @AllArgsConstructor
- @ToString
- public class CarDto {
- private String name;
- }
-
- @Mapper
- public interface CarMapper {
- CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
- @Mapping( target = "name",defaultExpression = "java(car.createName())")//[2]
- CarDto carToCarDto(Car car);
- }
-
- public class Test {
- public static void main(String[] args) {
- Car car = new Car(null);//[1]
- CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
- System.out.println(carDto); //CarDto(name=通过expression生成的名字)
- }
- }
注意观察,只有[1]处这也给属性值设置为null后,defaultExpression才会执行。否则会用属性当时已经存在的值
4.子类映射
之前我们讲的都是没有任何父子关系的一些类之间的属性映射。那些现在想一个问题,如果你现在只有子类的实例,如果想要把这个子类中的值映射给父类怎么办?
这个就会用到这个小节的知识点。我还是先上实例代码,然后在讲解。东西挺多,坚持看完:
- // -----------父类
- @Data
- @AllArgsConstructor
- @ToString
- public class Fruit {
- private String name;
- public Fruit(){};
- }
- // -----------子类
- @Data
- @AllArgsConstructor
- @ToString
- public class Apple extends Fruit{
- }
-
- // -----------父类
- @Data
- @AllArgsConstructor
- @ToString
- public class FruitDto {
- private String name;
- public FruitDto(){}
- }
-
- // -----------子类
- @Data
- @AllArgsConstructor
- @ToString
- public class AppleDto extends FruitDto{
- }
-
- @Mapper
- public interface FruitMapper {
- FruitMapper INSTANCE = Mappers.getMapper( FruitMapper.class );
-
- @SubclassMapping( source = AppleDto.class, target = Apple.class )//[1]
- Fruit map( FruitDto source );
- }
-
- public class Test {
- public static void main(String[] args) {
- AppleDto appleDto = new AppleDto();
- appleDto.setName("a1");
-
- Fruit fruit = FruitMapper.INSTANCE.map(appleDto);
- System.out.println(fruit); //属性name的值为a1
- }
- }
首先看弄清楚上面几个类的父子关系。然后看[1]处的@SubclassMapping,他的作用就将子类可以映射给父类。map方法中的参数就(FruitDto)是父类,传给他的子类与@SubclassMapping的哪个source对应,那么就会把这个转换为target后面对应的类的对象,这个对象必须是方法返回值的子类(Fruit是返回值的类型,所以targetApple必须是他的子类)。这就是子类映射的规则。按照这个规则写,就可以实现子类到父类的映射。
方法上可以写多个@SubclassMapping
5.如何指定映射的返回类型
先上代码,然后分析:
- public class Apple extends Fruit{
- public Apple(String name) {
- super(name);
- }
-
- public Apple() {
- }
- }
-
- public class Banana extends Fruit{
- public Banana(String name) {
- super(name);
- }
-
- public Banana() {
- }
- }
-
- public class FruitFactory {
-
- public Apple createApple() {
- return new Apple( "Apple" );
- }
-
- public Banana createBanana() {
- return new Banana( "Banana" );
- }
- }
-
-
- @Data
- @AllArgsConstructor
- @ToString
- public class FruitDto {
- private String name;
- public FruitDto(){}
- }
-
-
- @Mapper( uses = FruitFactory.class )
- public interface FruitMapper {
-
- FruitMapper INSTANCE = Mappers.getMapper( FruitMapper.class );
-
- @BeanMapping( resultType = Apple.class ) //[2]
- Fruit map( FruitDto source );//[1]
-
- }
-
-
- public class Test {
- public static void main(String[] args) {
- Fruit fruit = FruitMapper.INSTANCE.map(new FruitDto("apple"));
- System.out.println(fruit); //Fruit(name=apple)
- }
- }
上面我们讲了MapSTruct也可以实现继承关系的父子类映射。看[1]处,map方法的返回值是一个父类Fruit,他的子类有Apple和Banana,当参数传入一个FruitDto时,MapStruct怎么知道到底要映射给Apple还是Banana,这时候就需要我们告诉MapStruct,就要使用[2]处的resultType来制定一下。
FruitFactory是一个工厂类,这么写MapStruct就知道通过这个工厂类来创建实例,看下图实现类就明白了:
6.通过条件决定是给映射时的属性赋值
有些情况下,我们只想我们的属性在满足一定的条件下才把之赋值给目标属性,这时候就可以使用@Condition,具体用法直接上例子
- @Data
- @AllArgsConstructor
- @ToString
- public class Car {
- private String name;
- }
- @Data
- @AllArgsConstructor
- @ToString
- public class CarDto {
- private String name;
- }
-
- @Mapper
- public interface CarMapper {
- CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
-
- CarDto carToCarDto(Car car);
-
- @Condition
- default boolean isNotEmpty(String value) {
- return value != null && !value.isEmpty();
- }
- }
- public class Test {
- public static void main(String[] args) {
- Car car = new Car( "");
- CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
- System.out.println(carDto); //CarDto(name=null)
- }
- }
当我们使用了@Condition后,属性就会通过这个方法来判断是否满足我们的业务,只有满足条件才会把值赋值给目标属性。例子中我们给Car的name设置了空,所以最后carDto中的name就位null。下面我把Mapper的实现类粘贴上,大家就知道具体的原理了。
