zoukankan      html  css  js  c++  java
  • MapStruct 使用详解

    一、前言

    通常在后端开发中经常不直接返回实体Entity类,经过处理转换返回前端,前端提交过来的对象也需要经过转换Entity实体才做存储;通常使用的BeanUtils.copyProperties方法也比较粗暴,不仅效率低下(使用反射)而且仅映射相同名的属性,多数情况下还需要手动编写对应的转换方法实现。
    插件MapStruct以接口方法结合注解优雅实现对象转换,MapStruct生成器生成代码以更贴近原生的Setter、Getter方法处理属性映射更为高效。
    https://github.com/mapstruct/mapstruct/
    https://github.com/mapstruct/mapstruct-examples

    二、简单用例

    实体对象User

    @Data
    @AllArgsConstructor
    public class User {
        private int id;
        private String name;
        private int age;
        private String address;
    }
    

    转换对象UserVO

    @Data
    public class UserVO {
        private String userName;
        private int age;
    }
    

    转换接口

    @Mapper
    public interface UserConvert {
        UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
        @Mapping(source = "name", target = "userName")
        UserVO toVO(User entity);
    }
    

    使用示例

    @Test
    public void contextLoads() {
        User user = new User(0, "Tester", 1, "上海市徐汇区");
        UserVO userVO = UserConvert.INSTANCE.toVO(user);
    }
    

    三、使用详解

    3.1、关于接口注解@Mapper几种属性用法详解

    uses 使用其他手动编写的或者其他Mapper接口覆写相关的转换方法,不能循环引用

    @Mapper(uses=DateMapper.class)
    

    imports 引用相关类,允许通过mapping.expression()mapping.defaultExpression()直接使用这些类型

    @Mapper(imports = DateUtil.class)
    public interface UserConvert {
        UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
        @Mappings({
                @Mapping(source = "name", target = "userName"),
                // 以指定方法转换属性,这里如果不使用imports,则需要写全引用类包路径
                @Mapping(target = "birthday", expression = "java(DateUtil.formatDate(entity.getBirthday()))")
        })
        UserVO toVO(User entity);
    }
    

    unmappedSourcePolicyunmappedTargetPolicy 针对源类型/目标类型中未映射属性的反馈策略
    typeConversionPolicy 针对有损转换的反馈策略,例如Long转Integer
    反馈策略主要有三种:IGNORE,默认值,忽略未映射的字段。WARN,警告。ERROR,报错

    @Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR)
    

    componentModel 指定生成映射器实例的模式,主要有四种:
    default,不主动生成实例,通常以Mappers.getMapper(Class)实例化。
    cdi,以CDI标准实例化映射器,使用@Inject注入相关实例,
    spring,Spring Bean方式实例化,
    jsr330,jsr330标准实例化

    @Mapper(componentModel = "spring")
    public interface UserConvert {
        @Mapping(source = "name", target = "userName")
        UserVO toVO(User entity);
    }

    @Autowired
    private UserConvert userConvert;
    

    implementationName 指定实现类名称,映射生成器接口会自动生成实现类<CLASS_NAME>Impl,使用此属性可避免类冲突
    implementationPackage 指定实现类包路径

    config 指定配置类,由指定的@MapperConfig配置类,config导入相关配置
    配置类

    @MapperConfig(
            uses = DateUtil.class,
            unmappedSourcePolicy = ReportingPolicy.WARN
    )
    public interface UserConfig {
    }
    

    导入配置类

    @Mapper(componentModel = "spring", config = UserConfig.class)
    

    collectionMappingStrategy 集合映射策略,这里注意集合映射时,如果集合中的类型已有对应转换方法,集合转换时会优先使用

    @Mappings({
            @Mapping(source = "name", target = "userName"),
            @Mapping(target = "birthday", expression = "java(DateUtil.formatDate(entity.getBirthday()))")
    })
    UserVO toVO(User entity);
    List<UserVO> collectionCvt(List<User> entities);
    

    GENERATED CODE 生成器生成代码

    public List<UserVO> collectionCvt(List<User> entities) {
        if (entities == null) {
            return null;
        } else {
            List<UserVO> list = new ArrayList(entities.size());
            Iterator var3 = entities.iterator();
    
            while(var3.hasNext()) {
                User user = (User)var3.next();
                // 集合转换时优先使用了已定义的toVO方法
                list.add(this.toVO(user));
            }
    
            return list;
        }
    }
    

    nullValueMappingStrategy null作为源值映射策略;RETURN_NULL默认返回null, RETURN_DEFAULT返回默认值,对于对象会通过构造器自动构造对象返回,集合会返回空集合

    当值为RETURN_DEFAULT时,如果映射规则中包含Mapping.expression、Mapping.constant必须手动判空处理,否则NPE

    @MapperConfig(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT)
    

    NullValuePropertyMappingStrategy null作为源属性映射策略;SET_TO_NULL默认返回null,SET_TO_DEFAULT返回默认值,IGNORE 忽略该值,以目标对象已存在的值为准

    MappingInheritanceStrategy 继承方法级映射配置策略:EXPLICIT 显示使用InheritConfiguration生效。AUTO_INHERIT_FROM_CONFIG 自动继承正向转换的配置。AUTO_INHERIT_REVERSE_FROM_CONFIG 自动继承反向转换的配置。AUTO_INHERIT_ALL_FROM_CONFIG 都继承

    @MapperConfig(
            mappingInheritanceStrategy = MappingInheritanceStrategy.EXPLICIT
    )
    
     
    @InheritConfiguration
    void cvtVO(User entity, @MappingTarget UserVO vo);
    

    nullValueCheckStrategy 空值监测策略

    3.2、其他方法级别注解

    @InheritInverseConfiguration 反向转换时继承映射规则

    @Mapping 配置类型属性的映射规则;
    dateFormat 格式化日期

    @Mapping(target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
    

    numberFormat 数字格式化

    @Mapping(target = "price", numberFormat = "$#.00")
    

    constant 常量

    @Mapping(target = "age", constant = "0")

    四、特殊情况处理

    上面已经把整个流程都给过了一遍了,对 mapstruct 也有了一个基础的了解了,接下来处理一些特殊情况。

    4.1、类型不一致

    实体类我们还是沿用 User;被映射对象 UserVO3 改为:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class UserVO3 {
        private String id;
        private String name;
        // 实体类该属性是String
        private LocalDateTime createTime;
        // 实体类该属性是LocalDateTime
        private String updateTime;
    }
    

    那么我们定义的接口就要稍稍修改一下了:

        @Mappings({
                @Mapping(target = "createTime", expression = "java(com.java.mmzsblog.util.DateTransform.strToDate(source.getCreateTime()))"),
        })
        UserVO3 toConvertVO3(User source);
    
        User fromConvertEntity3(UserVO3 userVO3);
    

    上面 expression 指定的表达式内容如下:

    public class DateTransform {
        public static LocalDateTime strToDate(String str){
            DateTimeFormatter df = DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm:ss");
            return LocalDateTime.parse("2018-01-12 17:07:05",df);
        }
    
    }
    

    通过IDE的反编译功能查看编译后的实现类,结果是这样子的:

    从图中我们可以看到,编译时使用了expression中定义的表达式对目标字段 createTime 进行了转换;然后你还会发现 updateTime 字段也被自动从 LocalDateTime 类型转换成了 String 类型。

    小结

    当字段类型不一致时,以下的类型之间是 mapstruct 自动进行类型转换的:

    • 1、基本类型及其他们对应的包装类型。
      此时 mapstruct 会自动进行拆装箱。不需要人为的处理
    • 2、基本类型的包装类型和string类型之间

    除此之外的类型转换我们可以通过定义表达式来进行指定转换。

    4.2、字段名不一致

    实体类我们还是沿用 User;被映射对象 UserVO4 改为:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class UserVO4 {
        // 实体类该属性名是id
        private String userId;
        // 实体类该属性名是name
        private String userName;
        private String createTime;
        private String updateTime;
    }
    

    那么我们定义的接口就要稍稍修改一下了:

        @Mappings({
                @Mapping(source = "id", target = "userId"),
                @Mapping(source = "name", target = "userName")
        })
        UserVO4 toConvertVO(User source);
        
        User fromConvertEntity(UserVO4 userVO4);
    

    通过IDE的反编译功能查看编译后的实现类,编译后的结果是这样子的:

    很明显, mapstruct 通过读取我们配置的字段名对应关系,帮我们把它们赋值在了相对应的位置上,可以说是相当优秀了,但这也仅仅是优秀,而更秀的还请继续往下看。

    小结

    当字段名不一致时,通过使用 @Mappings 注解指定对应关系,编译后即可实现对应字段的赋值。

    3.3、属性是枚举类型

    实体类我们还是改用 UserEnum:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class UserEnum {
        private Integer id;
        private String name;
        private UserTypeEnum userTypeEnum;
    }
    

    被映射对象 UserVO5 改为:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public class UserVO5 {
        private Integer id;
        private String name;
        private String type;
    }
    

    枚举对象是:

    @Getter
    @AllArgsConstructor
    public enum UserTypeEnum {
        Java("000", "Java开发工程师"),
        DB("001", "数据库管理员"),
        LINUX("002", "Linux运维员");
        
        private String value;
        private String title;
    
    }
    

    那么我们定义的接口还是照常定义,不会受到它是枚举就有所变化:

        @Mapping(source = "userTypeEnum", target = "type")
        UserVO5 toConvertVO5(UserEnum source);
    
        UserEnum fromConvertEntity5(UserVO5 userVO5);
    

    通过IDE的反编译功能查看编译后的实现类,编译后的结果是这样子的:

    很明显, mapstruct 通过枚举类型的内容,帮我们把枚举类型转换成字符串,并给type赋值,可谓是小心使得万年船啊。

  • 相关阅读:
    1.2顺序表
    1.1数据结构
    Java 造假数据
    Python造假数据,用这个库
    真香 用这七大Python效率工具
    mybatis 详情
    MySQL 的 INSERT ··· ON DUPLICATE KEY UPDATE
    mysql之case when then 经典用法
    SELECT NOW(),CURDATE(),CURTIME()
    MySQL CONCAT_WS 函数
  • 原文地址:https://www.cnblogs.com/johnvwan/p/12159854.html
Copyright © 2011-2022 走看看