• 【MapStruct】对象转换


    【一】MapStruct带来的改变

    MapSturct 是一个生成类型安全,高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)。
    (1)注解处理器
    (2)可以生成 JavaBean 之间那的映射代码
    (3)类型安全,高性能,无依赖性

    【二】MapStruct 入门

    【1】添加依赖

    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <version>1.16.20version>
        <scope>providedscope>
    dependency>
    <dependency>
        <groupId>org.mapstructgroupId>
        <artifactId>mapstruct-jdk8artifactId>
        <version>${org.mapstruct.version}version>
    dependency>
    <dependency>
        <groupId>org.mapstructgroupId>
        <artifactId>mapstruct-processorartifactId>
        <version>${org.mapstruct.version}version>
    dependency>
    <dependency>
        <groupId>cn.hutoolgroupId>
        <artifactId>hutool-allartifactId>
        <version>5.1.0version>
    dependency>
    <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>4.12version>
        <scope>testscope>
    dependency>
    
    • 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

    【2】po类

    @Data
    public class User {
        private Integer id;
        private String name;
        private String address;
        private Date birth;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    【3】dto类

    @Data
    public class UserDto implements Serializable {
        private Integer id;
        private String name;
        private String address;
        private Date birth;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    【4】创建转换接口

    //可以使用abstract class代替接口
    @Mapper
    public interface UserMapper {
        
        UserDto userToUserDto(User user);
        //集合
        List<UserDto> userToUserDto(List<User> users);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    【5】测试方法

    @Test
    public void userPoToUserDto() {
        User user =new User();
        user.setId(1);
        user.setName("myx");
        user.setAddress("江苏苏州");
        user.setBirth(new Date());
        UserMapper mapper = Mappers.getMapper(UserMapper.class);
        UserDto userDto = mapper.userToUserDto(user);
        System.out.println(userDto);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    【6】运行效果

    在这里插入图片描述

    【7】查看编译的class

    底层通过自动取值赋值操作完成

    【三】MapStruct优点分析

    【1】性能高

    这是相对反射来说的,反射需要去读取字节码的内容,花销会比较大。而通过 MapStruct 来生成的代码,其类似于人手写。速度上可以得到保证。

    【2】使用简单

    如果是完全映射的,使用起来肯定没有反射简单。用类似 BeanUtils 这些工具一条语句就搞定了。但是,如果需要进行特殊的匹配(特殊类型转换,多对一转换等),其相对来说也是比较简单的。

    基本上,使用的时候,我们只需要声明一个接口,接口下写对应的方法,就可以使用了。当然,如果有特殊情况,是需要额外处理的。

    【3】代码独立

    生成的代码是对立的,没有运行时的依赖。

    【4】易于 debug

    在我们生成的代码中,我们可以轻易的进行 debug。

    【四】MapStruct使用案例

    【1】属性名称相同

    在实现类的时候,如果属性名称相同,则会进行对应的转化。通过此种方式,我们可以快速的编写出转换的方法。(入门案例)

    【2】属性名不相同

    属性名不相同,在需要进行互相转化的时候,则我们可以通过@Mapping 注解来进行转化。

    @Data
    public class UserDto implements Serializable {
        private Integer id;
        private String name;
        private String address;
        private Date birth;
        private String password;
    }
    @Data
    public class User {
        private Integer id;
        private String name;
        private String address;
        private Date birth;
        private String pwd;
    }
    @Mapper
    public interface UserMapper {
        //单个属性
        //@Mapping(source = "pwd",target = "password")
        //多个属性
        @Mappings({
                @Mapping(source = "pwd",target = "password")
        })
        UserDto userToUserDto(User user);
    }
    
    • 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

    (1)source 需要转换的对接,通常是入参
    (2)target 转换的对接,通常是出参
    (3)ignore 忽略,默认false不忽略,需要忽略设置为true
    (4)defaultValue 默认值
    (5)expressions 可以通过表达式来构造一些简单的转化关系。虽然设计的时候想兼容很多语言,不过目前只能写Java代码。

    @Mappings({
                @Mapping(source = "birthdate", target = "birth"),//属性名不一致映射
                @Mapping(target = "birthformat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthdate(),\"yyyy-MM-dd HH:mm:ss\"))"),//自定义属性通过java代码映射
        })
    public PersonVo PersonToPersonVo(Person person);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里用到演示了如何使用TimeAndFormat对time和format操作,这里必须要指定需要使用的Java类的完整包名,不然编译的时候不知道你使用哪个Java类,会报错。

    @Test
    public void userPoToUserDto() {
        User user =new User();
        user.setId(1);
        user.setName("myx");
        user.setAddress("江苏苏州");
        user.setBirth(new Date());
        user.setPwd("123456");
        UserMapper mapper = Mappers.getMapper(UserMapper.class);
        UserDto userDto = mapper.userToUserDto(user);
        System.out.println(userDto);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    【3】转换非基础类型属性

    如果subUser与subUserDto字段名称相同直接配置即可完成(对象类型,包括list)

    @Data
    public class UserDto implements Serializable {
        private Integer id;
        private String name;
        private String address;
        private Date birth;
        private String password;
        private List subUserDto;
    }
    @Data
    public class User {
        private Integer id;
        private String name;
        private String address;
        private Date birth;
        private String pwd;
        private List subUser;
    }
    @Mappings({
            @Mapping(source = "pwd",target = "password"),
            @Mapping(source = "subUser", target = "subUserDto")
    })
    UserDto userToUserDto(User user);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    【4】Mapper 中使用自定义的转换

    有时候,对于某些类型,无法通过代码生成器的形式来进行处理。那么, 就需要自定义的方法来进行转换。这时候,我们可以在接口(同一个接口,后续还有调用别的 Mapper 的方法)中定义默认方法(Java8及之后)。

    @Data
    public class UserDto implements Serializable {
        private Integer id;
        private String name;
        private String address;
        private Date birth;
        private String password;
        private SubUserDto subUserDto;
    }
     
    @Data
    public class SubUserDto {
        private Boolean result;
        private String name;
    }
    @Data
    public class User {
        private Integer id;
        private String name;
        private String address;
        private Date birth;
        private String pwd;
        private SubUser subUser;
    }
     
    @Data
    public class SubUser {
        private Integer deleted;
        private String name;
    }
    @Mapper
    public interface UserMapper {
        @Mappings({
                @Mapping(source = "pwd",target = "password"),
                @Mapping(source = "subUser", target = "subUserDto")
        })
        UserDto userToUserDto(User user);
     
        default SubUserDto subSource2subTarget(SubUser subUser) {
            if (subUser == null) {
                return null;
            }
            SubUserDto subUserDto = new SubUserDto();
            subUserDto.setResult(!subUser.getDeleted().equals(0));
            subUserDto.setName(subUser.getName()==null?"":subUser.getName());
            return subUserDto;
        }
    }
    
    • 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

    只能存在一个default修饰的方法

    @Test
    public void userPoToUserDto() {
        User user =new User();
        user.setId(1);
        user.setName("myx");
        user.setAddress("江苏苏州");
        user.setBirth(new Date());
        user.setPwd("123456");
        SubUser subUser =new SubUser();
        subUser.setDeleted(0);
        subUser.setName("rkw");
        user.setSubUser(subUser);
        UserMapper mapper = Mappers.getMapper(UserMapper.class);
        UserDto userDto = mapper.userToUserDto(user);
        System.out.println(userDto);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    【5】多转一

    我们在实际的业务中少不了将多个对象转换成一个的场景。MapStruct 当然也支持多转一的操作。

    @Data
    public class SubUser {
        private Integer deleted;
        private String name;
    }
    @Data
    public class User {
        private Integer id;
        private String name;
        private String address;
        private Date birth;
        private String pwd;
    }
    @Mapper
    public interface UserMapper {
        @Mappings({
                @Mapping(source = "user.pwd",target = "password"),
                @Mapping(source = "subUser.name", target = "name")
        })
        NewUserDto userToUserDto(User user,SubUser subUser);
    }
    @Test
    public void userPoToUserDto() {
        User user =new User();
        user.setId(1);
        user.setName("myx");
        user.setAddress("江苏苏州");
        user.setBirth(new Date());
        user.setPwd("123456");
        SubUser subUser =new SubUser();
        subUser.setDeleted(0);
        subUser.setName("rkw");
        UserMapper mapper = Mappers.getMapper(UserMapper.class);
        NewUserDto userDto = mapper.userToUserDto(user,subUser);
        System.out.println(userDto);
    }
    
    • 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

    (1)遵循原则
    1-当多个对象中, 有其中一个为 null, 则会直接返回 null
    2-如一对一转换一样, 属性通过名字来自动匹配。因此, 名称和类型相同的不需要进行特殊处理
    3-当多个原对象中,有相同名字的属性时,需要通过 @Mapping 注解来具体的指定, 以免出现歧义(不指定会报错)。如上面的 name

    属性也可以直接从传入的参数来赋值

    @Mapping(source = "person.description", target = "description")
    @Mapping(source = "name", target = "name")
    DeliveryAddress personAndAddressToDeliveryAddressDto(Person person, String name);
    
    • 1
    • 2
    • 3

    【6】更新 Bean 对象

    有时候,我们不是想返回一个新的 Bean 对象,而是希望更新传入对象的一些属性。这个在实际的时候也会经常使用到。

    @Mapper
    public interface UserMapper {
     
        NewUserDto userToNewUserDto(User user);
     
        /**
         * 更新, 注意注解 @MappingTarget
         * 注解 @MappingTarget后面跟的对象会被更新。
         */
        void updateDeliveryAddressFromAddress(SubUser subUser,@MappingTarget NewUserDto newUserDto);
    }
    @Test
    public void userPoToUserDto() {
        User user =new User();
        user.setId(1);
        user.setName("myx");
        user.setAddress("江苏苏州");
        user.setBirth(new Date());
        SubUser subUser =new SubUser();
        subUser.setDeleted(0);
        subUser.setName("rkw");
        UserMapper mapper = Mappers.getMapper(UserMapper.class);
        NewUserDto userDto = mapper.userToNewUserDto(user);
        mapper.updateDeliveryAddressFromAddress(subUser,userDto);
        System.out.println(userDto);
    }
    
    • 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

    【7】map映射

    @MapMapping(valueDateFormat ="yyyy-MM-dd HH:mm:ss")
    public Map<String ,String> DateMapToStringMap(Map<String,Date> sourceMap);
    @Test
    public void mapMappingTest(){
        Map<String,Date> map=new HashMap<>();
        map.put("key1",new Date());
        map.put("key2",new Date(new Date().getTime()+9800000));
        Map<String, String> stringObjectMap = TestMapper.MAPPER.DateMapToStringMap(map);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    【8】多级嵌套

    只需要在mapper接口中定义相关的类型转换方法即可,list类型也适用

    (1)方式1

    @Data
    public class User {
        private Integer id;
        private String name;
        private String address;
        private Date birth;
        private Boolean isDisable;
        private List<SubUser> user;
    }
     
    @Data
    public class SubUser {
        private Integer deleted;
        private String name;
        private List<SubSubUser> subUser;
    }
    @Data
    public class SubSubUser {
        private String aaa;
        private String ccc;
    }
    @Data
    public class UserDto implements Serializable {
        private Integer id;
        private String name;
        private String address;
        private Date birth;
        private String isDisable;
        private List<SubUserDto> user;
    }
    @Data
    public class SubUserDto {
        private Integer deleted;
        private String name;
        private List<SubSubUserDto> subUser;
    }
    @Data
    public class SubSubUserDto {
        private String aaa;
        private  String bbb;
    }
    @Mapper
    public interface UserMapper {
     
        UserDto userToNewUserDto(User user);
        
        //子集字段相同方法不用编写会自动生成
        
        //孙子集字段不相同(list会自动读取此方法生成list)
        @Mapping(source = "ccc",target = "bbb")
        SubSubUserDto bbb(SubSubUser subSubUser);
     
    }
    
    • 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

    (2)方式2

    通过uses配置类型转换

    @Mapper(uses = {TestMapper.class})
    public interface UserMapper {
        UserDto userToNewUserDto(User user);
    }
    @Mapper
    public interface TestMapper {
        @Mapping(source = "ccc",target = "bbb")
        SubSubUserDto bbb(SubSubUser subSubUser);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    【五】获取 mapper

    【1】通过 Mapper 工厂获取

    我们都是通过 Mappers.getMapper(xxx.class) 的方式来进行对应 Mapper 的获取。此种方法为通过 Mapper 工厂获取。

    如果是此种方法,约定俗成的是在接口内定义一个接口本身的实例 INSTANCE, 以方便获取对应的实例。

    @Mapper
    public interface SourceMapper {
     
        SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
     
        // ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这样在调用的时候,我们就不需要在重复的去实例化对象了。类似下面

    Target target = SourceMapper.INSTANCE.source2target(source);
    
    • 1

    【2】使用依赖注入

    对于 Web 开发,依赖注入应该很熟悉。MapSturct 也支持使用依赖注入,同时也推荐使用依赖注入。
    在这里插入图片描述

    @Mapper(componentModel = "spring")
    
    • 1

    【3】依赖注入策略

    可以选择是通过构造方法或者属性注入,默认是属性注入。

    public enum InjectionStrategy {
     
        /** Annotations are written on the field **/
        FIELD,
     
        /** Annotations are written on the constructor **/
        CONSTRUCTOR
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    类似如此使用

    @Mapper(componentModel = "cdi" injectionStrategy = InjectionStrategy.CONSTRUCTOR)
    
    • 1

    【4】自定义类型转换

    有时候,在对象转换的时候可能会出现这样一个问题,就是源对象中的类型是Boolean类型,而目标对象类型是String类型,这种情况可以通过@Mapper的uses属性来实现:

    @Data
    public class User {
        private Integer id;
        private String name;
        private String address;
        private Date birth;
        private Boolean isDisable;
    }
    @Data
    public class UserDto implements Serializable {
        private Integer id;
        private String name;
        private String address;
        private Date birth;
        private String isDisable;
    }
    @Mapper(uses = {BooleanStrFormat.class})
    public interface UserMapper {
        UserDto userToNewUserDto(User user);
    }
    public class BooleanStrFormat {
        public String toStr(Boolean isDisable) {
            if (isDisable) {
                return "Y";
            } else {
                return "N";
            }
        }
        public Boolean toBoolean(String str) {
            if (str.equals("Y")) {
                return true;
            } else {
                return false;
            }
        }
    }
    
    • 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

    要注意的是,如果使用了例如像spring这样的环境,Mapper引入uses类实例的方式将是自动注入,那么这个类也应该纳入Spring容器

    @Test
    public void userPoToUserDto() {
        User user =new User();
        user.setId(1);
        user.setName("myx");
        user.setAddress("江苏苏州");
        user.setBirth(new Date());
        user.setIsDisable(true);
        SubUser subUser =new SubUser();
        subUser.setDeleted(0);
        subUser.setName("rkw");
        UserMapper mapper = Mappers.getMapper(UserMapper.class);
        UserDto userDto = mapper.userToNewUserDto(user);
        System.out.println(userDto);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    PLL深度解析第一篇——PLL的知识图谱
    华为机试真题 C++ 实现【运维日志排序】
    Android组件化实现,理解吸收
    一文掌握SSD、EMMC、UFS原理与差异
    猿创征文|C# DataSet类的简单学习
    蓝牙技术|物联网的可穿戴设备新工作模式,蓝牙BLE助力新工作模式
    vue-element-admin—登录页面添加自定义背景
    时序预测 | MATLAB实现POA-CNN-BiGRU鹈鹕算法优化卷积双向门控循环单元时间序列预测
    AgileBoot - 项目内统一的错误码设计
    EFCore之执行原生SQL语句
  • 原文地址:https://blog.csdn.net/weixin_44823875/article/details/132809578