映射框架--MapStruct--001--基本使用

简介

  • MapStruct是满足JSR269规范的一个Java注解处理器,用于为Java Bean生成类型安全且高性能的映射。它基于编译阶段生成get/set代码,此实现过程中没有反射,不会造成额外的性能损失。
  • 您所要做的就是定义一个mapper接口(@Mapper),该接口用于声明所有必须的映射方法。在编译期间MapStruct会为该接口自动生成实现类。该实现类使用简单的Java方法调用来映射source-target对象,在此过程中没有反射或类似的行为发生。
  • 编译时期使用自动生成get和set方法,进行两个值得赋值

优点

  • 与手工编写映射代码相比:
    • MapStruct通过生成冗长且容易出错的代码来节省时间。
  • 与动态映射框架相比:
    • 效率更高:使用简单的Java方法调用代替反射;
    • 编译时类型安全:只能映射相同名称或带映射标记的属性;
    • 编译时产生错误报告:如果映射不完整(存在未被映射的目标属性)或映射不正确(找不到合适的映射方法或类型转换)则会在编译时抛出异常。

相比于常见转换方法的有点

  • 相比于getter/setter方法,省去代码
  • 相比于BeanUtil.copyPropertie进行反射属性赋值,BeanUtil坑巨多,比如sources与target写反,难以定位某个字段在哪里进行的赋值,同时因为用到反射,导致性能也不佳。

坑点(后续实战不断补充)

  • 如果项目中也同时使用到了 Lombok,一定要注意Lombok的版本要等于或者高于1.18.10,否则会有编译不通过的情况发生

实战

  • 导入依赖的jar包
<!--mapStruct-->
<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct</artifactId>
  <version>1.3.1.Final</version>
</dependency>
<dependency>
  <groupId>org.mapstruct</groupId>
  <artifactId>mapstruct-processor</artifactId>
  <version>1.3.1.Final</version>
</dependency>

基本使用

相同名称,不同类型
  • 代码
    • 实体类
    @ToString
    @Data
    public class PersonVo {
    
        private String name;
    
        private Long age;
    }
    
    @Data
    @ToString
    @Accessors(chain = true)
    public class Person {
    
        private String name;
    
        private Integer age;
    }
    
    • 转换类,只需要声明接口,接口上面注明@Mapper标签即可
    //抽象转换类
    public interface BaseMapping<S,T> {
    
        /**
         * 用来映射同名属性
         */
        T sourceToTarget(S source);
    }
    
    //子转换类
    @Mapper
    public interface PersonMapper extends BaseMapping<Person, PersonVo>{
    }
    
    • 测试类
    public class PersonTest {
    
        public static void main(String[] args) {
            PersonMapper mapper = Mappers.getMapper(PersonMapper.class);
            Person person = new Person().setAge(10).setName("lisi");
            PersonVo personVo = mapper.sourceToTarget(person);
            System.out.println(personVo);
        }
    }
    
  • 结果
    • 类型可以自动转换
字段类型不一致,且不可以自动转换:
  • 只要字段名称相等,不区分大小写,且类型可以自动转换的,可以直接进行转换,如果类型不同,则报找不到此set方法,下面的错误是将PersonVo的age改为date
    • 代码
    public class PersonVo {
        private String name;
        private Date age;//区别
    }
    public class Person {
        private String name;
        private Integer age;//区别
    }
    
    Exception in thread "main" java.lang.NoSuchMethodError: org.vo.PersonVo.setAge(Ljava/lang/Long;)V
    	at org.convert.PersonMapperImpl.sourceToTarget(PersonMapperImpl.java:24)
    	at org.convert.PersonMapperImpl.sourceToTarget(PersonMapperImpl.java:7)
    	at org.test.PersonTest.main(PersonTest.java:13)
    
    • 解决方案
      • 修改类型
