SpringBoot 整合 MapStruct——告别 BeanUtils,实体转换用这个

Java 项目里最烦的重复劳动之一就是实体转换------PO、DO、DTO、VO 之间互相转,字段多的类写几十行 get/set。BeanUtils 虽然简单但性能差、容易出 Bug。MapStruct 是编译期生成转换代码,零反射、高性能。

一、为什么不用 BeanUtils

java 复制代码
// BeanUtils 的问题:
// 1. 性能差(反射,大量转换时慢)
// 2. 字段名不一致就静默失败
// 3. 类型不一致报错诡异
// 4. 没有编译期检查

User user = new User("张三", 25, "13912345678");
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);  // 如果字段名对不上,你都不知道

二、MapStruct 入门

1. 引入依赖

xml 复制代码
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.5.Final</version>
</dependency>
xml 复制代码
<!-- Maven 编译插件 -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <source>17</source>
        <target>17</target>
        <annotationProcessorPaths>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.5.5.Final</version>
            </path>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.28</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

关键: mapstruct-processor 和 lombok 要一起配在 annotationProcessorPaths 里,否则 Lombok 和 MapStruct 会打架。

2. 最简单的转换

java 复制代码
// Entity
@Data
public class User {
    private Long id;
    private String username;
    private String password;
    private String email;
    private String phone;
    private LocalDateTime createTime;
}

// VO(对外展示,不暴露密码)
@Data
public class UserVO {
    private Long id;
    private String username;
    private String email;
    private String phone;
    private LocalDateTime createTime;
}

Mapper 接口------这是 MapStruct 的精髓:

java 复制代码
@Mapper(componentModel = "spring")
public interface UserMapper {

    // 实例(Spring 环境下注入使用)
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    // VO 转 Entity(同名同类型字段自动映射)
    UserVO toVO(User user);

    // Entity 转 VO(List 批量转换)
    List<UserVO> toVOList(List<User> users);
}

使用:

java 复制代码
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public UserVO getUser(Long id) {
        User user = userMapper.selectById(id);
        // 一行转换,比 BeanUtils 快 10 倍
        return userMapper.toVO(user);
    }

    public List<UserVO> getUserList() {
        List<User> users = userMapper.selectList(null);
        return userMapper.toVOList(users);
    }
}

三、字段名不一致

java 复制代码
// Entity
@Data
public class Product {
    private Long id;
    private String productName;   // 数据库字段:product_name
    private BigDecimal productPrice;
}

// DTO
@Data
public class ProductDTO {
    private Long id;
    private String name;          // 字段名不一样
    private BigDecimal price;     // 字段名不一样
}
java 复制代码
@Mapper(componentModel = "spring")
public interface ProductMapper {

    // 用 @Mapping 指定字段映射
    @Mapping(source = "productName", target = "name")
    @Mapping(source = "productPrice", target = "price")
    ProductDTO toDTO(Product product);

    // 批量转换
    List<ProductDTO> toDTOList(List<Product> products);
}

四、类型不一致

java 复制代码
// Entity
@Data
public class Order {
    private Long id;
    private LocalDateTime orderTime;  // 数据库存的是 LocalDateTime
}

// DTO
@Data
public class OrderDTO {
    private Long id;
    private String orderTime;         // 前端要的是字符串 "2026-06-30 14:30:00"
}
java 复制代码
@Mapper(componentModel = "spring", imports = {LocalDateTimeUtil.class})
public interface OrderMapper {

    @Mapping(target = "orderTime", expression = "java(formatTime(order.getOrderTime()))")
    OrderDTO toDTO(Order order);

    default String formatTime(LocalDateTime time) {
        if (time == null) return "";
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return time.format(fmt);
    }
}

五、忽略字段

java 复制代码
@Mapper(componentModel = "spring")
public interface UserMapper {

    @Mapping(target = "password", ignore = true)  // 密码不暴露
    @Mapping(target = "createTime", ignore = true)
    UserVO toVO(User user);
}

六、更新已有对象

java 复制代码
@Mapper(componentModel = "spring")
public interface UserMapper {

    // 把 DTO 的字段合并到 Entity 中(更新已存在的对象)
    @Mapping(target = "id", ignore = true)
    @Mapping(target = "password", ignore = true)
    void updateEntity(UserDTO dto, @MappingTarget User user);
}
java 复制代码
// 使用:前端传了修改后的 DTO,合并到已有 Entity
User user = userService.getById(1L);
userMapper.updateEntity(dto, user);  // dto 的非空字段合并到 user
userService.updateById(user);

七、多个对象合并

java 复制代码
@Mapper(componentModel = "spring")
public interface OrderDetailMapper {

    // 把 Order 和 User 合并成 OrderDetailVO
    @Mapping(source = "order.id", target = "orderId")
    @Mapping(source = "order.orderNo", target = "orderNo")
    @Mapping(source = "user.username", target = "userName")
    @Mapping(source = "user.phone", target = "userPhone")
    OrderDetailVO toOrderDetail(Order order, User user);
}

八、自定义类型转换

java 复制代码
@Mapper(componentModel = "spring")
public interface ProductMapper {

    // 自定义转换方法
    default String bigDecimalToString(BigDecimal value) {
        if (value == null) return "0.00";
        return value.setScale(2, RoundingMode.HALF_UP).toString();
    }

    // MapStruct 会自动调用上面的方法
    @Mapping(target = "price", source = "productPrice")
    ProductDTO toDTO(Product product);
}

九、性能对比

工具 原理 性能 编译期检查
手写 get/set 硬编码 ⭐⭐⭐⭐⭐ 最快
MapStruct 编译期生成代码 ⭐⭐⭐⭐⭐
BeanUtils 运行时反射 ⭐⭐ 慢
Spring BeanUtils 运行时反射 ⭐⭐ 慢
Orika 运行时字节码 ⭐⭐⭐ 中等

MapStruct 在编译期生成转换代码,跟手写 get/set 性能几乎一样,但代码量减少 90%。

十、配合 Lombok 的注意事项

java 复制代码
// ❌ 常见错误:Lombok 和 MapStruct 一起用时报错
// 原因:annotationProcessorPaths 里没有同时配 lombok 和 mapstruct-processor

// ✅ 正确配置 pom.xml 的 compiler 插件(看上面第二部分)

生成代码的位置: 编译后在 target/generated-sources/annotations/ 下可以找到 MapStruct 生成的实现类,感兴趣可以打开看看。

总结

MapStruct 的核心优势就一句话:写一个接口,编译期自动生成转换实现,零反射、高性能、编译期检查。

复制代码
简单转换 → 写接口,字段名一致不用加注解
字段不同 → @Mapping(source = ..., target = ...)
忽略字段 → @Mapping(target = ..., ignore = true)
类型转换 → 自定义 default 方法
批量转换 → 定义 List<> 方法

💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。