BeanUtils.copyProperties 写起来最省事,代价藏得很深:字段名悄悄拼错时没有编译错误,类型不一致要运行时才发现,性能 also
依赖反射。项目改用 MapStruct,把 Entity 和 DTO 的转换提前到编译期。
一、转换器长什么样
@Mapper(componentModel = "spring")
public interface UserConverter {
UserDTO entityToDto(User user);
@Mapping(target = "isDeleted", ignore = true)
@Mapping(target = "createTime", ignore = true)
@Mapping(target = "updateTime", ignore = true)
User dtoToEntity(UserDTO dto);
}
componentModel = "spring" 让生成实现类注册成 Bean,Controller 直接注入使用。
二、显式忽略数据库托管字段
DTO 转 Entity 时,isDeleted、createTime、updateTime 被忽略。它们属于数据库和公共填充逻辑管理的字段,不应该由调用方随便传入。
这个忽略动作同时是文档:调用方能设置哪些字段,转换器一眼可见。
三、编译期发现字段问题
MapStruct 在编译时生成实现类。字段名不匹配、类型不能转换、目标字段没有来源,都会在构建阶段暴露。相比运行时反射拷贝,这是把风险往前挪了一大步。
四、列表转换不用手写循环
List<UserDTO> entityListToDtoList(List<User> users);
MapStruct 会基于单对象转换方法生成集合转换,减少模板代码。
五、经验总结
DTO 和 Entity 分离后,转换代码会变多,这是契约清晰的成本。MapStruct 把这部分成本控制在编译期,属于划算的交换。