[MapStruct]关于Mapping的高级选项

本篇内容对应的是官网【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就知道通过这个工厂类来创建实例,看下图实现类就明白了:
[MapStruct]关于Mapping的高级选项_第1张图片

 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的实现类粘贴上,大家就知道具体的原理了。

[MapStruct]关于Mapping的高级选项_第2张图片

 

你可能感兴趣的:(java,java)