• Mapstruct @Mapper @Mapping 使用介绍以及总结


    作用

    用于各个对象实体间的相互转换,例如数据库底层实体 转为页面对象,Model 转为 DTO(data transfer object 数据转换实体), DTO 转为其他中间对象, VO 等等,相关转换代码为编译时自动产生的新文件和代码。
    两个对象之间相同属性名的会被自动转换,指定特殊情况时需要通过注解在抽象方法上说明不同属性之间的转换。

    转换方法一般均为抽象方法,所以这一类文件一般使用 接口 类,或者抽象类均可,官方的介绍一般均使用了接口类文件来完成。
    如果感兴趣可以到官网看看
    官网链接

    Maven 依赖

    官方给与的示例配置,建议使用官方推荐的版本

    ...
    <properties>
        <org.mapstruct.version>1.5.2.Finalorg.mapstruct.version>
    properties>
    ...
    <dependencies>
        <dependency>
            <groupId>org.mapstructgroupId>
            <artifactId>mapstructartifactId>
            <version>${org.mapstruct.version}version>
        dependency>
    dependencies>
    ...
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>3.8.1version>
                <configuration>
                    <source>1.8source>
                    <target>1.8target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstructgroupId>
                            <artifactId>mapstruct-processorartifactId>
                            <version>${org.mapstruct.version}version>
                        path>
                    annotationProcessorPaths>
                configuration>
            plugin>
        plugins>
    build>
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    在本地经过一些实验后,发现这样也可以

    <dependencies>
    ........
        <dependency>
          <groupId>org.mapstructgroupId>
          <artifactId>mapstructartifactId>
          <version>1.4.2.Finalversion>
        dependency>
        <dependency>
          <groupId>org.mapstructgroupId>
          <artifactId>mapstruct-processorartifactId>
          <version>1.2.0.Finalversion>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这里我 IDEA 版本为 2020.1

    特别注意,mapstruct 以及 mapstruct-processor 不论哪种形式两个都需要同时存在,mapstruct 提供了注解等,mapstruct-processor 会在编译时将相关接口文件的实现类生成出来,最终完成想要的映射实现。

    若与 lombok 连用

    如果同时使用了另一个 lombok 代码生成相关的依赖,那么需要注意lombok maven 依赖版本需要在 1.16.16 以上,否则可能会出现

    **No property named “XXX“ exists in source parameter(s). Did you mean “null“?**之类的错误

    使用示例

    1.基本使用

    定义两个类如下:
    这里使用了lombok 实际上仅仅是简化了 get set 以及构造方法之类的。
    如果需要,可以看看另一篇关于 lombok 的介绍
    @Data 注解使用 lombok插件学习总结
    实体一DTO

    package org.aurora.lombok;
    
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.Date;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public class PersionDTO {
    
        private Long pId;
    
        private String name;
    
        private Integer age;
    
        private String sex;
    
        private String address;
    
        private Float height;
    
        private Float weight;
    
        private Date birthDay;
    
        private String birthLocation;
    
        private Boolean married;
    
        private Boolean hasParents;
    
        private Boolean hasChildren;
    
        private String describe;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    其中
    @Data 编译时帮助实现了 get set 方法
    @AllArgsConstructor 全参构造函数
    @NoArgsConstructor 无参构造函数
    @Builder 允许使用 builder 方式构造对象,其实和构造函数差不多

    另一个实体 VO:

    package org.aurora.lombok;
    
    import lombok.Data;
    import lombok.ToString;
    
    @Data
    @ToString
    public class PersonVO {
    
        private Long pId;
    
        private String name;
    
        private Integer age;
    
        private String sex;
    
        private String des;
    
        private String birth;
    
        private String choice;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    开始加入 MapStruct 的转换相关的,注意这里我定义的是抽象类,实际上使用接口类也可以
    先看看正常的接口类

    package org.aurora.mapstruct;
    
    import org.aurora.lombok.PersionDTO;
    import org.aurora.lombok.PersonVO;
    import org.mapstruct.Mapper;
    import org.mapstruct.Mapping;
    import org.mapstruct.factory.Mappers;
    
    @Mapper
    public interface MyMapper {
    
        MyMapper INSTANCE = Mappers.getMapper(MyMapper.class);
    
        @Mapping(source = "describe", target = "des")
        PersonVO transToViewObject(PersionDTO persionDTO);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    上面可以如下解释
    @Mapper 当前类认为是要执行 MapStruct 相关操作的类
    @Mapping 为指定某些特殊映射的注解,没有指定的内容,当源对象与 目标对象拥有一样的属性时会自动转换

    INSTANCE 是为了在外面外面调用该方法, 接口中的属性默认为静态属性所以可以直接调用到

    @Mapping 中 source 为入参,源对象的属性名, target 为目标对象的属性 source 和 target 没有顺序之分,按自己的来即可
    在这里插入图片描述
    生成代码如下,注意这些代码是直接生成到 classes 里面的

    package org.aurora.mapstruct;
    
    import javax.annotation.Generated;
    import org.aurora.lombok.PersionDTO;
    import org.aurora.lombok.PersonVO;
    
    @Generated(
        value = "org.mapstruct.ap.MappingProcessor",
        date = "2022-08-11T16:24:31+0800",
        comments = "version: 1.2.0.Final, compiler: javac, environment: Java 1.8.0_211 (Oracle Corporation)"
    )
    public class MyMapperImpl implements MyMapper {
    
        @Override
        public PersonVO transToViewObject(PersionDTO persionDTO) {
            if ( persionDTO == null ) {
                return null;
            }
    
            PersonVO personVO = new PersonVO();
    
            personVO.setDes( persionDTO.getDescribe() );
            personVO.setPId( persionDTO.getPId() );
            personVO.setName( persionDTO.getName() );
            personVO.setAge( persionDTO.getAge() );
            personVO.setSex( persionDTO.getSex() );
    
            return personVO;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在Main 中测试如下:

        public static void main(String[] args){
        // 其实就是一个构造方法。可以看作连续的set 属性。
            PersionDTO persionDTO = PersionDTO.builder().pId(1515435488753L).name("小天").age(-2).sex("male").birthDay(new Date()).address("甘雨胡同十六号").height(18f).weight(4f)
                    .birthLocation("chinese").describe("嗷嗷嗷").married(false).hasChildren(false).hasParents(true).build();
    		// 转化
            PersonVO personVO = MyMapper.INSTANCE.transToViewObject(persionDTO);
    
            System.out.println(personVO.toString());
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    结果如下

    PersonVO(pId=1515435488753, name=小天, age=-2, sex=male, des=嗷嗷嗷, birth=null, choice=null)
    
    • 1

    可以看出来,当两个实体中的参数中存在一致的属性时会被自动转换,属性名对不上如果 @Mapping 没有指定的处理方式,则不会处理,实际上这一条已经适用于大多数场景。

    接下来看下使用抽象类的方式,结果大同小异
    转换类如下定义:

    package org.aurora.mapstruct;
    
    import org.aurora.lombok.PersionDTO;
    import org.aurora.lombok.PersonVO;
    import org.mapstruct.AfterMapping;
    import org.mapstruct.Mapper;
    import org.mapstruct.Mapping;
    import org.mapstruct.MappingTarget;
    import org.mapstruct.factory.Mappers;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    @Mapper
    public abstract class AbstractPersonConverter {
    
        public static final AbstractPersonConverter INSTANCE = Mappers.getMapper(AbstractPersonConverter.class);
    
        /**
         * persionDTO 转到 PersonVO
         * @param persionDTO
         * @return
         */
        @Mapping(source = "describe", target = "des")
        public abstract PersonVO transToViewObject(PersionDTO persionDTO);
    
        /**
         * persionDTO 转到  personVO的后置转换处理
         * @param personVO
         * @param persionDTO
         */
        @AfterMapping
        void changeDateIntoStr(@MappingTarget PersonVO personVO, PersionDTO  persionDTO ){
            Date birthDay = persionDTO.getBirthDay();
            if (birthDay != null){
                personVO.setBirth(transDate(birthDay));
            }
        }
    
        /**
         * 日期转换
         * @param date
         * @return
         */
        String transDate(Date date){
            if (date ==  null){
                return null;
            }
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("y-M-d");
            String format = simpleDateFormat.format(date);
            return format;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    生成代码如下:

    package org.aurora.mapstruct;
    
    import javax.annotation.Generated;
    import org.aurora.lombok.PersionDTO;
    import org.aurora.lombok.PersonVO;
    
    @Generated(
        value = "org.mapstruct.ap.MappingProcessor",
        date = "2022-08-11T15:11:27+0800",
        comments = "version: 1.2.0.Final, compiler: javac, environment: Java 1.8.0_211 (Oracle Corporation)"
    )
    public class AbstractPersonConverterImpl extends AbstractPersonConverter {
    
        @Override
        public PersonVO transToViewObject(PersionDTO persionDTO) {
            if ( persionDTO == null ) {
                return null;
            }
    
            PersonVO personVO = new PersonVO();
    
            personVO.setDes( persionDTO.getDescribe() );
            personVO.setPId( persionDTO.getPId() );
            personVO.setName( persionDTO.getName() );
            personVO.setAge( persionDTO.getAge() );
            personVO.setSex( persionDTO.getSex() );
    
            changeDateIntoStr( personVO, persionDTO );
    
            return personVO;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    其中

    2.@AfterMapping @BeforeMapping

    为指定某类型换为某一类型指定的在一般Mapping 结束后附加的操作
    @MappingTarget PersonVO personVO 指定了最终转换结果为 PersonVO 类的对象,personVO 以及 persionDTO 均为入参,在这里可以做一些自己的额外操作

    实验代码:

        public static void main(String[] args){
    
            PersionDTO persionDTO = PersionDTO.builder().pId(1515435488753L).name("小天").age(-2).sex("male").birthDay(new Date()).address("甘雨胡同十六号").height(18f).weight(4f)
                    .birthLocation("chinese").describe("嗷嗷嗷").married(false).hasChildren(false).hasParents(true).build();
    
            PersonVO personVO = AbstractPersonConverter.INSTANCE.transToViewObject(persionDTO);
            System.out.println(personVO.toString());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    最终结果:

    PersonVO(pId=1515435488753, name=小天, age=-2, sex=male, des=嗷嗷嗷, birth=2022-8-11, choice=null)
    
    • 1

    可以看得出最终的 结果中对 birth 的特殊操作是成功进入了最终的结果,实际上这里的时间转换可以通过 Mapstruct 的注解自带功能完成。AfterMapping 中支持我们做更多特殊的业务操作,@BeforeMapping与 AfterMapping 相反 会在实际映射方法前执行,但作用一致

    3.多个入参转为一个对象

    上面的方法多添加一个抽象方法如下:

        @Mapping(source = "persionDTO.describe", target = "des")
        @Mapping(source = "apee.ap", target = "choice")
        public abstract PersonVO transToViewObject(PersionDTO persionDTO, Apee apee);
    
    • 1
    • 2
    • 3

    生成的代码如下:

        @Override
        public PersonVO transToViewObject(PersionDTO persionDTO, Apee apee) {
            if ( persionDTO == null && apee == null ) {
                return null;
            }
    
            PersonVO personVO = new PersonVO();
    
            if ( persionDTO != null ) {
                personVO.setDes( persionDTO.getDescribe() );
                personVO.setPId( persionDTO.getPId() );
                personVO.setName( persionDTO.getName() );
                personVO.setAge( persionDTO.getAge() );
                personVO.setSex( persionDTO.getSex() );
            }
            if ( apee != null ) {
                personVO.setChoice( apee.getAp() );
            }
    
            changeDateIntoStr( personVO, persionDTO );
    
            return personVO;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    测试代码:

        public static void main(String[] args){
    
            PersionDTO persionDTO = PersionDTO.builder().pId(1515435488753L).name("小天").age(-2).sex("male").birthDay(new Date()).address("甘雨胡同十六号").height(18f).weight(4f)
                    .birthLocation("chinese").describe("嗷嗷嗷").married(false).hasChildren(false).hasParents(true).build();
    
            Apee apee = new Apee();
            apee.setAp("hahahahahaha");
    
            PersonVO personVO = AbstractPersonConverter.INSTANCE.transToViewObject(persionDTO, apee);
            System.out.println(personVO.toString());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    结果:

    PersonVO(pId=1515435488753, name=小天, age=-2, sex=male, des=嗷嗷嗷, birth=2022-8-11, choice=hahahahahaha)
    
    • 1

    这里需要注意的是,当你的多个对象作为入参时,多个对象均含有同一个属性时即当目标对象与另外多个入参都含有一个或多个相同属性时,在mapping 也需要指定,否则不知道拿哪个属性会抛出异常。

    4.当源属性没有时指定默认值

    在Mapping 中添加 default 属性,给与默认值即可,例如:

        @Mapping(source = "persionDTO.describe", target = "des")
        @Mapping(source = "apee.ap", target = "choice", defaultValue = "给个默认值")
        public abstract PersonVO transToViewObject(PersionDTO persionDTO, Apee apee);
    
    • 1
    • 2
    • 3

    生成的代码

        @Override
        public PersonVO transToViewObject(PersionDTO persionDTO, Apee apee) {
            if ( persionDTO == null && apee == null ) {
                return null;
            }
    
            PersonVO personVO = new PersonVO();
    
            if ( persionDTO != null ) {
                personVO.setDes( persionDTO.getDescribe() );
                personVO.setPId( persionDTO.getPId() );
                personVO.setName( persionDTO.getName() );
                personVO.setAge( persionDTO.getAge() );
                personVO.setSex( persionDTO.getSex() );
            }
            if ( apee != null ) {
                if ( apee.getAp() != null ) {
                    personVO.setChoice( apee.getAp() );
                }
                else {
                    personVO.setChoice( "给个默认值" );
                }
            }
    
            changeDateIntoStr( personVO, persionDTO );
    
            return personVO;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    执行测试代码:

        public static void main(String[] args){
    
            PersionDTO persionDTO = PersionDTO.builder().pId(1515435488753L).name("小天").age(-2).sex("male").birthDay(new Date()).address("甘雨胡同十六号").height(18f).weight(4f)
                    .birthLocation("chinese").describe("嗷嗷嗷").married(false).hasChildren(false).hasParents(true).build();
    
            Apee apee = new Apee();
    //        apee.setAp("hahahahahaha");
    
            PersonVO personVO = AbstractPersonConverter.INSTANCE.transToViewObject(persionDTO, apee);
            System.out.println(personVO.toString());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    结果:

    PersonVO(pId=1515435488753, name=小天, age=-2, sex=male, des=嗷嗷嗷, birth=2022-8-11, choice=给个默认值)
    
    • 1

    5.一般的类型以及日期转换(dateFormat), 数字转换(numberFormat)

    之前的 Apee 定义如下

    package org.aurora.lombok;
    
    
    import java.util.Date;
    
    public class Apee {
    
        private String ap;
    
        private String cp;
    
        private Date timeAA;
    
        private String strTime;
    
        public String getCp() {
            return cp;
        }
    
        public void setCp(String cp) {
            this.cp = cp;
        }
    
        public Date getTimeAA() {
            return timeAA;
        }
    
        public void setTimeAA(Date timeAA) {
            this.timeAA = timeAA;
        }
    
        public String getStrTime() {
            return strTime;
        }
    
        public void setStrTime(String strTime) {
            this.strTime = strTime;
        }
    
        public Apee() {
        }
    
        public Apee(String ap, String cp) {
            this.ap = ap;
            this.cp = cp;
        }
    
        public String getAp() {
            return ap;
        }
    
        public void setAp(String ap) {
            this.ap = ap;
        }
    
        @Override
        public String toString() {
            return "Apee{" +
                    "ap='" + ap + '\'' +
                    ", cp='" + cp + '\'' +
                    ", timeAA=" + timeAA +
                    ", strTime='" + strTime + '\'' +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    抽象方法定义如下:

        @Mapping(source = "persionDTO.birthDay", target = "strTime", dateFormat = "yyyy-MM-dd")
        @Mapping(source = "persionDTO.describe", target = "timeAA", dateFormat = "yyyy-MM-dd")
        @Mapping(source = "persionDTO.age", target = "ap")
        public abstract Apee transDateToApee(PersionDTO persionDTO);
    
    • 1
    • 2
    • 3
    • 4

    生成代码:

    @Override
        public Apee transDateToApee(PersionDTO persionDTO) {
            if ( persionDTO == null ) {
                return null;
            }
    
            Apee apee = new Apee();
    
            try {
                if ( persionDTO.getDescribe() != null ) {
                    apee.setTimeAA( new SimpleDateFormat( "yyyy-MM-dd" ).parse( persionDTO.getDescribe() ) );
                }
            }
            catch ( ParseException e ) {
                throw new RuntimeException( e );
            }
            if ( persionDTO.getBirthDay() != null ) {
                apee.setStrTime( new SimpleDateFormat( "yyyy-MM-dd" ).format( persionDTO.getBirthDay() ) );
            }
            if ( persionDTO.getAge() != null ) {
                apee.setAp( String.valueOf( persionDTO.getAge() ) );
            }
    
            return apee;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    测试代码:

        public static void main(String[] args){
    
            PersionDTO persionDTO = PersionDTO.builder().pId(1515435488753L).name("小天").age(-2).sex("male").birthDay(new Date()).address("甘雨胡同十六号").height(18f).weight(4f)
                    .birthLocation("chinese").describe("2022-07-08").married(false).hasChildren(false).hasParents(true).build();
    
            Apee personVO = AbstractPersonConverter.INSTANCE.transDateToApee(persionDTO);
            System.out.println(personVO.toString());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    测试结果:

    Apee{ap='-2', cp='null', timeAA=Fri Jul 08 00:00:00 CST 2022, strTime='2022-08-11'}
    
    • 1

    可以看的出当指定日期格式 dateFormat 时 不论是从 String 到 Date 还是 Date 到 String 还是一般的类型转 String 等等都可以自动完成适应,而且我发现当我提供 日期转换方法时它竟然自动引用了我的方法,当我删除时,它使用了自己生成的代码

    在上方转化代码中我提供了

        /**
         * 日期转换
         * @param date
         * @return
         */
        String transDate(Date date){
            if (date ==  null){
                return null;
            }
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("y-M-d");
            String format = simpleDateFormat.format(date);
            return format;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    结果生成的代码如下:

        @Override
        public Apee transDateToApee(PersionDTO persionDTO) {
            if ( persionDTO == null ) {
                return null;
            }
    
            Apee apee = new Apee();
    
            try {
                if ( persionDTO.getDescribe() != null ) {
                    apee.setTimeAA( new SimpleDateFormat( "yyyy-MM-dd" ).parse( persionDTO.getDescribe() ) );
                }
            }
            catch ( ParseException e ) {
                throw new RuntimeException( e );
            }
            apee.setStrTime( transDate( persionDTO.getBirthDay() ) );
            if ( persionDTO.getAge() != null ) {
                apee.setAp( String.valueOf( persionDTO.getAge() ) );
            }
    
            return apee;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    官方支持的基本类型自动转换大致如下:

    基本类型及其对应的包装类之间:比如, int 和 Integer, float 和 Float, long 和 Long,boolean 和 Boolean 等
    任意基本类型与任意包装类之间:如 int 和 long, byte 和 Integer 等
    所有基本类型及包装类与String之间:如 boolean 和 String, Integer 和 String, float 和 String 等
    枚举和String之间。
    Java大数类型(java.math.BigInteger, java.math.BigDecimal) 和Java基本类型(包括其包装类)与String之间

    数字转String numberFormat

    即 数字类型转String 指定格式下也是支持的
    上面方法修改如下:

    	@Mapping(source = "persionDTO.birthDay", target = "strTime", dateFormat = "yyyy-MM-dd")
        @Mapping(source = "persionDTO.describe", target = "timeAA", dateFormat = "yyyy-MM-dd")
        @Mapping(source = "persionDTO.age", target = "ap", numberFormat = "#0.00岁")
        public abstract Apee transDateToApee(PersionDTO persionDTO);
    
    • 1
    • 2
    • 3
    • 4

    生成的关键代码:

    		if ( persionDTO.getAge() != null ) {
                apee.setAp( new DecimalFormat( "#0.00岁" ).format( persionDTO.getAge() ) );
            }
    
    • 1
    • 2
    • 3

    实际测试输出后:

    Apee{ap='-2.00岁', cp='null', timeAA=Fri Jul 08 00:00:00 CST 2022, strTime='2022-8-12'}
    
    • 1

    6.Expression 表达式以及 Mappings

    Expression 可以用来替代 source 中的一般属性,允许直接写入一般的 java 表达式,即自己提供source 的表达式,但目前支持 Java 一种语言。

    Mappings 可以理解为一个 普通 Mapping 的父标签,允许里面放多个 Mapping 使用 ,分割与直接写 多个Mapping 没什么区别, Expression 与 Mappings 没必要连用,这里只是为了同时介绍
    示例如下:

    @Mappings({
            @Mapping(expression = "java( new java.util.Date() )", target = "timeAA"),
            @Mapping(source = "persionDTO.age", target = "ap")
        })
        public abstract Apee transExpressionToApee(PersionDTO persionDTO);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    生成的代码如下:

        @Override
        public Apee transExpressionToApee(PersionDTO persionDTO) {
            if ( persionDTO == null ) {
                return null;
            }
    
            Apee apee = new Apee();
    
            if ( persionDTO.getAge() != null ) {
                apee.setAp( String.valueOf( persionDTO.getAge() ) );
            }
    
            apee.setTimeAA( new java.util.Date() );
    
            return apee;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    7.转换时 不使用对象中的同名属性 使用指定参数作为某一属性的值

    当我们两个对象中的属性一样,但是并不想使用时可以如下类似定义
    此时 PersonVO PersionDTO均含有PId 但是我并不想使用对象中的值而是采用我传入的值,则如下:

    /**
         * 使用其他值
         * @param persionDTO 
         * @param id
         * @return
         */
        @Mapping(source = "persionDTO.describe", target = "des")
        @Mapping(source = "id", target = "PId")
        public abstract PersonVO transToViewObject2(PersionDTO persionDTO, Long id);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意如果有其他值需要特殊处理时 需要如上指定参数,否则可能编译时出现错误

    生成的代码

        @Override
        public PersonVO transToViewObject2(PersionDTO persionDTO, Long id) {
            if ( persionDTO == null && id == null ) {
                return null;
            }
    
            PersonVO personVO = new PersonVO();
    
            if ( persionDTO != null ) {
                personVO.setDes( persionDTO.getDescribe() );
                personVO.setName( persionDTO.getName() );
                personVO.setAge( persionDTO.getAge() );
                personVO.setSex( persionDTO.getSex() );
            }
            if ( id != null ) {
                personVO.setPId( id );
            }
    
            return personVO;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    8.其他类作为属性时

    与一般的引用类型一致,当两个类中都含有某一个同类型的同名属性时,即使是类也会自动映射,如果类型一致,但是名称不一致,则指定名称后依旧会自动映射
    上面的两个类中添加同一类型的一个类如下:
    在这里插入图片描述
    在这里插入图片描述
    转换方法如下定义:

    	@Mapping(source = "describe", target = "des")
        @Mapping(source = "apee", target = "apee2")
        public abstract PersonVO transToViewObject(PersionDTO persionDTO);
    
    • 1
    • 2
    • 3

    生成代码如下

        @Override
        public PersonVO transToViewObject(PersionDTO persionDTO) {
            if ( persionDTO == null ) {
                return null;
            }
    
            PersonVO personVO = new PersonVO();
    
            personVO.setApee2( persionDTO.getApee() );
            personVO.setDes( persionDTO.getDescribe() );
            personVO.setPId( persionDTO.getPId() );
            personVO.setName( persionDTO.getName() );
            personVO.setAge( persionDTO.getAge() );
            personVO.setSex( persionDTO.getSex() );
    
            return personVO;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    9.逆映射

    即 一个文件内,两个类之间互相转换映射时其实只要写好其中一个映射,另一个可以通过注解继承原有映射配置,即原有的 target 与 source 会互换

        @Mapping(source = "describe", target = "des")
        @Mapping(source = "apee", target = "apee2")
        public abstract PersonVO transToViewObject(PersionDTO persionDTO);
    
        @InheritInverseConfiguration
        public abstract PersionDTO transToViewObject(PersonVO personVO);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    生成代码:

        @Override
        public PersonVO transToViewObject(PersionDTO persionDTO) {
            if ( persionDTO == null ) {
                return null;
            }
    
            PersonVO personVO = new PersonVO();
    
            personVO.setApee2( persionDTO.getApee() );
            personVO.setDes( persionDTO.getDescribe() );
            personVO.setPId( persionDTO.getPId() );
            personVO.setName( persionDTO.getName() );
            personVO.setAge( persionDTO.getAge() );
            personVO.setSex( persionDTO.getSex() );
    
            return personVO;
        }
    
        @Override
        public PersionDTO transToViewObject(PersonVO personVO) {
            if ( personVO == null ) {
                return null;
            }
    
            PersionDTO persionDTO = new PersionDTO();
    
            persionDTO.setApee( personVO.getApee2() );
            persionDTO.setDescribe( personVO.getDes() );
            persionDTO.setPId( personVO.getPId() );
            persionDTO.setName( personVO.getName() );
            persionDTO.setAge( personVO.getAge() );
            persionDTO.setSex( personVO.getSex() );
    
            return persionDTO;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    10.支持注入spring

    在mapper 上添加注解 类似如下:
    在这里插入图片描述
    之后该类可通过 @Autowired 注入方式引入到文件中

    11. 自定义映射 ,list, Map, 枚举

    以下四种映射案例参考了其他文章的代码
    参考博客

    自定义映射 @Named

    // 自定义的映射方法:转换boolen为String时,做一些判断然后返回对应的值。
    @Named("DoneFormater")
    public class DoneFormater {
    
        @Named("DoneFormater")
        public String toStr(Boolean isDone) {
            if (isDone) {
                return "已完成";
            } else {
                return "未完成";
            }
        }
    
        @Named("DoneDetailFormater")
        public String toDetail(Boolean isDone) {
            if (isDone) {
                return "该产品已完成";
            } else {
                return "该产品未完成";
            }
        }
    
        public Boolean toBoolean(String str) {
            if (str.equals("已完成")) {
                return true;
            } else {
                return false;
            }
        }
    
    }
    
    // 通过uses 来导入上面我们写的 自定义映射方法
    @Mapper( uses = {DoneFormater.class})
    public interface ObjectQualiferMapper {
    
        ObjectQualiferMapper INSTANCE = Mappers.getMapper(ObjectQualiferMapper.class);
    	
        // 当有多个方法 拥有一样的参数和返回类型时,需要指定使用其中的哪一个,使用qualifiedByName指定
        @Mapping(source = "isDone", target = "isDone", qualifiedByName = "DoneDetailFormater")
        ProductDTO toDto(Product product);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    List 映射

    @Mapper(componentModel = "spring")
    public interface UserMapping {
    
        /**
         * Student 转化为 User
         * @param Student
         * @return
         */
         User studentToUser(Student student);
         
        // 	当执行 下面这个List的转换时,会遍历list: students,
       	//  然后自动调用上面的Student转User的转换方法,来进行转换
         /**
         * Students 转化为 Users
         * @param Students
         * @return
         */
         List<user> studentsToUsers(List<student> students);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    map 映射

    @Mapper
    public interface MapMapper {
    
        MapMapper INSTANCE = Mappers.getMapper(MapMapper.class);
    
        @MapMapping(valueDateFormat = "yyyy-MM-dd HH:mm:ss")
        Map<string, string=""> toDTO(Map<long, date=""> map);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    枚举

    public enum E1 {
        E1_1,
        E1_2,
        E1_3
    }
    public enum E2 {
        E2_1,
        E2_2,
        E2_3
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    映射方法:使用@ValueMappings和@ValueMapping ,可以理解成是用if判断枚举值,然后返回对应结果

    @Mapper
    public interface DataEnumMapper {
    
        DataEnumMapper INSTANCE = Mappers.getMapper(DataEnumMapper.class);
    
        @ValueMappings({
                @ValueMapping(target = "E1_1", source = "E2_1"),
                @ValueMapping(target = "E1_2", source = "E2_2"),
                @ValueMapping(target = MappingConstants.NULL, source = "E2_3") //转换成null
        })
        E1 toDTO(E2 e2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Final. 其他问题

    出现问题 Couldn’t retrieve @Mapper annotation

    可能是 项目中使用了swagger,swagger里面也包含mapstruct,排除掉就好

    <dependency>
        <groupid>io.springfoxgroupid>
        <artifactid>springfox-swagger2artifactid>
        <version>${swagger2.version}version>
        <scope>compilescope>
        <exclusions>
            <exclusion>
              <groupid>org.mapstructgroupid>
              <artifactid>mapstructartifactid>
            exclusion>
        exclusions>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    也可能是 导入mapstruct项目时,同时使用了 mapstruct-jdk8 和 mapstruct-processor 但版本不一致,将版本号改为一样即可

    更多详细说明参见官网说明文档
    https://mapstruct.org/documentation/stable/reference/html/#defining-mapper

  • 相关阅读:
    Jenkins从放弃到入门:部署、配置与应用
    CSDN 创作规范
    Edge浏览器特殊功能
    路由知识——路由分类+RIP相关知识+链路状态协议和距离矢量协议的区别
    【AWS】AI 代码生成器—Amazon CodeWhisperer初体验 | 开启开挂编程之旅
    Codigger的项目代码检测工具:特性和优势
    STM32F4 磁链观测器+PLL 无感无刷电机位置驱动
    并发-Java中的锁(二)--- 重入锁ReentrantLock,公平锁,非公平锁笔记
    力扣每日一题2022-08-16简单题:设计有序流
    .net core 上传文件大小限制
  • 原文地址:https://blog.csdn.net/weixin_44131922/article/details/126232977