在项目开发中,对象之间的相互转换是一个高频操作。尤其在分层架构的系统中,数据在实体对象(Entity)、数据传输对象(DTO)、值对象(VO)之间的转换尤为常见。
选择一个高效、可靠的对象映射方案对提高系统性能和开发效率至关重要。
本文将介绍4种对象映射方案。
方案一:手动映射
手动映射是最基础也是最直接的方法,通过显式编写代码将一个对象的属性复制到另一个对象。
实现方式
首先定义两个示例类:
less
// 用户实体类
@Entity
@Table(name = "users")
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String email;
private String phoneNumber;
private LocalDateTime createTime;
private LocalDateTime updateTime;
// getter和setter省略
}
// 用户DTO类
public class UserDTO {
private Long id;
private String username;
private String email;
private String phone; // 注意命名差异
private LocalDateTime registerTime; // 映射自createTime
// getter和setter省略
}
手动映射实现:
scss
public class UserMapper {
public static UserDTO toDTO(UserEntity entity) {
if (entity == null) {
return null;
}
UserDTO dto = new UserDTO();
dto.setId(entity.getId());
dto.setUsername(entity.getUsername());
dto.setEmail(entity.getEmail());
dto.setPhone(entity.getPhoneNumber()); // 不同名称字段映射
dto.setRegisterTime(entity.getCreateTime()); // 语义转换
return dto;
}
public static UserEntity toEntity(UserDTO dto) {
if (dto == null) {
return null;
}
UserEntity entity = new UserEntity();
entity.setId(dto.getId());
entity.setUsername(dto.getUsername());
entity.setEmail(dto.getEmail());
entity.setPhoneNumber(dto.getPhone()); // 不同名称字段映射
entity.setCreateTime(dto.getRegisterTime()); // 语义转换
return entity;
}
// 集合转换方法
public static List<UserDTO> toDTOList(List<UserEntity> entities) {
if (entities == null) {
return Collections.emptyList();
}
return entities.stream()
.map(UserMapper::toDTO)
.collect(Collectors.toList());
}
}
优缺点分析
优点:
- 完全掌控映射逻辑,灵活性最高
- 不依赖第三方库,无学习成本
- 映射逻辑清晰直观,易于调试
- 性能最优,无反射开销
缺点:
- 代码冗长,大量重复劳动
- 属性变更需要手动同步修改转换代码
- 随着对象数量增加,维护成本剧增
- 容易出现人为错误,如遗漏字段
方案二:MapStruct
MapStruct是一个代码生成器,它基于约定优于配置的方法,在编译时生成类型安全的对象映射代码。
实现方式
1. 添加依赖
xml
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.3.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.3.Final</version>
<scope>provided</scope>
</dependency>
2. 定义映射接口
less
@Mapper(componentModel = "spring")
public interface UserMapStruct {
@Mapping(source = "phoneNumber", target = "phone")
@Mapping(source = "createTime", target = "registerTime")
UserDTO toDTO(UserEntity entity);
@Mapping(source = "phone", target = "phoneNumber")
@Mapping(source = "registerTime", target = "createTime")
UserEntity toEntity(UserDTO dto);
List<UserDTO> toDTOList(List<UserEntity> entities);
// 默认值处理
@Mapping(target = "email", defaultValue = "[email protected]")
UserDTO toDTOWithDefaultEmail(UserEntity entity);
// 自定义方法处理复杂转换
default String formatPhoneNumber(String phoneNumber) {
if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
return null;
}
// 格式化电话号码逻辑
return phoneNumber.replaceAll("(\d{3})(\d{4})(\d{4})", "$1-$2-$3");
}
}
3. 使用方式
kotlin
@Service
public class UserService {
private final UserMapStruct userMapper;
@Autowired
public UserService(UserMapStruct userMapper) {
this.userMapper = userMapper;
}
public UserDTO getUserDTO(Long id) {
UserEntity entity = userRepository.findById(id).orElseThrow();
return userMapper.toDTO(entity);
}
public List<UserDTO> getAllUserDTOs() {
List<UserEntity> entities = userRepository.findAll();
return userMapper.toDTOList(entities);
}
}
优缺点分析
优点:
- 编译时生成代码,运行时性能极高
- 编译时类型安全检查,错误提前发现
- 代码简洁,开发效率高
- 支持复杂映射和自定义转换逻辑
- 简化集合映射,无需手动循环
缺点:
- 需要额外依赖和配置
- 调试相对复杂(需查看生成的代码)
方案三:ModelMapper
ModelMapper是一个灵活、强大的对象映射库,通过反射机制实现自动映射。
实现方式
1. 添加依赖
xml
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.1.1</version>
</dependency>
2. 配置ModelMapper
scss
@Configuration
public class ModelMapperConfig {
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
// 配置映射策略
modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT)
.setPropertyCondition(Conditions.isNotNull())
.setFieldAccessLevel(Configuration.AccessLevel.PRIVATE)
.setFieldMatchingEnabled(true);
// 自定义字段映射
PropertyMap<UserEntity, UserDTO> userMap = new PropertyMap<UserEntity, UserDTO>() {
@Override
protected void configure() {
map().setPhone(source.getPhoneNumber());
map().setRegisterTime(source.getCreateTime());
}
};
modelMapper.addMappings(userMap);
return modelMapper;
}
}
3. 使用方式
kotlin
@Service
public class UserService {
private final ModelMapper modelMapper;
private final UserRepository userRepository;
@Autowired
public UserService(ModelMapper modelMapper, UserRepository userRepository) {
this.modelMapper = modelMapper;
this.userRepository = userRepository;
}
public UserDTO getUserDTO(Long id) {
UserEntity entity = userRepository.findById(id).orElseThrow();
return modelMapper.map(entity, UserDTO.class);
}
public List<UserDTO> getAllUserDTOs() {
List<UserEntity> entities = userRepository.findAll();
return entities.stream()
.map(entity -> modelMapper.map(entity, UserDTO.class))
.collect(Collectors.toList());
}
public UserEntity createUser(UserDTO dto) {
UserEntity entity = modelMapper.map(dto, UserEntity.class);
return userRepository.save(entity);
}
}
优缺点分析
优点:
- 使用简单,API友好
- 配置灵活,支持多种映射策略
- 运行时动态映射,无需预定义
- 支持深层映射和复杂对象图
- 类型转换内置支持
缺点:
- 基于反射,性能要求极高的场景可能不适合
- 运行时类型不安全,可能产生运行时异常
- 映射逻辑不直观,调试困难
- 复杂映射需要额外配置
方案四:Spring BeanUtils和BeanCopier
Spring框架提供了多种Bean属性复制工具,其中BeanUtils是基于反射的,而BeanCopier是基于字节码生成的高性能方案。
实现方式
1. Spring BeanUtils
typescript
import org.springframework.beans.BeanUtils;
public class UserMapperWithBeanUtils {
public static UserDTO toDTO(UserEntity entity) {
if (entity == null) {
return null;
}
UserDTO dto = new UserDTO();
// 复制相同名称的属性
BeanUtils.copyProperties(entity, dto);
// 手动处理不同名称的属性
dto.setPhone(entity.getPhoneNumber());
dto.setRegisterTime(entity.getCreateTime());
return dto;
}
public static UserEntity toEntity(UserDTO dto) {
if (dto == null) {
return null;
}
UserEntity entity = new UserEntity();
BeanUtils.copyProperties(dto, entity);
// 手动处理不同名称的属性
entity.setPhoneNumber(dto.getPhone());
entity.setCreateTime(dto.getRegisterTime());
return entity;
}
public static List<UserDTO> toDTOList(List<UserEntity> entities) {
if (entities == null) {
return Collections.emptyList();
}
return entities.stream()
.map(UserMapperWithBeanUtils::toDTO)
.collect(Collectors.toList());
}
}
2. CGLib BeanCopier
java
import org.springframework.cglib.beans.BeanCopier;
public class UserMapperWithBeanCopier {
// 创建并缓存BeanCopier实例,提高性能
private static final BeanCopier ENTITY_TO_DTO = BeanCopier.create(UserEntity.class, UserDTO.class, false);
private static final BeanCopier DTO_TO_ENTITY = BeanCopier.create(UserDTO.class, UserEntity.class, false);
public static UserDTO toDTO(UserEntity entity) {
if (entity == null) {
return null;
}
UserDTO dto = new UserDTO();
// 复制相同名称的属性
ENTITY_TO_DTO.copy(entity, dto, null);
// 手动处理不同名称的属性
dto.setPhone(entity.getPhoneNumber());
dto.setRegisterTime(entity.getCreateTime());
return dto;
}
public static UserEntity toEntity(UserDTO dto) {
if (dto == null) {
return null;
}
UserEntity entity = new UserEntity();
DTO_TO_ENTITY.copy(dto, entity, null);
// 手动处理不同名称的属性
entity.setPhoneNumber(dto.getPhone());
entity.setCreateTime(dto.getRegisterTime());
return entity;
}
}
优缺点分析
Spring BeanUtils
优点:
- 开箱即用,无需额外依赖
- 使用简单,API简洁
- 内置在Spring框架中
- 支持属性忽略功能
缺点:
- 基于反射,性能要求极高的场景可能不适合
- 仅支持相同名称的属性自动复制
- 不同名称属性需手动处理
- 不支持深层嵌套对象自动映射
CGLib BeanCopier
优点:
- 性能极高,接近手动映射
- 基于字节码生成,避免反射开销
- 缓存BeanCopier实例可进一步提升性能
- Spring Boot默认包含CGLib依赖
缺点:
- 只能复制名称完全相同的属性
- 不支持类型转换
- 配置选项有限
- 不同名称属性仍需手动处理
- 文档较少,使用门槛略高
九、总结
对象映射是Spring Boot应用中的常见需求,选择合适的映射方案能显著提高开发效率和应用性能。
根据项目规模、性能要求和团队熟悉度选择合适的方案,同时注意映射过程中的深浅拷贝、循环引用等问题。
合理使用对象映射工具,可以大幅减少样板代码,提高代码质量和可维护性。