Java中MapStruct对象映射的实现

 更新时间:2024年12月06日 09:46:17   作者:沙坪坝、黄师傅  
MapStruct是一种Java实体类映射框架,本文就来介绍一下Java中MapStruct对象映射的实现,具有一定的参考价值,感兴趣的可以了解一下

简介

MapStruct是一种实体类映射框架,能够通过Java注解将一个实体类的属性安全地赋值给另一个实体类。有了mapstruct,只需要定义一个映射器接口,声明需要映射的方法,在编译过程中,mapstruct会自动生成该接口的实现类,实现将源对象映射到目标对象的效果。

MapStruct是基于JSR 269实现的,JSR 269是JDK引进的一种规范。有了它,能够实现在编译期处理注解,并且读取、修改和添加抽象语法树中的内容。JSR 269使用Annotation Processor在编译期间处理注解,Annotation Processor相当于编译器的一种插件,因此又称为插入式注解处理。官网通道 | Github

优点

  • 安全性高‌:由于映射是在编译期间实现的,如果编译器能够通过,运行期就不会报错。

  • 高性能:编译时生成bean映射的实现类,通过使⽤普通⽅法(getter/setter)调⽤⽽不是反射来快速执⾏。

缺点

  • 使用复杂度:对于更复杂的映射,需要开发人员编写自定义映射接口和函数。

同类对比

映射工具

实现机制

性能对比

备注

Dozer

反射机制

使用递归将数据从一个对象复制到另一个对象

Orika

反射机制

同Dozer,不过Orika 使用字节码生成

ModelMapper

反射机制

简单易用,它根据约定确定对象之间的映射方式

JMapper

编译生成

基于Javassist 的Java映射框架

MapStruct

编译生成

在编译时生成bean映射,以确保高性能、彻底的错误检查

快速入门

Maven依赖

<!-- 定义版本 -->
  <properties>
      <org.mapstruct.version>1.6.0</org.mapstruct.version>
      <org.projectlombok.mapstruct.version>0.2.0</org.projectlombok.mapstruct.version>
  </properties>

<!-- 依赖包 -->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>${org.mapstruct.version}</version>
</dependency>

  <!--MapStruct会用到对象中的get、set方法,但get、set方法又需要lombok来生成。因此需要控制这两者工作顺序-->
 <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok-mapstruct-binding</artifactId>
      <version>${org.projectlombok.mapstruct.version}</version>
 </dependency>

编译插件

<!-- 方式一-->
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>${org.mapstruct.version}</version>
    <scope>provided</scope>
</dependency>
  
<!-- 方式二-->
  <build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>17</source>
                <target>17</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

Model定义

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserDTO implements Serializable {
    private Integer id;
    private String userName;
    private String password;
    private Integer age;
    private String address;
    private String email;
    private List<UserRole> roles;
}

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserVO implements Serializable {
    private Integer id;
    private String name;
    private String pwd;
    private Integer age;
    private String email;
    private List<UserRole> roles;
}

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserRole implements Serializable {
    private Integer roleId;
    private String roleName;
    private String remark;
}

Mapper定义

//@Mapper(componentModel = "spring")
@Mapper
public interface MapStructMapper {

    MapStructMapper INSTANCE = Mappers.getMapper(MapStructMapper.class);

    /**
     * 单个对象转换
     * @param userDTO
     * @return
     */
    UserVO userDtoToVO(UserDTO userDTO);

    /**
     * 集合对象转换
     * @param userDTO
     * @return
     */
    List<UserVO> userDtoToVOList(List<UserDTO> userDTO);

}

重要:SpringBoot项目可以使用@Mapper(componentModel = "spring")的方式将bean交给spring容器进行管理,因此可以不用写MapStructMapper INSTANCE = Mappers.getMapper(MapStructMapper.class);

转换调用

    public void mapStructConvertTest(){
        // 初始化用户
        UserDTO userDTO = this.instanceUser();
        UserVO userVO = MapStructMapper.INSTANCE.userDtoToVO(userDTO);
        log.info("userVO:{}", JSON.toJSONString(userVO));
    }

