看了我的 Mapstruct 用法,同事们也开始悄悄模仿了!

前几天同事review我的代码,发现mapstruct有这么多好用的技巧,遇到POJO转换的问题经常过来沟通。考虑到不可能每次都一对一,所以我来梳理五个场景,谁在过来问,直接甩出总结。

看了我的 Mapstruct 用法,同事们也开始悄悄模仿了!

环境准备

由于日常使用都是Spring,所以后面的示例都是在SpringBoot框架中运行的。关键pom依赖如下:

<properties>
    <Java.version>1.8</java.version>
    <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
    <org.projectlombok.version>1.18.30</org.projectlombok.version>
</properties>
<dependencies>

    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok-mapstruct-binding</artifactId>
        <version>0.2.0</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

场景一:常量转换

这是最简单的一个场景,比如需要设置字符串、整形和长整型的常量,有的又需要日期,或者新建类型。下面举个例子,演示如何转换

//实体类
@Data
public class Source {
    private String stringProp;
    private Long longProp;
}
@Data
public class Target {
    private String stringProperty;
    private long longProperty;
    private String stringConstant;
    private Integer integerConstant;
    private Long  longWrapperConstant;
    private Date dateConstant;
}
  • 设置字符串常量
  • 设置long常量
  • 设置java内置类型默认值,比如date

那么mapper这么设置就可以

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface SourceTargetMapper {

    @Mapping(target = "stringProperty", source = "stringProp", defaultValue = "undefined")
    @Mapping(target = "longProperty", source = "longProp", defaultValue = "-1l")
    @Mapping(target = "stringConstant", constant = "Constant Value")
    @Mapping(target = "integerConstant", constant = "14")
    @Mapping(target = "longWrapperConstant", constant = "3001L")
    @Mapping(target = "dateConstant", dateFormat = "yyyy-MM-dd", constant = "2023-09-01-")
    Target sourceToTarget(Source s);
}

解释下,constant用来设置常量值,source的值如果没有设置,则会使用defaultValue的值,日期可以按dateFormat解析。

Talk is cheap, show me the code !废话不多说,自动生成的转换类如下:

@Component
public class SourceTargetMapperImpl implements SourceTargetMapper {
    public SourceTargetMapperImpl() {
    }

    public Target sourceToTarget(Source s) {
        if (s == null) {
            return null;
        } else {
            Target target = new Target();
            if (s.getStringProp() != null) {
                target.setStringProperty(s.getStringProp());
            } else {
                target.setStringProperty("undefined");
            }

            if (s.getLongProp() != null) {
                target.setLongProperty(s.getLongProp());
            } else {
                target.setLongProperty(-1L);
            }

            target.setStringConstant("Constant Value");
            target.setIntegerConstant(14);
            target.setLongWrapperConstant(3001L);

            try {
                target.setDateConstant((new SimpleDateFormat("dd-MM-yyyy")).parse("09-01-2014"));
                return target;
            } catch (ParseException var4) {
                throw new RuntimeException(var4);
            }
        }
    }
}

是不是一目了然

看了我的 Mapstruct 用法,同事们也开始悄悄模仿了!

场景二:转换中调用表达式

比如id不存在使用UUID生成一个,或者使用已有参数新建一个对象作为属性。当然可以用after mappingqualifiedByName等实现,感觉还是不够优雅,这里介绍个雅的(代码少点的)。

实体类如下:

@Data
public class CustomerDto {
    public Long id;
    public String customerName;

    private String format;
    private Date time;
}
@Data
public class Customer {
    private String id;
    private String name;
    private TimeAndFormat timeAndFormat;
}
@Data
public class TimeAndFormat {
    private Date time;
    private String format;

    public TimeAndFormat(Date time, String format) {
        this.time = time;
        this.format = format;
    }
}

Dto转customer,加创建TimeAndFormat作为属性,mapper实现如下:

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, imports = UUID.class)
public interface CustomerMapper 
{

    @Mapping(target = "timeAndFormat",
            expression = "java( new TimeAndFormat( s.getTime(), s.getFormat() ) )")
    @Mapping(target = "id", source = "id", defaultExpression = "java( UUID.randomUUID().toString() )")
    Customer toCustomer(CustomerDto s);

}

解释下,id为空则走默认的defaultExpression,通过imports引入,java括起来调用。新建对象直接new TimeAndFormat。有的小伙伴喜欢用qualifiedByName自定义方法,可以对比下,哪个合适用哪个,都能调用转换方法。

生成代码如下:

@Component
public class CustomerMapperImpl implements CustomerMapper {
    public CustomerMapperImpl() {
    }

    public Customer toCustomer(CustomerDto s) {
        if (s == null) {
            return null;
        } else {
            Customer customer = new Customer();
            if (s.getId() != null) {
                customer.setId(String.valueOf(s.getId()));
            } else {
                customer.setId(UUID.randomUUID().toString());
            }

            customer.setTimeAndFormat(new TimeAndFormat(s.getTime(), s.getFormat()));
            return customer;
        }
    }
}

