深入解析 MapStruct:高效 Java 对象映射工具详解(附Demo)

前言

🤟 找工作,来万码优才: #小程序://万码优才/r6rqmzDaXpYkJZF

基本的Java知识推荐阅读:

  1. java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)
  2. 【Java项目】实战CRUD的功能整理(持续更新)

事因是在实战中看到这种用法,以往我的用法是这样转换,也可参考学习:Java 8 流式 API 将实体类列表转换为视图对象列表(附Demo)

在这里插入图片描述

1. 基本知识

MapStruct 是一个 Java 的代码生成器,专门用于对象映射,可以将一个 Java Bean 自动转换为另一个 Java Bean,而无需手动编写转换逻辑

它的主要特点是基于注解、编译时生成代码、高效无性能损耗,广泛应用于 DTO(数据传输对象)和实体对象之间的转换

作用 说明
对象映射 主要用于 DTO 和实体类的相互转换,减少重复代码
提高开发效率 省去手写转换逻辑,减少维护成本,提高开发效率
编译时生成代码 MapStruct 在编译期生成代码,性能优于运行时反射
可定制转换逻辑 允许手动定义映射规则,比如字段映射、类型转换等
支持 Spring 依赖注入 可以和 Spring 结合,通过 @Mapper(componentModel = “spring”) 让 Spring 自动管理 Mapper

核心类和方法

  1. @Mapper 注解
    用于定义接口,该接口会被 MapStruct 自动生成实现类

  2. Mappers.getMapper(Class<T> mapperInterface)
    用于获取 @Mapper 标注接口的实现类实例

  3. 默认方法(default)
    Java 8 开始支持,允许在接口中提供方法实现
    适用于特殊逻辑处理,而不依赖 MapStruct 自动生成

  4. 其他注解
    @Mapping:用于自定义字段映射关系
    @Mappings:多个 @Mapping 的集合
    @InheritInverseConfiguration:生成反向映射
    @Mapper(componentModel = "spring"):与 Spring 集成

上述接口的意义:

MailAccountConvert INSTANCE = Mappers.getMapper(MailAccountConvert.class);
  • INSTANCE 是 MapStruct 自动生成实现类的单例,便于全局调用
  • 这样做可以避免多次创建实例,提升性能
  • 利用 INSTANCE,用户可以直接调用接口中的方法

事先可以引入相关依赖:

