【MapStruct】对象转换

news2025/1/11 7:04:17

【MapStruct】对象转换

  • 【一】MapStruct带来的改变
  • 【二】MapStruct 入门
    • 【1】添加依赖
    • 【2】po类
    • 【3】dto类
    • 【4】创建转换接口
    • 【5】测试方法
    • 【6】运行效果
    • 【7】查看编译的class
  • 【三】MapStruct优点分析
    • 【1】性能高
    • 【2】使用简单
    • 【3】代码独立
    • 【4】易于 debug
  • 【四】MapStruct使用案例
    • 【1】属性名称相同
    • 【2】属性名不相同
    • 【3】转换非基础类型属性
    • 【4】Mapper 中使用自定义的转换
    • 【5】多转一
    • 【6】更新 Bean 对象
    • 【7】map映射
    • 【8】多级嵌套
      • (1)方式1
      • (2)方式2
  • 【五】获取 mapper
    • 【1】通过 Mapper 工厂获取
    • 【2】使用依赖注入
    • 【3】依赖注入策略
    • 【4】自定义类型转换

【一】MapStruct带来的改变

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

【二】MapStruct 入门

【1】添加依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.20</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>${org.mapstruct.version}</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>${org.mapstruct.version}</version>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.1.0</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

【2】po类

@Data
public class User {
    private Integer id;
    private String name;
    private String address;
    private Date birth;
}

【3】dto类

@Data
public class UserDto implements Serializable {
    private Integer id;
    private String name;
    private String address;
    private Date birth;
}

【4】创建转换接口

//可以使用abstract class代替接口
@Mapper
public interface UserMapper {
    
    UserDto userToUserDto(User user);
    //集合
    List<UserDto> userToUserDto(List<User> users);
}

【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);
}

【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)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);

这里用到演示了如何使用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);
}

【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> subUserDto;
}
@Data
public class User {
    private Integer id;
    private String name;
    private String address;
    private Date birth;
    private String pwd;
    private List<SubUser> subUser;
}
@Mappings({
        @Mapping(source = "pwd",target = "password"),
        @Mapping(source = "subUser", target = "subUserDto")
})
UserDto userToUserDto(User user);

【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;
    }
}

只能存在一个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);
}

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

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

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

【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);
}

【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);
}

【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);
 
}

(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);
}

【五】获取 mapper

【1】通过 Mapper 工厂获取

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

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

@Mapper
public interface SourceMapper {
 
    SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
 
    // ......
}

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

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

【2】使用依赖注入

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

@Mapper(componentModel = "spring")

【3】依赖注入策略

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

public enum InjectionStrategy {
 
    /** Annotations are written on the field **/
    FIELD,
 
    /** Annotations are written on the constructor **/
    CONSTRUCTOR
}

类似如此使用

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

【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;
        }
    }
}

要注意的是,如果使用了例如像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);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1002692.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【python 多线程】初体验+单线程下载器+多线程并行下载器+ 多进程下载器 以及线程和进程的切换成本比较

前置知识&#xff1a; ref&#xff1a;https://www.osgeo.cn/pillow/reference/ImageFile.html ref&#xff1a;https://blog.csdn.net/weixin_67510296/article/details/125207042 1.多线程初体验 主线程的id和进程的id是一个 查看进程pid下有多少个线程 ps -T -p pid(bas…

《追逐胜利:编程之路上的三子棋游戏实践》

文章目录 前言一、三子棋游戏规则二、步骤详解1.游戏菜单的实现2.棋盘的实现2.1 初始化棋盘2.2 打印棋盘 3.游戏逻辑实现3.1 玩家下棋3.2 电脑下棋 4.判断输赢4.1 win函数实现 5.完整代码 总结 前言 大家好&#xff01;我是艾老虎尤&#xff01;今天我很高兴来和大家分享我最近…

【linux基础(五)】Linux中的开发工具(上)---yum和vim

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到开通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux中的开发工具 1. 前言2.…

CMD 命令和 ENTRYPOINT 命令的区别

目录 CMD 命令CMD-shell 形式1. 创建 Dockerfile12. 构建和运行新镜像3. 覆盖 CMD4. 添加命令选项 CMD-exec形式1. 创建Dockerfile2、构建和运行新镜像2.覆盖 CMD和添加命令选项 ENTRYPOINT 命令ENTRYPOINT-shell1. 创建Dockerfile3、构建和运行新镜像2. 覆盖 ENTRYPOINT 和 添…

华为云云耀云服务器L实例评测|了解配置和管理L型云服务器

华为云云耀云服务器L实例配置和管理教程 华为云云耀云服务器L实例的介绍概述特点优势与弹性云服务器&#xff08;ECS&#xff09;的对比 注册和创建L型云服务器注册华为云账号创建L型云服务器实例配置实例参数配置其他参数尝试登录 远程登录 L实例查看公网ip通过本地shell远程连…

QT Pyside2 Designer 的基本使用

文章目录 前言PySide2PySide2 Designer 一、安装PySide2、PyQt5二、使用designer.exe2.1 工具的大致介绍2.2 创建一个新的UI2.3 UI文件另存为/保存(CtrlS)2.4 使用python操作UI文件 总结 前言 PySide2 QT PySide2 是一个用于 Python 编程语言的开源框架&#xff0c;它提供了与…