场景三:类共用属性,如何复用

比如下面的Bike和车辆类,都有id和creationDate属性,我又不想重复写mapper属性注解

public class Bike {
    /**
     * 唯一id
     */

    private String id;

    private Date creationDate;

    /**
     * 品牌
     */

    private String brandName;
}

public class Car {
    /**
     * 唯一id
     */

    private String id;

    private Date creationDate;
    /**
     * 车牌号
     */

    private String chepaihao;
 }

解决起来很简单,写个共用的注解,使用的时候引入就可以,示例如下:

//通用注解
@Retention(RetentionPolicy.CLASS)
//自动生成当前日期
@Mapping(target = "creationDate", expression = "java(new java.util.Date())")
//忽略id
@Mapping(target = "id", ignore = true)
public @interface ToEntity { }

//使用
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface TransportationMapper {
    @ToEntity
    @Mapping( target = "brandName", source = "brand")
    Bike map(BikeDto source);

    @ToEntity
    @Mapping( target = "chepaihao", source = "plateNo")
    Car map(CarDto source);
}

这里Retention修饰ToEntity注解,表示ToEntity注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期,辅助生成mapper实现类。上面定义了creationDate和id的转换规则,新建日期,忽略id。

生成的mapper实现类如下:

@Component
public class TransportationMapperImpl implements TransportationMapper {
    public TransportationMapperImpl() {
    }

    public Bike map(BikeDto source) {
        if (source == null) {
            return null;
        } else {
            Bike bike = new Bike();
            bike.setBrandName(source.getBrand());
            bike.setCreationDate(new Date());
            return bike;
        }
    }

    public Car map(CarDto source) {
        if (source == null) {
            return null;
        } else {
            Car car = new Car();
            car.setChepaihao(source.getPlateNo());
            car.setCreationDate(new Date());
            return car;
        }
    }
}

坚持一下,还剩俩场景,剩下的俩更有意思

场景四:lombok和mapstruct冲突了

啥冲突?用了builder注解后,mapstuct转换不出来了。哎,这个问题困扰了我那同事两天时间。

解决方案如下:

 <dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok-mapstruct-binding</artifactId>
   <version>0.2.0</version>
 </dependency>

加上lombok-mapstruct-binding就可以了,看下生成的效果:

@Builder
@Data
public class Person {
    private String name;
}
@Data
public class PersonDto {
    private String name;
}
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface PersonMapper {

    Person map(PersonDto dto);
}
@Component
public class PersonMapperImpl implements PersonMapper {
    public PersonMapperImpl() {
    }

    public Person map(PersonDto dto) {
        if (dto == null) {
            return null;
        } else {
            Person.PersonBuilder person = Person.builder();
            person.name(dto.getName());
            return person.build();
        }
    }
}

从上面可以看到,mapstruct匹配到了lombok的builder方法。

场景五:说个难点的,转换的时候,如何注入springBean

看了我的 Mapstruct 用法,同事们也开始悄悄模仿了!

有时候转换方法比不是静态的,他可能依赖spring bean,这个如何导入?

这个使用需要使用抽象方法了,上代码:

@Component
public class SimpleService {
    public String formatName(String name) {
        return  "您的名字是:" + name;
    }
}
@Data
public class Student {
    private String name;
}
@Data
public class StudentDto {
    private String name;
}
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public abstract class StudentMapper {

    @Autowired
    protected SimpleService simpleService;

    @Mapping(target = "name", expression = "java(simpleService.formatName(source.getName()))")
    public abstract StudentDto map(StudentDto source);
}

接口是不支持注入的,但是抽象类可以,所以采用抽象类解决,后面expression直接用皆可以了,生成mapperimpl如下:

@Component
public class StudentMapperImpl extends StudentMapper {
    public StudentMapperImpl() {
    }

    public StudentDto map(StudentDto source) {
        if (source == null) {
            return null;
        } else {
            StudentDto studentDto = new StudentDto();
            studentDto.setName(this.simpleService.formatName(source.getName()));
            return studentDto;
        }
    }
}

思考

以上场景肯定还有其他解决方案,遵循合适的原则就可以。驾驭不了的代码,可能带来更多问题,先简单实现,后续在迭代优化可能适合更多的业务场景。

另外文章中的示例代码地址是:

https://github.com/dayuqichengbao/mapStructExample

来源|juejin.cn/post/7297222349731627046


后端专属技术群

构建高质量的技术交流社群,欢迎从事编程开发、技术招聘HR进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!

文明发言,以交流技术职位内推行业探讨为主

广告人士勿入,切勿轻信私聊,防止被骗

看了我的 Mapstruct 用法,同事们也开始悄悄模仿了!

加我好友,拉你进群

原文始发于微信公众号(Java笔记虾):看了我的 Mapstruct 用法,同事们也开始悄悄模仿了!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/173742.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!