• 【代码优化】Bean映射之MapStruct


    【代码优化】Bean映射之MapStruct

    一、背景

    领域模型相互转换就只能靠手工的 get()/set()

    普遍的做法有以下几种:

    1. 手工 get()/set()
    2. 构造器;
    3. BeanUtils 工具类(ApacheSpring 都包含该工具类,使用方式稍稍不同);
    4. Builder 模式。

    这些方式都存在一些缺点:耦合性强,手工 get()/set() 经常丢参数,或者搞错参数值....

    本文推荐一种效率较高的方式:MapStruct

    二、理论基础

    MapStruct 是一个自动生成 Bean 映射类的代码生成器MapStruct 还能够在不同的数据类型之间进行转换。

    2.1 pom.xml

    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.4.2.Final</version>
    </dependency>
    

    2.2 注解关键词

    • @Mapper:只有在接口加上这个注解, MapStruct 才会去实现该接口;
    • @Mapping:属性映射,若源对象属性与目标对象名字一致,会自动映射对应属性:
      1. source:源属性;
      2. target:目标属性;
      3. dateFormat:字符串与日期之间相互转换;
      4. ignore: 某个属性不想映射,可以加上 ignore=true
      5. expression:自定义指定的映射方法;
    • @Mappings:配置多个@Mapping
    • @MappingTarget:映射到现有示例。

    2.3 工作原理

    我们要做的就是定义一个 mapper 接口,该接口声明任何所需的映射方法。在编译期间,MapStruct 将生成此接口的实现。此实现使用普通的Java方法调用来在源对象和目标对象之间进行映射。

    三、MapStruct 实践

    3.1 基本准备

    • 新增三个数据库 DO 类:

    用户信息:

    @Data
    public class UserInfoDO {
    
        private Long id;
    
        private String userName;
    
        private String password;
    
        private String phoneNum;
    
        private Date gmtBroth;
    
        private RoleDO role;
    
        public UserInfoDO() {
    
        }
    
        public UserInfoDO(RoleDO role,Long id,String userName,String password,String phoneNum,Date gmtBroth) {
            this.role = role;
            this.id = id;
            this.userName = userName;
            this.password = password;
            this.phoneNum = phoneNum;
            this.gmtBroth = gmtBroth;
        }
    }
    

    用户补充信息:

    @Data
    public class UserExtInfoDO {
    
        private String favorite;
    
        public UserExtInfoDO() {
    
        }
    
        public UserExtInfoDO(String favorite) {
            this.favorite = favorite;
        }
    }
    

    角色信息:

    @Data
    public class RoleDO {
    
        private Long id;
        private String roleName;
        private String description;
    
        public RoleDO() {
    
        }
    
        public RoleDO(Long id, String roleName, String description) {
            this.id = id;
            this.roleName = roleName;
            this.description = description;
        }
    }
    
    • 新增一个数据传输 DTO 类:
    @Data
    public class UserInfoDTO {
        /**
         * 用户id
         */
        private Long userId;
        /**
         * 用户名
         */
        private String userName;
        /**
         * 用户名
         */
        private String password;
    
        /**
         * 生日
         */
        private String brothStr;
    
        /**
         * 手机号
         */
        private String phoneNum;
    
        /**
         * 角色名
         */
        private String roleName;
    
        /**
         * 喜好
         */
        private String favorite;
    
    }
    
    • 新增一个加密工具类
    public class Base64Util {
    
        public static String encode(String str) {
            BASE64Encoder encoder = new BASE64Encoder();
            String encode = encoder.encode(str.getBytes());
            return encode;
        }
    }
    
    • 添加映射接口
    @Mapper
    public interface MapstructConvert {
    
        /**
         * 获取该类自动生成的实现类的实例
         */
        MapstructConvert INSTANCE = Mappers.getMapper(MapstructConvert.class);
    }
    
    1. 添加一个 interface 接口,使用 MapStruct@Mapper 注解修饰;
    2. 使用 Mappers 添加一个 INSTANCE 实例(也可以使用 Spring 注入,后面会扩展)。

    3.2 一对一映射

    • 自定义转换时间格式

    通过 dateFormat = "xx" 指定映射的日期格式。

    • 指定默认值

    如果该值为空,则使用指定的默认值,如:defaultValue = "-"

    • 忽略不映射的字段

    可以通过 ignore = true 指定不需要映射的属性,如: @Mapping(target = "password", ignore = true)

    • 嵌套映射

    如果一个 DTO 中的值都是从一个对象中的多个嵌套对象映射时,如果不想一个个写映射,目标可以用 . 表示,如:

    @Mapping(source = "role.roleName", target = "roleName")
    
    • 自定义映射

    当我们映射 DTO 的时候,如果某些参数的值 MapStruct 的映射配置不能满足要求,可以使用自定义方法,例如我们对手机号字段借助工具类进行加密后返回:

    @Mapping(target = "phoneNum", expression = "java(cn.van.spring.copy.mapstruct.util.Base64Util.encode(userInfoDO.getPhoneNum()))")
    
    • 完整代码如下:
    @Mappings({
            @Mapping(source = "id", target = "userId"),
            // 自定义转换时间格式
            @Mapping(source = "gmtBroth", target = "brothStr", dateFormat = "yyyy-MM-dd",defaultValue = "-"),
            // 嵌套映射
            @Mapping(source = "role.roleName", target = "roleName"),
            // 忽略不映射的字段
            @Mapping(target = "password", ignore = true),
            // 自定义映射
            @Mapping(target = "phoneNum", expression = "java(cn.van.spring.copy.mapstruct.util.Base64Util.encode(userInfoDO.getPhoneNum()))"),
    })
    UserInfoDTO doToDTO(UserInfoDO userInfoDO);
    

    3.3 多参数映射

    MapStruct 可以将几种类型的对象映射为另外一种类型,比如将多个 DO 对象转换为一个 DTO

    @Mappings({
                @Mapping(source = "userInfoDO.id", target = "userId"),
                @Mapping(source = "userInfoDO.gmtBroth", target = "brothStr", dateFormat = "yyyy-MM-dd",defaultValue = "-"),
                @Mapping(source = "userInfoDO.role.roleName", target = "roleName"),
                // 忽略不映射的字段
                @Mapping(target = "password", ignore = true),
                // 自定义映射
                @Mapping(target = "phoneNum", expression = "java(cn.van.spring.copy.mapstruct.util.Base64Util.encode(userInfoDO.getPhoneNum()))"),
                @Mapping(source = "userExtInfoDO.favorite", target = "favorite"),
        })
        UserInfoDTO doToDtoMulti(UserInfoDO userInfoDO, UserExtInfoDO userExtInfoDO);
    

    这样,我们就可以把 UserInfoDOUserExtInfoDO 映射为 UserInfoDTO

    3.4 集合映射

    属性映射关系基于一对一的映射关系。

    List<UserInfoDTO> doSToDTOS(List<UserInfoDO> userInfoDOS);
    

    3.5 映射到现有实例

    上面都是映射并生成一个新的实例,如果是想映射到已有的现有实例呢?

    我们只需用 @MappingTarget 修饰。

    3.6 注入 Spring

    上面的示例调用时都是手动创建了一个 MapstructConvert 实例,
    现在都是 Spring 的生态,MapStruct 也可以通过 Spring 注入

    @Mapper(componentModel = "spring")
    public interface SpringMapstructConvert {
    
        /**
         * 一对一映射
         * @param userInfoDO
         * @return
         */
        @Mappings({
                @Mapping(source = "id", target = "userId"),
                // 自定义转换时间格式,如果为空,给予默认值 "-"
                @Mapping(source = "gmtBroth", target = "brothStr", dateFormat = "yyyy-MM-dd",defaultValue = "-"),
                // 嵌套映射
                @Mapping(source = "role.roleName", target = "roleName"),
                // 忽略不映射的字段
                @Mapping(target = "password", ignore = true),
                // 自定义映射
                @Mapping(target = "phoneNum", expression = "java(cn.van.spring.copy.mapstruct.util.Base64Util.encode(userInfoDO.getPhoneNum()))"),
        })
        UserInfoDTO doToDTO(UserInfoDO userInfoDO);
    }
    

    相较于前者:干掉了初始化的 INSTANCE@Mapper 注解加入了 componentModel = "spring"

    注意:默认是以覆盖原有值的方式映射的,如果要保留原有的值,使用 ignore 忽略字段即可。

    四、总结

    • 与手工编写映射代码相比

    MapStruct通过生成繁琐且易于编写的代码来节省时间。遵循约定优于配置方法,MapStruct使用合理的默认值,但在配置或实现特殊行为时

  • 相关阅读:
    api接口安全
    php读取大文件
    thinkphp5.0的工作流程
    php扩展包索引
    redis学习之持久化与内存淘汰机制
    mysql优化之分区
    简练软考知识点整理-控制干系人参与
    简练软考知识点整理-控制干系人参与
    简练软考知识点整理-管理干系人参与
    简练软考知识点整理-管理干系人参与
  • 原文地址:https://www.cnblogs.com/VanFan/p/15800707.html
Copyright © 2020-2023  润新知