字段名称不一致:
  • 修改PersonVo中的age名称为age1,报下面错误,可以猜测出内部原理,使用Person的字段名称,调用PersonVo此字段名称的set方法,如果不存在则报错
    • 代码
    public class PersonVo {
        private String name;
        private Integer age;//区别
    }
    public class Person {
        private String name;
        private Integer age1;//区别
    }
    
    Exception in thread "main" java.lang.NoSuchMethodError: org.vo.PersonVo.setAge(Ljava/lang/Long;)V
    	at org.convert.PersonMapperImpl.sourceToTarget(PersonMapperImpl.java:24)
    	at org.convert.PersonMapperImpl.sourceToTarget(PersonMapperImpl.java:7)
    	at org.test.PersonTest.main(PersonTest.java:13)
    
    • 解决方案
      1. 修改名称
      2. 使用@Mapping实现不同名称之间的映射
      public class PersonVo {
          private String name;
          private Integer age;//区别
      }
      public class Person {
          private String name;
          private Integer age1;//区别
      }
      
      
      @Mapper
      public interface PersonMapper extends BaseMapping<Person, PersonVo>{
          
          /**
           * 重写父接口方法,同时表明,名字不同的字段,进行转换
           * source为Person中的字段
           * target为PersonVo中的字段
           * 
           * 只需要声明,名称不同的字段如何对应转换
           * 名称相同的,会自动进行转换
           */
          @Override
          @Mapping(source = "age",target = "age1")
          PersonVo sourceToTarget(Person source);
      }
      
日期类型:
  • 增加一个生日,此生日,Person中存储为String,PersonVo中为Date,转换方式,如果存在多个字段需要自定义转换,使用@Mappings来定义多个转换方式,否则使用@Mapping即可
    • 代码
    @ToString
    @Data
    public class PersonVo {
        private String name;
        private Integer age1;
        private Date birthday;
    }
    
    @Data
    @ToString
    @Accessors(chain = true)
    public class Person {
        private String name;
        private Integer age;
        private String birthday;
    }
    
    @Mapper
    public interface PersonMapper extends BaseMapping<Person, PersonVo>{
    
        /**
         * Person中的birthday的格式必须是"yyyy-MM-dd HH:mm:ss",否则报错
         */
        @Override
        @Mappings({
                @Mapping(source = "age", target = "age1"),
                @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
        })
        PersonVo sourceToTarget(Person source);
    }
    
源字段经过处理之后,映射为目标中的字段:
  • PersonVo中增加一个ageIsRtTen的boolean类型,判断Person中的age 字段,如果大于10返回true,否则false
    • 代码
    @Data
    @ToString
    @Accessors(chain = true)
    public class Person {
        private String name;
        private Integer age;
    }
    
    @ToString
    @Data
    public class PersonVo {
        private String name;
        private Integer age;
        private Boolean ageIsRtTen;
    }
    
    @Mapper
    public interface PersonMapper extends BaseMapping<Person, PersonVo>{
    
        /**
         * 注意两点
         * 1. source和expression不可以同时存在
         * 2. expression的格式,文件类型(静态方法全路径/source中的方法/target中的方法(请求参数的获取方法))
         */
        @Override
        @Mapping(target = "ageIsRtTen",expression = "java(org.expression.AgeUtil.isRtTen(source.getAge()))")
        PersonVo sourceToTarget(Person source);
    }
    
    public class AgeUtil {
    
        public static Boolean isRtTen(Integer age){
            return age!=null && age>10;
        }
    }
    
    //测试类
    public class PersonTest {
    
        public static void main(String[] args) {
            PersonMapper mapper = Mappers.getMapper(PersonMapper.class);
            //生日的格式,必须和转换器中设置的格式相同,否则编译报错
            Person person = new Person().setAge(11).setName("lisi");
            PersonVo personVo = mapper.sourceToTarget(person);
            System.out.println(personVo);
    
            Person person1 = new Person().setAge(1).setName("lisi");
            PersonVo personVo1 = mapper.sourceToTarget(person1);
            System.out.println(personVo1);
        }
    }
    //结果
    PersonVo(name=lisi, age=11, ageIsRtTen=true)
    PersonVo(name=lisi, age=1, ageIsRtTen=false)
    
