• [MapStruct]关于Mapping的高级选项


    本篇内容对应的是官网【10. Advanced mapping options】相关内容

    1. 默认值和常量

    这小节的内容简单来说就是当我们映射时可以通过Mapping给目标实例中的属性设置默认值或者设置一个常量。先看一个默认值的例子:

    1. @Data
    2. @AllArgsConstructor
    3. @ToString
    4. public class Car {
    5. private String name;
    6. }
    7. @Data
    8. @AllArgsConstructor
    9. @ToString
    10. public class CarDto {
    11. private String name;
    12. }
    13. @Mapper
    14. public interface CarMapper {
    15. CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
    16. @Mapping( source = "name",target = "name",defaultValue = "DEFAULT")
    17. CarDto carToCarDto(Car car);
    18. }
    19. public class Test {
    20. public static void main(String[] args) {
    21. Car car = new Car( null);//[1]
    22. CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
    23. System.out.println(carDto); //CarDto(name=DEFAULT)
    24. }
    25. }

    看[1]处,通过构造函数给name属性赋值为null,当把属性值映射给CarDto实例时,如果属性为null,则会使用@Mapping中defaultValue设置的值。所以结果为DEFAULT

    再来看一个设置常量的例子:

    1. @Data
    2. @AllArgsConstructor
    3. @ToString
    4. public class Car {
    5. private String name;
    6. }
    7. @Data
    8. @AllArgsConstructor
    9. @ToString
    10. public class CarDto {
    11. private String name;
    12. private String sex; //[1]
    13. }
    14. @Mapper
    15. public interface CarMapper {
    16. CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
    17. @Mapping( target = "sex",constant = "man")
    18. CarDto carToCarDto(Car car);
    19. }
    20. public class Test {
    21. public static void main(String[] args) {
    22. Car car = new Car( "focus");
    23. CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
    24. System.out.println(carDto); //CarDto(name=focus, sex=man)
    25. }
    26. }

    看代码中CarDto有一个sex属性是Car没有的,这样在映射时是不会给这个sex赋值的,如果想给他付一个值,就可以在Mapping中使用constant来指定一个常量,这个常量可以是基本数据类型或者是包装类。

    默认值和常量的使用场景:当Mapper中参数对象中的属性为null时,会使用默认值;当返回值的对象中的属性是在参数对象中没有时,就是用常量constant。

    2.表达式

    在Mapping中,我们也可以使用表达式生成一个值,赋值给目标对象中的属性。看例子

    1. @Data
    2. @AllArgsConstructor
    3. @ToString
    4. public class Car {
    5. private String name;
    6. public String createName(){
    7. return "通过expression生成的名字";
    8. }
    9. }
    10. @Data
    11. @AllArgsConstructor
    12. @ToString
    13. public class CarDto {
    14. private String name;
    15. }
    16. @Mapper
    17. public interface CarMapper {
    18. CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
    19. @Mapping( target = "name",expression = "java(car.createName())")//[2]
    20. CarDto carToCarDto(Car car);
    21. }
    22. public class Test {
    23. public static void main(String[] args) {
    24. Car car = new Car("focus");
    25. CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
    26. System.out.println(carDto); //CarDto(name=通过expression生成的名字)
    27. }
    28. }

    观察[2]处的expression就是我们要说的表达式。他的作用就是通过一个java对象的方法生成一个值赋值给target中的属性。例子中我们就是调用Car类中的createName方法生成的值,而不是使用构造方法中传入的值了(其实是有进行了一次赋值)。

    对照我写的,再去看一下官网理解下就明白了。很简单。

    3.defaultExpression

    除了expression外,还有一个defaultExpression,他们两个的区别是后者只有在属性为null时才会调用defaultExpression获取一个值。上面例子中如果构造方法这么写Car car = new Car(null),那么defaultExpression才会调用。代码如下:

    1. @Data
    2. @AllArgsConstructor
    3. @ToString
    4. public class Car {
    5. private String name;
    6. public String createName(){
    7. return "通过expression生成的名字";
    8. }
    9. }
    10. @Data
    11. @AllArgsConstructor
    12. @ToString
    13. public class CarDto {
    14. private String name;
    15. }
    16. @Mapper
    17. public interface CarMapper {
    18. CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
    19. @Mapping( target = "name",defaultExpression = "java(car.createName())")//[2]
    20. CarDto carToCarDto(Car car);
    21. }
    22. public class Test {
    23. public static void main(String[] args) {
    24. Car car = new Car(null);//[1]
    25. CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
    26. System.out.println(carDto); //CarDto(name=通过expression生成的名字)
    27. }
    28. }

    注意观察,只有[1]处这也给属性值设置为null后,defaultExpression才会执行。否则会用属性当时已经存在的值

    4.子类映射

    之前我们讲的都是没有任何父子关系的一些类之间的属性映射。那些现在想一个问题,如果你现在只有子类的实例,如果想要把这个子类中的值映射给父类怎么办?

    这个就会用到这个小节的知识点。我还是先上实例代码,然后在讲解。东西挺多,坚持看完:

    1. // -----------父类
    2. @Data
    3. @AllArgsConstructor
    4. @ToString
    5. public class Fruit {
    6. private String name;
    7. public Fruit(){};
    8. }
    9. // -----------子类
    10. @Data
    11. @AllArgsConstructor
    12. @ToString
    13. public class Apple extends Fruit{
    14. }
    15. // -----------父类
    16. @Data
    17. @AllArgsConstructor
    18. @ToString
    19. public class FruitDto {
    20. private String name;
    21. public FruitDto(){}
    22. }
    23. // -----------子类
    24. @Data
    25. @AllArgsConstructor
    26. @ToString
    27. public class AppleDto extends FruitDto{
    28. }
    29. @Mapper
    30. public interface FruitMapper {
    31. FruitMapper INSTANCE = Mappers.getMapper( FruitMapper.class );
    32. @SubclassMapping( source = AppleDto.class, target = Apple.class )//[1]
    33. Fruit map( FruitDto source );
    34. }
    35. public class Test {
    36. public static void main(String[] args) {
    37. AppleDto appleDto = new AppleDto();
    38. appleDto.setName("a1");
    39. Fruit fruit = FruitMapper.INSTANCE.map(appleDto);
    40. System.out.println(fruit); //属性name的值为a1
    41. }
    42. }

    首先看弄清楚上面几个类的父子关系。然后看[1]处的@SubclassMapping,他的作用就将子类可以映射给父类。map方法中的参数就(FruitDto)是父类,传给他的子类与@SubclassMapping的哪个source对应,那么就会把这个转换为target后面对应的类的对象,这个对象必须是方法返回值的子类(Fruit是返回值的类型,所以targetApple必须是他的子类)。这就是子类映射的规则。按照这个规则写,就可以实现子类到父类的映射。

    方法上可以写多个@SubclassMapping

     5.如何指定映射的返回类型

    先上代码,然后分析:

    1. public class Apple extends Fruit{
    2. public Apple(String name) {
    3. super(name);
    4. }
    5. public Apple() {
    6. }
    7. }
    8. public class Banana extends Fruit{
    9. public Banana(String name) {
    10. super(name);
    11. }
    12. public Banana() {
    13. }
    14. }
    15. public class FruitFactory {
    16. public Apple createApple() {
    17. return new Apple( "Apple" );
    18. }
    19. public Banana createBanana() {
    20. return new Banana( "Banana" );
    21. }
    22. }
    23. @Data
    24. @AllArgsConstructor
    25. @ToString
    26. public class FruitDto {
    27. private String name;
    28. public FruitDto(){}
    29. }
    30. @Mapper( uses = FruitFactory.class )
    31. public interface FruitMapper {
    32. FruitMapper INSTANCE = Mappers.getMapper( FruitMapper.class );
    33. @BeanMapping( resultType = Apple.class ) //[2]
    34. Fruit map( FruitDto source );//[1]
    35. }
    36. public class Test {
    37. public static void main(String[] args) {
    38. Fruit fruit = FruitMapper.INSTANCE.map(new FruitDto("apple"));
    39. System.out.println(fruit); //Fruit(name=apple)
    40. }
    41. }

    上面我们讲了MapSTruct也可以实现继承关系的父子类映射。看[1]处,map方法的返回值是一个父类Fruit,他的子类有Apple和Banana,当参数传入一个FruitDto时,MapStruct怎么知道到底要映射给Apple还是Banana,这时候就需要我们告诉MapStruct,就要使用[2]处的resultType来制定一下。

    FruitFactory是一个工厂类,这么写MapStruct就知道通过这个工厂类来创建实例,看下图实现类就明白了:

     6.通过条件决定是给映射时的属性赋值

    有些情况下,我们只想我们的属性在满足一定的条件下才把之赋值给目标属性,这时候就可以使用@Condition,具体用法直接上例子

    1. @Data
    2. @AllArgsConstructor
    3. @ToString
    4. public class Car {
    5. private String name;
    6. }
    7. @Data
    8. @AllArgsConstructor
    9. @ToString
    10. public class CarDto {
    11. private String name;
    12. }
    13. @Mapper
    14. public interface CarMapper {
    15. CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
    16. CarDto carToCarDto(Car car);
    17. @Condition
    18. default boolean isNotEmpty(String value) {
    19. return value != null && !value.isEmpty();
    20. }
    21. }
    22. public class Test {
    23. public static void main(String[] args) {
    24. Car car = new Car( "");
    25. CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
    26. System.out.println(carDto); //CarDto(name=null)
    27. }
    28. }

    当我们使用了@Condition后,属性就会通过这个方法来判断是否满足我们的业务,只有满足条件才会把值赋值给目标属性。例子中我们给Car的name设置了空,所以最后carDto中的name就位null。下面我把Mapper的实现类粘贴上,大家就知道具体的原理了。

     

  • 相关阅读:
    Redis入门完整教程:复制拓扑
    python根据文件创建时间删除文件
    海格里斯HEGERLS工程项目案例|陕西西安某新能源电池制造集团企业三期自放电立体库工程项目安装过程
    node.js与内置模块
    2678. 老人的数目 --力扣 --JAVA
    面试突击81:什么是跨域问题?如何解决?
    autoshop梯形图谁写一下,不知道怎么完成这个
    商城免费搭建之java商城 开源java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c
    2022.7.11-7.17 AI行业周刊(第106期):竭尽全力,努力就好
    【第49篇】Swin Transformer V2:扩展容量和分辨率
  • 原文地址:https://blog.csdn.net/liuhaibo_ljf/article/details/126544552