1. 概述
在软件开发领域,实体(Entity)和DTO(数据传输对象)有着明确的区别。理解它们各自的角色和差异,能帮助我们构建更高效、更易维护的软件系统。
本文将深入探讨实体与DTO的区别,通过一个基于Spring Boot和JPA的用户管理示例,清晰说明二者的设计目的和应用场景。
2. 实体
实体是应用领域中真实世界对象或概念的核心表示。它们通常直接对应数据库表或领域对象,主要职责是封装和管理这些对象的状态与行为。
2.1. 实体示例
为项目创建实体:一个用户拥有多本书。先定义Book
实体:
@Entity
@Table(name = "books")
public class Book {
@Id
private String name;
private String author;
// 标准构造器/getter/setter
}
再定义User
实体:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private String address;
@OneToMany(cascade=CascadeType.ALL)
private List<Book> books;
public String getNameOfMostOwnedBook() {
Map<String, Long> bookOwnershipCount = books.stream()
.collect(Collectors.groupingBy(Book::getName, Collectors.counting()));
return bookOwnershipCount.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse(null);
}
// 标准构造器/getter/setter
}
2.2. 实体特征
实体具有以下显著特点:
✅ ORM注解:
@Entity
标记类为实体,建立Java类与数据库表的直接关联@Table
指定关联的数据库表名@Id
定义主键字段
这些注解简化了数据库映射过程
✅ 实体关系:
通过@OneToMany
等注解建立实体间关联(如用户与书籍的拥有关系)
✅ 业务逻辑封装:
实体可包含领域特定逻辑(如getNameOfMostOwnedBook()
方法),符合OOP原则和DDD思想,将领域操作内聚在实体中
⚠️ 其他特性:
可能包含验证约束或生命周期方法
3. DTO
DTO本质是纯数据载体,不包含任何业务逻辑,用于在不同应用或应用组件间传输数据。
简单应用中常直接使用领域对象作为DTO,但随着系统复杂度增加,从安全和封装角度考虑,直接暴露整个领域模型给外部客户端并不可取。
3.1. DTO示例
实现用户创建和查询功能。先定义书籍DTO:
public class BookDto {
@JsonProperty("NAME")
private final String name;
@JsonProperty("AUTHOR")
private final String author;
// 标准构造器/getter
}
为用户定义两个DTO:创建用和响应用:
public class UserCreationDto {
@JsonProperty("FIRST_NAME")
private final String firstName;
@JsonProperty("LAST_NAME")
private final String lastName;
@JsonProperty("ADDRESS")
private final String address;
@JsonProperty("BOOKS")
private final List<BookDto> books;
// 标准构造器/getter
}
public class UserResponseDto {
@JsonProperty("ID")
private final Long id;
@JsonProperty("FIRST_NAME")
private final String firstName;
@JsonProperty("LAST_NAME")
private final String lastName;
@JsonProperty("BOOKS")
private final List<BookDto> books;
// 标准构造器/getter
}
3.2. DTO特征
基于示例可总结以下特点:
✅ 不可变性:
- 最佳实践是保持DTO不可变
- 实现方式:声明属性为
final
且不提供setter
,或使用Lombok的@Value
注解/Java 14+的record
类型
✅ 验证能力:
通过验证注解确保传输数据符合规范,防止无效数据污染领域模型
✅ JSON映射注解:
使用@JsonProperty
等注解控制JSON属性与DTO字段的映射关系
4. 仓储、映射器与控制器
为展示实体与DTO的协作价值,补充完整代码:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
创建实体与DTO的映射器:
public class UserMapper {
public static UserResponseDto toDto(User entity) {
return new UserResponseDto(
entity.getId(),
entity.getFirstName(),
entity.getLastName(),
entity.getBooks().stream().map(UserMapper::toDto).collect(Collectors.toList())
);
}
public static User toEntity(UserCreationDto dto) {
return new User(
dto.getFirstName(),
dto.getLastName(),
dto.getAddress(),
dto.getBooks().stream().map(UserMapper::toEntity).collect(Collectors.toList())
);
}
public static BookDto toDto(Book entity) {
return new BookDto(entity.getName(), entity.getAuthor());
}
public static Book toEntity(BookDto dto) {
return new Book(dto.getName(), dto.getAuthor());
}
}
💡 复杂模型可使用MapStruct等工具避免样板代码
最后实现控制器:
@RestController
@RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping
public List<UserResponseDto> getUsers() {
return userRepository.findAll().stream().map(UserMapper::toDto).collect(Collectors.toList());
}
@PostMapping
public UserResponseDto createUser(@RequestBody UserCreationDto userCreationDto) {
return UserMapper.toDto(userRepository.save(UserMapper.toEntity(userCreationDto)));
}
}
⚠️ 注意:findAll()
在大数据量时可能影响性能,建议添加分页机制。
5. 为何需要同时使用实体和DTO?
5.1. 关注点分离
- 实体:紧密耦合数据库模式和领域操作
- DTO:专为数据传输设计
在六边形架构等范式中,领域模型层完全解耦技术细节,使核心业务逻辑独立于数据库/框架实现。
5.2. 敏感数据隐藏
实体可能包含敏感信息或内部逻辑,DTO作为屏障确保仅向客户端暴露安全且必要的数据。
5.3. 性能优化
DTO模式(Martin Fowler提出)的核心是将多个参数打包为单次调用:
- 避免多次网络请求获取零散数据
- 通过GraphQL等技术实现客户端按需查询
典型场景:将关联数据聚合到DTO中单次传输,减少网络开销。
6. 总结
实体与DTO承担不同职责且差异显著:
- 实体:领域核心,包含状态、行为和持久化逻辑
- DTO:数据传输载体,注重安全性和性能
二者结合使用能确保数据安全、关注点分离和高效管理,构建更健壮、可维护的软件系统。
完整代码示例见:GitHub仓库