下面通过一个具体的业务场景,演示 MapStruct 在 Spring Boot 项目中的完整用法。我们将实现 AuditInfoBO(业务对象)到 AuditInfoDTO(数据传输对象)的转换,包括字段映射、嵌套对象、集合处理等常见情况。
场景说明
假设我们有一个审计日志模块,BO 包含了审计记录的基本信息、操作人信息和操作项列表,DTO 需要将这些数据返回给前端,同时做一些字段调整(如字段重命名、状态码转描述)。
1. 定义 BO 和 DTO 类
BO 类(业务对象)
java
// AuditInfoBO.java
import java.time.LocalDateTime;
import java.util.List;
public class AuditInfoBO {
private Long id;
private String action; // 操作动作
private Integer status; // 状态码:0-成功,1-失败
private LocalDateTime createTime; // 创建时间
private UserBO operator; // 操作人(嵌套)
private List<AuditItemBO> items; // 操作项列表
// 构造器、getter/setter 省略(可用 Lombok)
}
java
// UserBO.java
public class UserBO {
private Long userId;
private String userName;
private String email;
// getter/setter
}
java
// AuditItemBO.java
public class AuditItemBO {
private String itemId;
private String itemName;
private Integer quantity;
// getter/setter
}
DTO 类(数据传输对象)
java
// AuditInfoDTO.java
import java.time.LocalDate;
import java.util.List;
public class AuditInfoDTO {
private Long id;
private String action;
private String statusDesc; // 状态描述(由 status 转换而来)
private LocalDate createDate; // 仅日期(原 createTime 是 LocalDateTime)
private UserDTO operator; // 操作人(嵌套)
private List<AuditItemDTO> items; // 操作项列表
// getter/setter
}
java
// UserDTO.java
public class UserDTO {
private Long id; // 字段名与 BO 不同(原 userId -> id)
private String name; // 字段名不同(原 userName -> name)
private String email;
// getter/setter
}
java
// AuditItemDTO.java
public class AuditItemDTO {
private String itemId;
private String itemName;
private Integer quantity;
// getter/setter
}
2. 创建 MapStruct Mapper 接口
我们将使用 @Mapper 注解,并通过 componentModel = "spring" 将其注册为 Spring Bean。
java
// AuditInfoMapper.java
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
@Mapper(componentModel = "spring") // 生成 Spring Bean
public interface AuditInfoMapper {
// 基础映射:同名属性会自动映射(如 id -> id, action -> action)
@Mapping(source = "createTime", target = "createDate", dateFormat = "yyyy-MM-dd")
@Mapping(source = "status", target = "statusDesc", qualifiedByName = "statusToDesc")
@Mapping(source = "operator", target = "operator")
@Mapping(source = "items", target = "items")
AuditInfoDTO toDto(AuditInfoBO bo);
// 处理嵌套对象 UserBO -> UserDTO(单独定义映射方法,MapStruct 会自动调用)
@Mapping(source = "userId", target = "id")
@Mapping(source = "userName", target = "name")
UserDTO toUserDto(UserBO userBO);
// 处理嵌套集合中的每个元素(自动调用下面的方法)
AuditItemDTO toItemDto(AuditItemBO itemBO);
// 自定义方法:将状态码转换为描述
@Named("statusToDesc")
default String statusToDesc(Integer status) {
if (status == null) return "未知";
switch (status) {
case 0: return "成功";
case 1: return "失败";
default: return "其他";
}
}
}
说明:
-
@Mapping(source = "createTime", target = "createDate", dateFormat = "yyyy-MM-dd"):将 LocalDateTime 格式化为指定格式的字符串(如果目标类型是 String),但这里目标是 LocalDate,MapStruct 会自动截取日期部分,dateFormat 用于明确转换格式。
-
@Mapping(source = "status", target = "statusDesc", qualifiedByName = "statusToDesc"):通过自定义方法 statusToDesc 将状态码转换为描述。
-
嵌套对象 operator 和集合 items 的映射:MapStruct 会自动查找类型匹配的映射方法(toUserDto 和 toItemDto),无需额外注解。
-
如果字段名完全一致,连 @Mapping 都不需要,直接自动映射。
3. 在 Service 中使用 Mapper
java
// AuditService.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AuditService {
@Autowired
private AuditMapper mybatisMapper; // MyBatis 的 Mapper(数据库操作)
@Autowired
private AuditInfoMapper mapstructMapper; // MapStruct 的 Mapper(对象转换)
public AuditInfoDTO getAuditInfo(Long id) {
// 1. 从数据库查询 BO(由 MyBatis 直接映射为 BO 或 Entity)
AuditInfoBO bo = mybatisMapper.selectAuditInfoBO(id);
// 2. 使用 MapStruct 转换为 DTO
AuditInfoDTO dto = mapstructMapper.toDto(bo);
return dto;
}
public List<AuditInfoDTO> getAuditList() {
List<AuditInfoBO> boList = mybatisMapper.selectAll();
// 3. 集合映射(MapStruct 会逐个转换)
return mapstructMapper.toDtoList(boList); // 需要额外定义 toDtoList 方法
}
}
为了让集合映射更简洁,可以在 Mapper 接口中增加默认方法或直接定义:
java
@Mapper(componentModel = "spring")
public interface AuditInfoMapper {
// … 已有的映射方法
List<AuditInfoDTO> toDtoList(List<AuditInfoBO> boList);
}
MapStruct 会自动生成实现,遍历集合并调用 toDto 方法。
4. 编译后生成的实现类(供理解)
执行 mvn clean compile 后,MapStruct 会在 target/generated-sources/annotations 下生成一个实现类,大致内容如下:
java
@Component
public class AuditInfoMapperImpl implements AuditInfoMapper {
@Override
public AuditInfoDTO toDto(AuditInfoBO bo) {
if (bo == null) return null;
AuditInfoDTO dto = new AuditInfoDTO();
dto.setId(bo.getId());
dto.setAction(bo.getAction());
// 处理自定义映射
dto.setCreateDate(bo.getCreateTime().toLocalDate()); // 根据 dateFormat 处理
dto.setStatusDesc(statusToDesc(bo.getStatus()));
dto.setOperator(toUserDto(bo.getOperator()));
dto.setItems(toItemDtoList(bo.getItems()));
return dto;
}
@Override
public UserDTO toUserDto(UserBO userBO) {
if (userBO == null) return null;
UserDTO dto = new UserDTO();
dto.setId(userBO.getUserId());
dto.setName(userBO.getUserName());
dto.setEmail(userBO.getEmail());
return dto;
}
@Override
public AuditItemDTO toItemDto(AuditItemBO itemBO) {
// 同名属性直接映射
// …
}
@Override
public List<AuditInfoDTO> toDtoList(List<AuditInfoBO> boList) {
// 循环调用 toDto
}
}
5. 与 BeanUtils.copyProperties 对比
| 类型安全 | 编译期检查字段类型、名称 | 运行时可能抛出异常 |
| 性能 | 直接调用 getter/setter(无反射) | 反射调用,性能较低 |
| 嵌套对象映射 | 自动递归映射(需定义嵌套映射方法) | 浅拷贝,只复制引用 |
| 集合映射 | 自动生成循环代码 | 需手动遍历 |
| 自定义转换 | 表达式、默认方法、自定义类型转换器 | 无法直接实现,需转换前/后处理 |
| 字段名称不一致 | @Mapping 明确指定 | 无法自动处理,必须手动 set |
| 代码可读性 | 映射规则集中在一个接口,清晰 | 分散在业务代码中,难以追踪 |
6. 注意事项
-
与 MyBatis Mapper 同名问题:将 MapStruct 的 Mapper 放在不同包(如 converter),命名用 XxxConverter 或 XxxStructMapper,避免混淆。
-
Lombok 配合:如果 BO/DTO 使用了 Lombok,确保 pom.xml 中正确配置了 lombok-mapstruct-binding,否则可能出现属性找不到的错误。
-
复杂映射:若需更复杂的类型转换(如 String 转 Enum),可定义自定义类型转换器(@Mapper 的 uses 属性)。
总结
通过这个例子可以看出,MapStruct 将对象转换逻辑从业务代码中抽离,以声明式接口定义映射规则,既清晰又高效。在 Spring Boot 项目中,搭配 componentModel = "spring" 可以无缝集成,是替代 BeanUtils.copyProperties 的理想选择。



