1. 常见的数据交换对象
1. DTO
- 目的:用于服务层与外部系统(如前端、第三方服务)之间传输数据,通常是序列化的对象。
- 特点:
- 常作为 Controller 接口的请求/响应参数
- 可以包含多个PO的组合数据。
- 字段不一定与数据库一致。
- 无业务逻辑
举例:
1 2 3 4 5
| public class LoginDTO { private String username; private String password; }
|
1 2 3 4 5 6 7 8 9
| public class UserDTO { private Long id; private String username; private String nickName; private String avatar; private Integer age; private String roleName; }
|
2. BO
- 目的:封装业务逻辑或业务规则,用于服务层内部处理。
- 特点:
- 常在 Service 层中使用
- 可包含业务方法和业务校验逻辑
- 可能由多个DTO/PO组合而成,用于Service层处理复杂业务。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class OrderBO { private String orderId; private BigDecimal amount; private Integer itemCount; private UserBO user; private List<OrderItemBO> items; private String discountRule; private BigDecimal finalAmount; private Boolean isOverdue;
public void calculateTotal() { ... } public void applyDiscount() { ... } }
|
1 2 3 4 5
| public interface ISysUserService { boolean checkUserNameUnique(SysUserBo user); boolean checkPhoneUnique(SysUserBo user); }
|
3. PO
- 目的:用于表示数据库中的实体,与表结构对应。
- 特点:
- 一般与数据库字段一一对应
- 可被 ORM(如 MyBatis、JPA)直接映射
- 一般不包含业务逻辑。
- 常用于DAO层与数据库交互。
1 2 3 4 5 6 7 8 9
| public class UserPO { private Long id; private String username; private String password; private LocalDateTime createTime;
}
|
如果你的项目确实需要区分持久化对象和领域对象,可以考虑命名如 UserPO
或者 UserEntity
,但更常见的做法是直接用 User
作为实体类名,并通过注解等方式来标记它是持久化的实体。
4. DO
目的:
- DO = Domain Object,领域对象
- 在领域驱动设计(DDD) 中使用,代表业务领域的核心模型,包含数据和行为。
- 在非DDD的项目中,PO 与 DO 有时可混用
特点:
是“充血模型”(有方法+属性)。
强调业务语义,如“下单”、“支付”等行为。
与PO可能结构不同。
📌 DDD中,DO是核心,PO只是持久化载体。
通常直接命名为 User
,因为它代表了业务领域的核心模型,包含了数据和相关的行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class UserDO { private Long id; private String username; private String password; private LocalDateTime createTime;
public void changePassword(String oldPwd, String newPwd) { if (!password.equals(DigestUtils.md5Hex(oldPwd))) { throw new IllegalArgumentException("原密码错误"); } this.password = DigestUtils.md5Hex(newPwd); }
public boolean isPremiumUser() { return createTime.isBefore(LocalDateTime.now().minusYears(1)); } }
|
5. VO
目的:用于向前端展示数据,通常是页面展示的数据模型。
特点:
- 通常包含经过组合或格式化的展示数据
- 不一定与数据库字段一一对应
- 有时会包含前端所需的额外字段,如状态名、格式化日期等
1 2 3 4 5
| public class UserVO { private String username; private String email; private String createTimeFormatted; }
|
2. 组合使用
如果你在项目中使用的是 Spring Boot + MyBatis 或 JPA 的架构,通常会看到如下组合使用:
层级 |
使用对象 |
职责说明 |
Controller |
DTO , VO |
接收请求参数(DTO)、返回响应(VO) |
Service |
BO , DTO |
业务逻辑处理,DTO → BO → DO,DO → BO → VO |
DAO / Repository |
DO |
数据库操作,如增删改查 |
Entity (DB映射) |
DO/PO |
与数据库表对应的实体 |
1. 前端提交请求(JSON)
1 2 3 4 5 6
| POST /api/user/register { "username": "jack", "email": "jack@example.com", "password": "123456" }
|
2. Controller 层
1 2 3 4 5
| @PostMapping("/register") public Result<UserVO> register(@RequestBody UserRegisterDTO dto) { UserVO vo = userService.register(dto); return Result.success(vo); }
|
- 入参:
UserRegisterDTO
- 出参:
UserVO
3. Service 层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public UserVO register(UserRegisterDTO dto) { UserBO bo = new UserBO(dto.getUsername(), dto.getEmail(), dto.getPassword());
if (!bo.isValidEmail()) { throw new BusinessException("邮箱不合法"); }
UserDO userDO = new UserDO(); BeanUtils.copyProperties(bo, userDO); userDao.insert(userDO);
UserVO vo = new UserVO(); BeanUtils.copyProperties(userDO, vo); return vo; }
|
4. DAO 层(MyBatis 示例)
1 2 3 4 5
| @Mapper public interface UserDAO { void insert(UserDO user); UserDO findByUsername(String username); }
|
5. UserDO 映射数据库表
1 2 3 4 5 6 7
| public class UserDO { private Long id; private String username; private String email; private String password; private LocalDateTime createTime; }
|
数据库表结构示例:
1 2 3 4 5 6 7
| CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50), email VARCHAR(100), password VARCHAR(100), create_time DATETIME );
|