源字段为字符串,目标字段为枚举类型
字符串到枚举的映射
  • 只能使用expression方法来进行映射
  • 代码
    @Data
    @ToString
    @Accessors(chain = true)
    public class Person {
        private String name;
        private Integer age;
        private String sex;
    }
    
    @ToString
    @Data
    public class PersonVo {
        private String name;
        private Integer age;
        private SexEnum sexEnum;
    }
    
    @Mapper
    public interface PersonMapper extends BaseMapping<Person, PersonVo>{
    
        @Override
        @Mapping(target = "sexEnum",expression = "java(org.constant.SexEnum.convertSexNum(source.getSex()))")
        PersonVo sourceToTarget(Person source);
    }    
    
    public enum SexEnum {
        MAN,
        WOMAN;
        public static SexEnum convertSexNum(String sex){
            return Arrays.stream(SexEnum.values()).filter(s->s.name().equalsIgnoreCase(sex))
                    .findFirst().get();
        }
    }
    
    public class PersonTest {
    
        public static void main(String[] args) {
            PersonMapper mapper = Mappers.getMapper(PersonMapper.class);
            //生日的格式,必须和转换器中设置的格式相同,否则编译报错
            Person person = new Person().setAge(11).setName("lisi").setSex("man");
            PersonVo personVo = mapper.sourceToTarget(person);
            System.out.println(personVo);
        }
    }
    
    //结果
    PersonVo(name=lisi, age=11, sexEnum=MAN)
    
枚举到枚举的映射
  • 源目标是不同的枚举类
  • 代码
    public enum SexEnum {
        MAN,
        WOMAN;
    }
    
    public enum CityEnum {
    
        NANJING,
        SHANGHAI;
    }
    
    @Data
    @ToString
    @Accessors(chain = true)
    public class Person {
        private String name;
        private Integer age;
        private SexEnum sexEnum;
    }
    
    
    @ToString
    @Data
    public class PersonVo {
        private String name;
        private Integer age;
        private CityEnum cityEnum;
    }
    
    @Mapper
    public interface PersonMapper extends BaseMapping<Person, PersonVo>{
    
        @Override
        @Mapping(target = "cityEnum",source = "sexEnum")
        PersonVo sourceToTarget(Person source);
    
    
        
        @ValueMapping(source = "MAN",target = "SHANGHAI")
        @ValueMapping(source = "WOMAN",target = "NANJING")
        CityEnum sexToSexEnum(SexEnum sexEnum);
    }
    
    public class PersonTest {
    
        public static void main(String[] args) {
            PersonMapper mapper = Mappers.getMapper(PersonMapper.class);
            //生日的格式,必须和转换器中设置的格式相同,否则编译报错
            Person person = new Person().setAge(11).setName("lisi").setSexEnum(SexEnum.MAN);
            PersonVo personVo = mapper.sourceToTarget(person);
            System.out.println(personVo);
        }
    }
    
    //结果
    PersonVo(name=lisi, age=11, cityEnum=SHANGHAI)
    
  • 注意
    • 枚举到枚举的映射,除了source到target映射外,还需要增加一个值得映射 @ValueMapping
    • @ValueMapping必须是枚举到枚举的映射,不可以是字符串到枚举的映射,因为此处市将两个枚举的映射关系都进行了声明,而且不管是source还是target都必须是枚举中的常量,大小写也区分,否则报错
    • @ValueMapping相当于一个显示的映射表,必须是枚举到枚举,如果是通过此方法实现string到枚举的映射,则报错,可以看出提示表示,不可以从一个非枚举到枚举的映射
     Error:(25, 13) java: Can't generate mapping method from non-enum type to enum type.
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值