【MapStruct】对象转换

【MapStruct】对象转换

【一】MapStruct带来的改变

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

(1)注解处理器

(2)可以生成 JavaBean 之间那的映射代码

(3)类型安全,高性能,无依赖性

【二】MapStruct 入门

【1】添加依赖

xml 复制代码
<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类

java 复制代码
@Data
public class User {
    private Integer id;
    private String name;
    private String address;
    private Date birth;
}

【3】dto类

java 复制代码
@Data
public class UserDto implements Serializable {
    private Integer id;
    private String name;
    private String address;
    private Date birth;
}

【4】创建转换接口

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

【5】测试方法

java 复制代码
@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 注解来进行转化。

java 复制代码
@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代码。

powershell 复制代码
@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类,会报错。

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)

powershell 复制代码
@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及之后)。

powershell 复制代码
@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修饰的方法

powershell 复制代码
@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 当然也支持多转一的操作。

java 复制代码
@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

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

java 复制代码
@Mapping(source = "person.description", target = "description")
@Mapping(source = "name", target = "name")
DeliveryAddress personAndAddressToDeliveryAddressDto(Person person, String name);

【6】更新 Bean 对象

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

java 复制代码
@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映射

java 复制代码
@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

java 复制代码
@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配置类型转换

java 复制代码
@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, 以方便获取对应的实例。

java 复制代码
@Mapper
public interface SourceMapper {
 
    SourceMapper INSTANCE = Mappers.getMapper(SourceMapper.class);
 
    // ......
}

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

powershell 复制代码
Target target = SourceMapper.INSTANCE.source2target(source);

【2】使用依赖注入

对于 Web 开发,依赖注入应该很熟悉。MapSturct 也支持使用依赖注入,同时也推荐使用依赖注入。

java 复制代码
@Mapper(componentModel = "spring")

【3】依赖注入策略

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

java 复制代码
public enum InjectionStrategy {
 
    /** Annotations are written on the field **/
    FIELD,
 
    /** Annotations are written on the constructor **/
    CONSTRUCTOR
}

类似如此使用

java 复制代码
@Mapper(componentModel = "cdi" injectionStrategy = InjectionStrategy.CONSTRUCTOR)

【4】自定义类型转换

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

java 复制代码
@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容器

java 复制代码
@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);
}
相关推荐
ttwuai11 分钟前
Go 后台接口 401/403 排查:JWT 过期、刷新请求和权限码怎么定位
开发语言·golang·状态模式
李可以量化14 分钟前
PTrade 量化入门必学:get\_trading\_day 交易日函数全解与实战(下)
python
柒和远方15 分钟前
LeetCode 139. 单词拆分 —— 从暴力回溯到 DP 完全背包
javascript·python·算法
漫随流水33 分钟前
Java——springboot web案例
java·开发语言·spring boot
从此以后自律40 分钟前
Java Object 类常用方法全讲解
java·开发语言
半兽先生44 分钟前
大模型实现金融文本分类
人工智能·python·金融
汤姆小白1 小时前
06-LoRA参数高效微调
人工智能·python·机器学习·transformer
XH华1 小时前
C++语言第二章类和对象(下)
开发语言·c++
从零开始的代码生活_1 小时前
C++ stack、queue 与 priority_queue:容器适配器原理与实战
开发语言·c++·后端·学习·算法
铅笔侠_小龙虾1 小时前
Rust 学习(6)-所有权规则、移动语义、Clone 与 Copy
python·学习·rust