<dependencies>
    <!-- Spring Boot 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- MapStruct 依赖 -->
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.5.5.Final</version>
    </dependency>

    <!-- MapStruct 处理器 (需要让 MapStruct 自动生成转换代码) -->
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>1.5.5.Final</version>
        <scope>provided</scope> <!-- 只在编译时使用,不打包 -->
    </dependency>

    <!-- Lombok (减少 Getter/Setter 代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.26</version>
        <scope>provided</scope>
    </dependency>

    <!-- Lombok MapStruct 兼容插件 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok-mapstruct-binding</artifactId>
        <version>0.2.0</version>
        <scope>provided</scope>
    </dependency>

    <!-- Spring Boot 测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2. Demo

简易Demo 实现简单对象转换:

import lombok.AllArgsConstructor;
import lombok.Data;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Data
@AllArgsConstructor
class Student {
   
    private String username;
    private Integer age;
}

@Data
@AllArgsConstructor
class StudentDTO {
   
    private String username;
    private Integer age;
}

@Mapper
interface StudentMapper {
   
    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
    StudentDTO toStudentDTO(Student student);
}

public class SimpleDemo {
   
    public static void main(String[] args) {
   
        Student student = new Student("码农", 20);
        StudentDTO studentDTO = StudentMapper.INSTANCE.toStudentDTO(student);
        System.out.println(studentDTO);
    }
}

截图如下:

在这里插入图片描述

下面的类和方法都不大一样,总的来说,就是替换去尝试摸索!

类似不一样参数的话:

在这里插入图片描述

2.1 基础用法

成员变量名相同时的映射

import lombok.AllArgsConstructor;
import lombok.Data;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Data
@AllArgsConstructor
class User {
   
    private String username;
    private Integer age;
}

@Data
@AllArgsConstructor
class UserDTO {
   
    private String username;
    private Integer age;
}

@Mapper
interface UserMapper {
   
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    UserDTO toUserDTO(User user);
}

public class Main {
   
    public static void main(String[] args) {
   
        User user = new User("张三", 25);
        UserDTO userDTO = UserMapper.INSTANCE.toUserDTO(user);
        System.out.println(userDTO);
    }
}

输出如下:

UserDTO(username=张三, age=25)

成员变量名不同时的映射

如果字段名称不同,需要使用 @Mapping 注解指定 source 和 target

import org.mapstruct.Mapping;

@Data
@AllArgsConstructor
class Employee {
   
    private String fullName;
    private Integer yearsOld;
}

@Data
@AllArgsConstructor
class EmployeeDTO {
   
    private String name;
    private Integer age;
}

@Mapper
interface EmployeeMapper {
   
    EmployeeMapper INSTANCE = Mappers.getMapper(EmployeeMapper.class);
    @Mapping(source = "fullName", target = "name")
    @Mapping(source = "yearsOld", target = "age")
    EmployeeDTO toEmployeeDTO(Employee employee);
}

2.2 进阶用法

多参数源映射

可以将多个对象的字段合并到一个对象中

@Data
@AllArgsConstructor
class Address {
   
    private String city;
    private String street;
}

@Data
@AllArgsConstructor
class Person {
   
    private String name;
    private Integer age;
    private String city;
    private String street;
}

@Mapper
interface PersonMapper {
   
    PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
    @Mapping(source = "address.city", target = "city")
    @Mapping(source = "address.street", target = "street")
    Person toPerson(String name, Integer age, Address address);
}

更新现有 Bean

使用 @MappingTarget 更新现有对象,而不是创建新的对象

@Mapper
interface UpdateMapper {
   
    UpdateMapper INSTANCE = Mappers.getMapper(UpdateMapper.class);
    @Mapping(target = "username", ignore = true) // 忽略 username 字段
    void updateUser(@MappingTarget User user, UserDTO userDTO);
}

映射集合

MapStruct 允许直接映射 List、Set 等集合

@Mapper
interface ListMapper {
   
    ListMapper INSTANCE = Mappers.getMapper(ListMapper.class);
    List<UserDTO> toUserDTOList(List<User> users);
}

映射枚举
如果需要将枚举类进行转换,可以手动指定映射关系

enum Role {
   
    ADMIN, USER
}

enum RoleDTO {
   
    ROLE_ADMIN, ROLE_USER
}

@Mapper
interface RoleMapper {
   
    RoleMapper INSTANCE = Mappers.getMapper(RoleMapper.class);
    @Mapping(source = "ADMIN", target = "ROLE_ADMIN")
    @Mapping(source = "USER", target = "ROLE_USER")
    RoleDTO toRoleDTO(Role role);
}

2.3 高级特性

依赖注入
可以让 MapStruct 自动被 Spring 识别并注入

@Mapper(componentModel = "spring")
interface InjectedMapper {
   
    UserDTO userToDTO(User user);
}

映射工厂
可以自定义工厂方法创建对象,而不是直接 new 一个新实例

@Mapper
interface CustomFactoryMapper {
   
    CustomFactoryMapper INSTANCE = Mappers.getMapper(CustomFactoryMapper.class);
    default UserDTO userToDTO(User user) {
   
        return new UserDTO(user.getUsername(), user.getAge());
    }
}

3. 总结

MapStruct 是 Java 中非常强大的对象转换工具,适用于 DTO 与实体类的转换,避免手写 getter/setter 代码,提高开发效率

掌握 MapStruct 的基本用法、注解规则,可以在实际开发中大幅简化对象转换逻辑

看完上述内容,可能很多内容都了解了!

那么给个实战中比较复杂的代码(源自ruoyi-vue-pro的代码)

在这里插入图片描述

后续执行调用过程:

在这里插入图片描述
以及

在这里插入图片描述

以下是大杂烩的Demo

import lombok.AllArgsConstructor;
import lombok.Data;
import org.mapstruct.*;
import org.mapstruct.factory.Mappers;

import java.util.List;
import java.util.stream.Collectors;

/** * MapStruct 知识点总结: * 1. 成员变量名相同时的映射(直接映射) * 2. 成员变量名不同时的映射(使用 @Mapping 指定 source 和 target) * 3. 多参数源映射(多个对象组合到一个目标对象) * 4. 多层嵌套映射(嵌套对象字段的映射) * 5. 更新现有 Bean(@MappingTarget 更新现有实例) * 6. 映射器工厂(使用默认方法或手动创建对象) * 7. 依赖注入(使用 @Mapper(componentModel = "spring") 进行 Spring 依赖注入) * 8. 数据类型转换(使用 expression 或默认转换规则) * 9. 映射集合(列表、集合等转换) * 10. 映射枚举(枚举值转换) * * 实战使用位置: * - DTO 与实体类转换,降低耦合度,提高代码可读性。 * - 复杂数据结构转换,减少手写转换逻辑的冗余代码。 * - 依赖 Spring 进行自动管理,提高开发效率。 * - 适用于微服务、Web 开发等场景。 */

@Data
@AllArgsConstructor
class User {
   
    private String username;
    private Integer age;
}

@Data
@AllArgsConstructor
class UserDTO {
   
    private String username;
    private Integer age;
}

@Mapper
interface UserMapper {
   
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    UserDTO toUserDTO(User user);
}

@Data
@AllArgsConstructor
class Employee {
   
    private String fullName;
    private Integer yearsOld;
}

@Data
@AllArgsConstructor
class EmployeeDTO {
   
    private String name;
    private Integer age;
}

@Mapper
interface EmployeeMapper {
   
    EmployeeMapper INSTANCE = Mappers.getMapper(EmployeeMapper.class);
    @Mapping(source = "fullName", target = "name")
    @Mapping(source = "yearsOld", target = "age")
    EmployeeDTO toEmployeeDTO(Employee employee);
}

@Data
@AllArgsConstructor
class Address {
   
    private String city;
    private String street;
}

@Data
@AllArgsConstructor
class Person {
   
    private String name;
    private Integer age;
    private String city;
    private String street;
}

@Mapper
interface PersonMapper {
   
    PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
    @Mapping(source = "address.city", target = "city")
    @Mapping(source = "address.street", target = "street")
    Person toPerson(String name, Integer age, Address address);
}

@Data
@AllArgsConstructor
class Department {
   
    private String departmentName;
}

@Data
@AllArgsConstructor
class Worker {
   
    private String workerName;
    private Integer workerAge;
    private Department department;
}

@Data
@AllArgsConstructor
class WorkerDTO {
   
    private String workerName;
    private Integer workerAge;
    private String departmentName;
}

@Mapper
interface WorkerMapper {
   
    WorkerMapper INSTANCE = Mappers.getMapper(WorkerMapper.class);
    @Mapping(source = "department.departmentName", target = "departmentName")
    WorkerDTO toWorkerDTO(Worker worker);
}

@Mapper
interface UpdateMapper {
   
    UpdateMapper INSTANCE = Mappers.getMapper(UpdateMapper.class);
    @Mapping(target = "username", ignore = true)
    void updateUser(@MappingTarget User user, UserDTO userDTO);
}

@Mapper
interface CustomFactoryMapper {
   
    CustomFactoryMapper INSTANCE = Mappers.getMapper(CustomFactoryMapper.class);
    default UserDTO userToDTO(User user) {
   
        return new UserDTO(user.getUsername(), user.getAge());
    }
}

@Mapper(componentModel = "spring")
interface InjectedMapper {
   
    UserDTO userToDTO(User user);
}

@Data
@AllArgsConstructor
class Order {
   
    private String orderId;
    private Long orderAmount;
}

@Data
@AllArgsConstructor
class OrderDTO {
   
    private String orderId;
    private String orderAmount;
}

@Mapper
interface OrderMapper {
   
    OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);
    @Mapping(target = "orderAmount", expression = "java(String.valueOf(order.getOrderAmount()))")
    OrderDTO toOrderDTO(Order order);
}

