核心概念总览

POJO是 Plain Old Java Object 的缩写,直译为"普通的、老式的 Java 对象"。

它指的是一个简单的、纯粹的 Java 对象,不依赖于任何特定的框架、接口或注解。

下面都属于POJO

缩写 英文全称 中文释义 核心职责与使用场景
PO Persistent Object 持久化对象 与数据库表结构一一对应,是数据层(DAO层)操作的核心。
DO Domain Object 领域对象 领域驱动设计(DDD) 中,代表业务领域中的核心实体。
DTO Data Transfer Object 数据传输对象 用于进程/服务间的数据传输,通常用于远程调用等需要网络传输的场景。
VO View Object 视图对象 用于展示层,封装前端页面需要的数据。
BO Business Object 业务对象 在业务逻辑层,由多个PO或其它BO组合而成的复合业务对象。

详细解释与用法

1. PO (Persistent Object) - 持久化对象
  • 是什么:PO 是与数据库表结构直接映射的 Java 对象。每个属性通常对应数据库表的一个字段。

  • 怎么用

    • 主要在 DAO / Mapper 层 使用。

    • 通过 MyBatis、Hibernate 等ORM框架进行增删改查操作。

    • 不应包含任何业务逻辑,只是一个纯粹的数据载体。

  • 示例
    假设有一张 user 表,有 id, username, password 字段。

    public class UserPO {
        private Long id;
        private String username;
        private String password;
        // getter, setter ...
    }
    
    // 在DAO/Mapper层使用
    @Mapper
    public interface UserMapper {
        UserPO selectById(Long id);
        void insert(UserPO userPO);
    }
2. DO (Domain Object) - 领域对象
  • 是什么:在领域驱动设计(DDD) 模式中,DO 是业务领域的核心实体,它不仅有数据,还包含与这个数据相关的业务行为(方法)。

  • 怎么用

    • 领域层 使用。

    • 它强调的是封装性行为,而不仅仅是数据的getter/setter。

    • 在很多项目中,如果没有严格采用DDD,PO 和 DO 常常被混用,即一个类既是PO也是DO。但在严谨的架构中,它们应该分离。

  • 示例
    一个银行领域的 Account 对象。

    public class AccountDO {
        private String accountNumber;
        private BigDecimal balance;
    
        // 行为:存款
        public void deposit(BigDecimal amount) {
            if (amount.compareTo(BigDecimal.ZERO) <= 0) {
                throw new IllegalArgumentException("存款金额必须大于0");
            }
            this.balance = this.balance.add(amount);
        }
    
        // 行为:取款(包含业务规则)
        public void withdraw(BigDecimal amount) {
            if (amount.compareTo(BigDecimal.ZERO) <= 0) {
                throw new IllegalArgumentException("取款金额必须大于0");
            }
            if (this.balance.compareTo(amount) < 0) {
                throw new RuntimeException("余额不足");
            }
            this.balance = this.balance.subtract(amount);
        }
        // getter ...
    }
3. DTO (Data Transfer Object) - 数据传输对象
  • 是什么:用于不同进程或服务之间传输数据的对象。在网络传输(如RPC、REST API)时,它作为数据的容器。

  • 怎么用

    • 服务层(Service)控制层(Controller) 之间,或者微服务之间传输。

    • 它的设计不依赖于数据库表结构,而是根据传输需求来定义字段。

    • 通常是一个纯数据结构,只有属性和getter/setter。

  • 示例
    在用户注册时,前端传来的数据可能不包括 id(由数据库生成),但包括密码确认字段。

    // 用于接收前端注册请求的DTO
    public class UserRegisterDTO {
        private String username;
        private String password;
        private String confirmPassword; // 这个字段在PO中不存在
        // getter, setter ...
    }
4. VO (View Object) - 视图对象
  • 是什么专门为前端界面展示而设计的对象。

  • 怎么用

    • Controller 层 组装并返回给前端。

    • 它的字段结构完全由前端页面的需求决定,通常会聚合多个PO/DTO的数据,或者对数据进行格式化。

    • 绝对不能包含敏感信息(如密码)。

  • 示例
    用户信息页面需要展示用户基本信息和他的订单数量。

    // 用于前端用户信息页面的VO
    public class UserProfileVO {
        private String username;
        private String nickname;
        private String avatarUrl;
        private Integer orderCount; // 这个数据可能来自Order服务
        // getter, setter ...
    }
    
    // 在Controller层使用
    @RestController
    public class UserController {
        @GetMapping("/user/profile")
        public UserProfileVO getUserProfile() {
            // ... 业务逻辑,组装VO
            return userProfileVO;
        }
    }
5. BO (Business Object) - 业务对象
  • 是什么:在业务逻辑层(Service层) 使用的对象,它代表一个复合的业务实体,通常由多个PO聚合而成。

  • 怎么用

    • Service 层 内部使用,用于封装复杂的业务逻辑。

    • 一个BO可能包含多个PO,或者包含对其他BO的引用。

  • 示例
    “订单”这个业务对象,不仅包含订单本身的信息(OrderPO),还包含订单下的商品列表(OrderItemPO列表)和用户信息(UserPO)。

    public class OrderBO {
        // 来自 order 表
        private OrderPO orderInfo;
        // 来自 order_item 表
        private List<OrderItemPO> orderItems;
        // 来自 user 表
        private UserPO user;
    
        // 可以在BO中计算业务属性,如总价
        public BigDecimal getTotalAmount() {
            return orderItems.stream()
                    .map(item -> item.getPrice().multiply(new BigDecimal(item.getQuantity())))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
        }
        // getter, setter ...
    }

数据流转与转换

在一个典型的分层架构中,数据的流转如下:

数据库 <-> PO <-> DAO层 <-> Service层 <-> Controller层 <-> 前端

  • DAO层:操作 PO

  • Service层

    • 内部使用 BO 来封装业务。

    • 接收来自Controller的 DTO

    • 返回 BO 或直接是 PO 给Controller。

  • Controller层

    • 接收前端传来的 DTO(通常通过 @RequestBody)。

    • 调用Service层,获取 BO/PO

    • BO/PO 转换为 VO,返回给前端。

关键点:各层之间的对象不应该直接传递,而应该进行转换。例如,Service层返回的BO,在Controller层要转换成VO再返回。这避免了底层数据结构的变化直接影响到上层(如API接口)。

转换工具:为了避免繁琐的 setter/getter 代码,推荐使用 MapStructBeanUtils(谨慎使用,注意性能和数据覆盖问题)、ModelMapper 等工具进行对象转换。

总结与最佳实践

  1. 职责分离:每个对象都有自己的职责,不要用一个对象贯穿所有层。这符合软件设计的“单一职责原则”。

  2. 避免“大而全”的对象:不要创建一个包含所有字段的“User”类,然后希望它在所有地方都能用。这会导致数据冗余、安全隐患(如把密码返回给前端)和强耦合。

  3. 按需定义:VO、DTO 的结构应根据前端和接口的需求来灵活定义,而不是死板地对应数据库。

  4. 理解本质而非记名字:在不同的公司或团队中,对这些对象的称呼可能不同(例如,有人把所有对象都叫POJO)。最重要的是理解它们背后的设计意图:在不同的上下文中,使用最适合的数据模型

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