场景示例

常规映射

@Mapper
public interface MapStructMapper {

    MapStructMapper INSTANCE = Mappers.getMapper(MapStructMapper.class);

    UserVO userDtoToVO(UserDTO userDTO);

}

集合映射

@Mapper
public interface MapStructMapper {

    MapStructMapper INSTANCE = Mappers.getMapper(MapStructMapper.class);

    List<UserVO> userDtoToVOList(List<UserDTO> userDTO);

}

重要:集合转换时,必须先有单个对象的转换函数。并且Mapper中只能有一个同类的原对象与目标对象的转换,否则集合转换不知道取哪一个单对象转换函数。

单字段映射

@Mapping(target = "pwd", source = "password")
UserVO userDtoToVO(UserDTO userDTO);

多字段映射

@Mappings({
  @Mapping(target = "pwd", source = "password"),
  @Mapping(target = "name", source = "userName"),
})
UserVO userDtoToVOMoreField(UserDTO userDTO);

忽略字段

@Mapping(target = "email", ignore = true)
UserVO userDtoToVOIgnoreField(UserDTO userDTO);

常量值映射

@Mapping(target = "constant", constant = "OK")
UserVO newUserWithConstant(UserDTO userDTO);

默认值映射

@Mapping(source = "email", target = "email", defaultValue = "默认值")
UserVO userDtoToVONullDefaultValue(UserDTO userDTO);

表达式映射

@Mapping(target = "fullName", expression = "java(userDTO.getUserName() + ' ' + userDTO.getAddress())")
UserVO userDtoToVOExpression(UserDTO userDTO);
@Mapping(target = "email", 
expression = "java(!userDTO.getEmail().isEmpty()? \"不为空\" : \"为空\")")
UserVO userDtoToVOWithCondition(UserDTO userDTO);

执行函数

@Mapping(target = "email", source = "email", qualifiedByName = "toUpperCase")
UserVO emailToUpperCase(UserDTO userDTO);

@Named("toUpperCase")
default String toUpperCase(String value) {
    // 转换大写
    return value == null ? null : value.toUpperCase();
}

深拷贝

@Mapper(componentModel = "spring",mappingControl = DeepClone.class)
public interface MapStructMapper {
  
}

说明:mappingControl = DeepClone.class 是指定深拷贝模式,不指定则默认浅拷贝,浅拷贝时集合类是底层是调用Array 的copy 方法。如果是深拷贝模式,MapStruct框架会生成集合遍历代码,集合中元素如果是引用类型会生成引用类型转换代码,层层转换,深度拷贝。集合类拷贝的限制比较多,不支持多层嵌套集合类深拷贝,而且要求源字段和目标字段集合类型严格一致。

浅拷贝 :只复制对象的引用,而不会复制对象本身的内容。如果更改了原始对象的一个地址,DTO中的地址也会跟着改变,因为它们指向的是同一个对象。

深拷贝:会递归地复制对象的所有内容,包括嵌套的对象。即使你更改了原始对象中的数据,DTO中的数据也不会受到影响。

逆向映射

    /**
     * 单个对象映射
     * @param userDTO
     * @return
     */

    UserVO userDtoToVO(UserDTO userDTO);

    /**
     * 逆向映射
     * @param userVO  源 VO 对象
     * @return 目标 DTO 对象
     */
    @InheritInverseConfiguration
    UserDTO userVOToDto(UserVO userVO);

说明:正向函数有业务逻辑处理或属性类型不匹配的不能逆向映射

映射后执行动作

 @AfterMapping
default void afterMapping(UserDTO userDTO, @MappingTarget UserVO userVO) {
    // Add custom post-mapping logic here
    userVO.setId(10000);
    System.out.println("afterMapping:id设置为:"+userVO.getId());
}

延伸内容

