【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);
}
相关推荐
万物得其道者成几秒前
React Zustand状态管理库的使用
开发语言·javascript·ecmascript
奈斯。zs4 分钟前
yjs08——矩阵、数组的运算
人工智能·python·线性代数·矩阵·numpy
Melody20504 分钟前
tensorflow-dataset 内网下载 指定目录
人工智能·python·tensorflow
学步_技术6 分钟前
Python编码系列—Python抽象工厂模式:构建复杂对象家族的蓝图
开发语言·python·抽象工厂模式
wn53129 分钟前
【Go - 类型断言】
服务器·开发语言·后端·golang
Narutolxy39 分钟前
Python 单元测试:深入理解与实战应用20240919
python·单元测试·log4j
Hello-Mr.Wang41 分钟前
vue3中开发引导页的方法
开发语言·前端·javascript
救救孩子把44 分钟前
Java基础之IO流
java·开发语言
WG_171 小时前
C++多态
开发语言·c++·面试
宇卿.1 小时前
Java键盘输入语句
java·开发语言