【建站教程】使用阿里云服务器怎么搭建网站?

使用阿里云服务器快速搭建网站教程&#xff0c;先为云服务器安装宝塔面板&#xff0c;然后在宝塔面板上新建站点&#xff0c;阿里云服务器网以搭建WordPress网站博客为例&#xff0c;阿小云来详细说下从阿里云服务器CPU内存配置选择、Web环境、域名解析到网站上线全流程&#x…

(10)(10.9) 术语表(一)

文章目录 前言 1 2.4Ghz 2 AGL 3 AHRS 4 APM 5 AMA 6 Arduino 7 APM (AutoPilot Mega) 8 ATC 9 Copter 10 Plane 11 Rover 12 BEC 13 Bootloader 14 COA 15 DCM 16 Eagle file 17 ESC 18 Firmware 19 FPV 20 FTDI 前言 &#xff01;Note 术语表未编入索…

C++零碎记录(十二)

22. 菱形继承 22.1 菱形继承简介 ① 菱形继承概念&#xff1a; 1. 两个派生类继承同一个基类 2. 又有某个类同时继承两个派生类 3. 这种继承被称为菱形继承 ② 羊继承了动物的数据&#xff0c;驼同样继承了动物的数据&#xff0c;当草泥马使用数据是&#xff0c;就会产生二义…

重构:在新底座之上让应用重生

应用重构正在开启一条云原生时代的新赛道。 数字化发展到今天&#xff0c;企业面临的挑战不仅来自技术层面&#xff0c;更来自认知层面。新架构、新应用正在重新定义数字生产力&#xff0c;重塑商业模式与市场核心竞争力。对金融行业来说&#xff0c;也是如此&#xff0c;一场…

计算机网络第六章——应用层(上)

人生若只如初见&#xff0c;何事秋风悲画扇 文章目录 基于服务的使用以及服务的提供而诞生的两个应用模型&#xff0c; 传输层提供一种端到端的服务&#xff0c;但是不同的网络应用的应用进程之间还需要有一些不同的通信规则&#xff0c;因此在传输层之上建立了一个应用层&am…

信息化管理工程验收评测规范

一、信息工程验收程序&#xff1a; 1.信息化建设项目验收分为初步验收和竣工验收两个阶段。验收由建设单位自行对照招标文件、投标文件和合同执行&#xff0c;并提交初验报告&#xff1b;企业与第三方联系实施验收。 2.工程试运行后30个工作日内&#xff0c;项目建设单位应将…

[计算机入门] 设置日期和时间

3.8 设置日期和时间 在任务栏的最右边是可以看到当前的日期和时间的。当然&#xff0c;如果这里的显示不对&#xff0c;也是可以进行设置的。 1、在任务栏的日期和时间位置&#xff0c;右键鼠标&#xff0c;在弹出的菜单中&#xff0c;点击调整日期/时间。 2、一般情况下&am…

easypoi和poi版本兼容问题记录

最近在开发导出word的功能&#xff0c;遇到下面的问题 提示xml报错的问题&#xff0c;我一度以为是项目换了java11造成的。经过询问朋友&#xff0c;得知有可能是版本冲突造成的&#xff0c;就猛然想起来&#xff0c;我的项目里面还引入了poi这个包。 于是我吧poi的版本降低到了…

【算法训练-数组 五】【二分查找】:旋转数组的最小数字、旋转数组的指定数字

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【数组的二分查找】&#xff0c;使用【数组】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为…

C#,《小白学程序》第二十一课:大数(BigInteger)的四则运算之二,减法

1 文本格式 using System; using System.Linq; using System.Text; using System.Collections.Generic; /// <summary> /// 大数的&#xff08;加减乘除&#xff09;四则运算、阶乘运算 /// 乘法计算包括小学生算法、Karatsuba和Toom-Cook3算法 /// </summary> p…

GO语言网络编程(并发编程)Goroutine池

GO语言网络编程&#xff08;并发编程&#xff09;Goroutine池 1. Goroutine池 1.1.1. worker pool&#xff08;goroutine池&#xff09; 本质上是生产者消费者模型可以有效控制goroutine数量&#xff0c;防止暴涨需求&#xff1a; 计算一个数字的各个位数之和&#xff0c;例…

2.5 循环结构语句

在程序设计中&#xff0c;有时需要反复执行一段相同的代码&#xff0c;这时就需要使用循环结构来实现&#xff0c;Java语言提供了while循环、do-while循环、for循环。 一般情况下&#xff0c;一个循环结构包含四部分内容&#xff1a; 初始化部分&#xff0c;设置循环开始时变量…

正交试验设计法

正交实验设计 一、什么是正交试验设计法&#xff1f; 是一种成对测试交互的系统的统计方法。它提供了一种能对所有变量对的组合进行典型覆盖&#xff08;均匀分布&#xff09;的方法。 可以从大量的试验点中挑出适量的、有代表性的点&#xff0c;利用“正交表”&#xff0c;…

Rsync远程同步+inotify监控

一、rsync同步简介 一款快速增量备份工具 rsync&#xff08;Remote Sync&#xff0c;远程同步&#xff09; 是一个开源的快速备份工具&#xff0c;可支持本地复制&#xff0c;或者与其他SSH,rsync主机同步。 cp&#xff1a;将原文件完整的复制到指定的路径下&#xff0c;而且…