MapStruct  Plus 是 MapStruct 的增强工具,在 MapStruct 的基础上,实现了自动生成 Mapper 接口的功能,并强化了部分功能,使 Java 类型转换更加便捷、优雅。官网通道 | GitHub

Maven依赖

<properties>
    <mapstruct-plus.version>1.4.5</mapstruct-plus.version>
</properties>
  
<dependency>
        <groupId>io.github.linpeilie</groupId>
        <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
        <version>${mapstruct-plus.version}</version>
    </dependency>

新增配置类

@ComponentModelConfig(componentModel = "default")
public class MapperConfiguration {
}

对象映射

@AutoMapper(target = UserDto.class)
@Data
public class User {
    // ...
}

转换Map

@AutoMapMapper
@Data
public class MapModelB {

    private Date date;

}

一个类转换为多个类

@Data
@AutoMappers({
    @AutoMapper(target = UserDto.class),
    @AutoMapper(target = UserVO.class)
})
public class User {
    // fields
}

循环嵌套

@Data
@AutoMapper(target = TreeNodeDto.class, cycleAvoiding = true)
public class TreeNode {
    private TreeNode parent;
    private List<TreeNode> children;
}

@Data
@AutoMapper(target = TreeNode.class, cycleAvoiding = true)
public class TreeNodeDto {
    private TreeNodeDto parent;
    private List<TreeNodeDto> children;
}

到此这篇关于Java中MapStruct对象映射的实现的文章就介绍到这了,更多相关MapStruct对象映射内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中for循环的执行过程分析

    Java中for循环的执行过程分析

    这篇文章主要介绍了Java中for循环的执行过程,实例分析了for循环的执行原理与顺序,对于深入理解Java具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • 如何设置Spring Boot测试时的日志级别

    如何设置Spring Boot测试时的日志级别

    Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。下面我们来一起学习一下吧
    2019-06-06
  • java在pdf中生成表格的方法

    java在pdf中生成表格的方法

    这篇文章主要介绍了java在pdf中生成表格的方法,需要的朋友可以参考下
    2015-11-11
  • Java 中不全部使用 Static 方法的理由

    Java 中不全部使用 Static 方法的理由

    这篇文章主要介绍了Java 中不全部使用 Static 方法的理由,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • Java Socket实现的传输对象功能示例

    Java Socket实现的传输对象功能示例

    这篇文章主要介绍了Java Socket实现的传输对象功能,结合具体实例形式分析了java socket传输对象的原理及接口、客户端、服务器端相关实现技巧,需要的朋友可以参考下
    2017-06-06
  • Java基本数据类型和运算符详解

    Java基本数据类型和运算符详解

    这篇文章主要介绍了Java基本数据类型和运算符,结合实例形式详细分析了java基本数据类型、数据类型转换、算术运算符、逻辑运算符等相关原理与操作技巧,需要的朋友可以参考下
    2020-02-02
  • Mybatis注解方式@Insert的用法

    Mybatis注解方式@Insert的用法

    这篇文章主要介绍了Mybatis注解方式@Insert的用法说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • 深入了解Spring中最常用的11个扩展点

    深入了解Spring中最常用的11个扩展点

    我们一说到spring,可能第一个想到的是 IOC(控制反转) 和 AOP(面向切面编程)。除此之外,我们在使用spring的过程中,有没有发现它的扩展能力非常强。今天就来跟大家一起聊聊,在Spring中最常用的11个扩展点
    2022-09-09
  • 教你怎么用java实现客户端与服务器一问一答

    教你怎么用java实现客户端与服务器一问一答

    这篇文章主要介绍了教你怎么用java实现客户端与服务器一问一答,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • apollo更改配置刷新@ConfigurationProperties配置类

    apollo更改配置刷新@ConfigurationProperties配置类

    这篇文章主要为大家介绍了apollo更改配置刷新@ConfigurationProperties配置类示例解析,apollo更改配置刷新@ConfigurationProperties配置类
    2023-04-04

最新评论