本篇内容对应的是官网【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来制定一下。
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的实现类粘贴上,大家就知道具体的原理了。