1. Entity(实体对象)
• 定义:与数据库表结构一一映射的 Java 类,通常使用 JPA/Hibernate 注解(如 @Entity、@Table)。 • 核心职责: • 表示数据库表的字段和关系(如 @OneToMany)。 • 通过 DAO/Repository 层进行持久化操作(增删改查)。 • 特点: • 字段与数据库列直接对应。 • 通常不包含业务逻辑(贫血模型)。 • 可能包含 JPA 懒加载、级联操作等特性。 • 示例:
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
// 省略 getter/setter
}2. VO(View Object,视图对象)
• 定义:专为前端展示设计的对象,用于封装需要呈现的数据。 • 核心职责: • 适配前端需求(如字段组合、格式化)。 • 隐藏敏感字段(如密码、内部状态)。 • 特点: • 字段可能与多个 Entity 或 DTO 关联。 • 无业务逻辑,仅关注数据展示。 • 通常由 Controller 层返回给前端。 • 示例:
public class UserVO {
private String username;
private String email;
private String lastLoginTime; // 格式化后的日期
private Integer orderCount; // 聚合其他表的数据
// 省略 getter/setter
}3. BO(Business Object,业务对象)
• 定义:封装复杂业务逻辑和状态的对象,通常与领域驱动设计(DDD)结合。 • 核心职责: • 实现核心业务规则(如订单价格计算、状态流转)。 • 协调多个 Entity 或外部服务。 • 特点: • 充血模型:包含数据字段和业务方法。 • 不直接映射数据库,可能聚合多个 Entity。 • 由 Service 层创建和操作。 • 示例:
public class OrderBO {
private User user;
private List<Product> products;
private Coupon coupon;
// 业务方法:计算订单总价
public BigDecimal calculateTotalPrice() {
BigDecimal total = products.stream()
.map(Product::getPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (coupon != null) {
total = coupon.applyDiscount(total);
}
return total;
}
}4. DAO(Data Access Object,数据访问对象)
• 定义:负责与数据库交互的组件,封装 CRUD 操作。 • 核心职责: • 提供对 Entity 的增删改查方法。 • 处理复杂查询(如分页、关联查询)。 • 特点: • 不包含业务逻辑,仅关注数据操作。 • 在 Spring 中通常由 JpaRepository 或 MyBatis Mapper 实现。 • 示例:
public interface UserDao extends JpaRepository<User, Long> {
// 自动生成查询方法
User findByUsername(String username);
// 自定义 JPQL
@Query("SELECT u FROM User u WHERE u.email LIKE %:domain")
List<User> findByEmailDomain(@Param("domain") String domain);
}对比表格
| 组件 | 用途 | 数据来源 | 是否含业务逻辑 | 分层位置 |
|---|---|---|---|---|
| Entity | 映射数据库表结构 | 数据库表字段 | 否(贫血模型) | DAO/Repository 层 |
| VO | 前端展示数据适配 | 多个 Entity/DTO 的聚合 | 否 | Controller 层 |
| BO | 封装复杂业务逻辑 | 多个 Entity/服务调用结果 | 是(充血模型) | Service 层 |
| DAO | 数据库操作(CRUD) | 数据库 | 否 | DAO/Repository 层 |
典型交互流程
[前端请求]
→ Controller 接收请求,使用 **VO** 接收参数或返回结果
→ Service 层调用 **DAO** 操作 **Entity**,或创建 **BO** 处理业务逻辑
→ **DAO** 读写数据库,返回 **Entity**
→ Service 将 **Entity** 转换为 **VO** 或 **DTO**
→ Controller 返回 **VO** 给前端最佳实践
1. Entity 与 VO 的隔离
• 不要直接返回 Entity 给前端:避免暴露敏感字段(如 password)。 • 使用 MapStruct 或手动转换:
@Mapper
public interface UserConverter {
UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);
@Mapping(target = "lastLoginTime", source = "lastLogin", dateFormat = "yyyy-MM-dd")
UserVO toVO(User user);
}2. BO 的设计原则
• 高内聚:将相关业务逻辑集中到 BO 中。 • 避免依赖 DAO:BO 应通过 Service 层获取数据,而非直接调用 DAO。
// 正确:由 Service 层提供数据
public class OrderService {
private OrderDao orderDao;
public void processOrder() {
List<Order> orders = orderDao.findAll();
OrderBO orderBO = new OrderBO(orders);
orderBO.validateStock();
}
}3. DAO 的职责边界
• 仅关注数据操作:不处理业务规则(如价格计算)。 • 复杂查询封装:使用 @Query 或 QueryDSL,避免在 Service 层拼接 SQL。
常见问题
Q1:Entity 可以直接作为 DTO 使用吗?
• 不推荐:Entity 可能包含敏感字段或 JPA 懒加载代理,直接暴露会导致序列化问题(如 LazyInitializationException)。
Q2:BO 和 Service 的区别是什么?
• BO:封装原子业务逻辑(如订单价格计算)。 • Service:协调多个 BO 或 DAO,处理事务和流程调度。
Q3:VO 和 DTO 有什么区别?
• DTO:用于层间数据传输(如 Service → Controller),字段接近 Entity。 • VO:专为前端定制,字段可能高度重组或格式化。
总结
• Entity 是数据库的映射,DAO 是数据库的操作者。 • VO 是前端的适配器,BO 是业务逻辑的容器。 • 通过清晰的分层和职责分离,可以提升代码的可维护性和扩展性。