@Mapper
interface ListMapper {
   
    ListMapper INSTANCE = Mappers.getMapper(ListMapper.class);
    List<UserDTO> toUserDTOList(List<User> users);
}

enum Role {
   
    ADMIN, USER
}

enum RoleDTO {
   
    ROLE_ADMIN, ROLE_USER
}

@Mapper
interface RoleMapper {
   
    RoleMapper INSTANCE = Mappers.getMapper(RoleMapper.class);
    @Mapping(source = "ADMIN", target = "ROLE_ADMIN")
    @Mapping(source = "USER", target = "ROLE_USER")
    RoleDTO toRoleDTO(Role role);
}

public class MapStructDemo {
   
    public static void main(String[] args) {
   
        User user = new User("张三", 25);
        UserDTO userDTO = UserMapper.INSTANCE.toUserDTO(user);
        System.out.println("1. " + userDTO);

        Employee employee = new Employee("李四", 30);
        EmployeeDTO employeeDTO = EmployeeMapper.INSTANCE.toEmployeeDTO(employee);
        System.out.println("2. " + employeeDTO);

        Address address = new Address("北京", "长安街");
        Person person = PersonMapper.INSTANCE.toPerson("王五", 40, address);
        System.out.println("3. " + person);

        Department department = new Department("IT部");
        Worker worker = new Worker("赵六", 35, department);
        WorkerDTO workerDTO = WorkerMapper.INSTANCE.toWorkerDTO(worker);
        System.out.println("4. " + workerDTO);

        UserDTO newUserDTO = new UserDTO("新名字", 28);
        UpdateMapper.INSTANCE.updateUser(user, newUserDTO);
        System.out.println("5. " + user);

        Order order = new Order("O12345", 9999L);
        OrderDTO orderDTO = OrderMapper.INSTANCE.toOrderDTO(order);
        System.out.println("6. " + orderDTO);

        List<User> users = List.of(new User("甲", 22), new User("乙", 23));
        List<UserDTO> userDTOList = ListMapper.INSTANCE.toUserDTOList(users);
        System.out.println("7. " + userDTOList);

        RoleDTO roleDTO = RoleMapper.INSTANCE.toRoleDTO(Role.ADMIN);
        System.out.println("8. " + roleDTO);
    }
}

4. 拓展

实战使用过程中,需要使用到在线表最后进入历史表中

@Mapper
public interface CabinetSwapConverter {
   

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

    @Mapping(target = "id", ignore = true) // 忽略 id 字段
    @Mapping(target = "checkStatus", constant = "2L") // 强制设定 checkStatus 为 2
    @Mapping(source = "createTime", target = "createTime") // 复制创建时间
    @Mapping(source = "updateTime", target = "updateTime") // 复制更新时间
    CabinetSwapDetailDO toDetailDO(CabinetSwapDO cabinetSwapDO);
}

在这里插入图片描述

后续调用过程中:

在这里插入图片描述

原文链接:深入解析 MapStruct:高效 Java 对象映射工具详解(附Demo)

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容