[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就知道通过这个工厂类来创建实例,看下图实现类就明白了:

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

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MapStructmapping是一种在对象映射过程中定义映射规则的方式。通过使用注解,可以指定源对象和目标对象之间的映射关系,以及如何将源对象的属性值转换为目标对象的属性值。 使用MapStructmapping功能,可以通过以下几种方式定义映射规则: 1. 直接映射:当源对象和目标对象的属性名称相同时,可以直接将源对象的属性值复制给目标对象的对应属性。 2. 自定义映射方法:通过在映射器接口或其他类中定义映射方法,可以实现复杂的映射逻辑。这些映射方法可以被MapStruct自动生成的映射器或手动编写的映射器调用。 3. 使用表达式:可以使用表达式语言,在映射过程中进行计算或转换操作。 在MapStruct中,可以使用@Mapping注解来指定映射规则。@Mapping注解可以用于方法级别或属性级别,用于指定源属性和目标属性之间的对应关系。可以通过source属性指定源属性的名称,target属性指定目标属性的名称,以及其他一些属性来定义更复杂的映射规则。 例如,可以使用@Mapping注解将源对象的name属性映射到目标对象的fullName属性: @Mapping(source = "name", target = "fullName") public TargetObject map(SourceObject source); 除了@Mapping注解外,还可以使用其他一些注解来定义映射规则,如@Mappings、@ValueMapping等。 通过使用MapStructmapping功能,可以简化对象之间的映射过程,并提高代码的复用性和可维护性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [MapStruct 详解](https://blog.csdn.net/chenshun123/article/details/83445438)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值