概念POJO、DTO、DAO、PO、BO、VO、QO、ENTITY详解

本文详细解释了Java开发中常见的各种对象类型,如POJO、PO、BO、VO等,并介绍了它们的区别与应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在java开发过程中,新手总是被DAO、PO、BO、VO等等概念弄得晕头转向。
下面我查找了很多资料,总结如下:

一、POJO(Plain Ordinary Java Object 简单Java对象)

实际就是普通Java Bean,是为了避免和EJB(Enterprise Java Bean 企业级Java Bean)混淆所创造的简称,也称为(Plain Old Java Object 又普通又老的对象)。

相比于EJB来说,的确是老的对象,因为ORM中间件的日趋流行,POJO又重新焕发了光彩。

POJO的内在含义是指:一个常规的、不受任何框架、平台的约束和限制的Java对象。除了遵守Java语法,它不应该继承预先设定的类、实现预先设定的接口或者包含预先指定的注解。可以认为,如果一个模块定义的对象皆为POJO,那么除了依赖JDK,它不会依赖任何框架或平台。

通俗的理解,POJO仅包含自身的属性以及自身属性的getter和setter方法,这意味者POJO可以方便的从一个框架迁移到另一个框架中,或者框架升级也会对代码毫无影响,因此而得到复用。

//例如在该实体EJB中,实体包含业务逻辑,同时也包含自身的持久化逻辑
//当更换数据源,或改变中间件框架时,则需要修改大量代码
public class Customer {
	private Long id;
	private String name;
	private Set<Order> orders = new HashSet();
	//省略业务逻辑
	//数据库访问方法
	public void remove(){
		//通过不同方式访问数据库,例如JDBC,Mybaits,JPA
	}
	public Customer load(){...}
	public Customer create(){...}
}
//当改为POJO时,则可以运行在任一JAVA环境中
public class Customer {
	private Long id;
	private String name;
	private Set<Order> orders = new HashSet();
	//省略getter和setter
}

一般,当需要持久化对象时,有些人喜欢将该对象放在名为xxxPOJO的目录中。当然,不建议这样命名。

POJO实际上是包括BO/VO/PO/DO等一系列对象的总称。有的团队规定禁止命名成xxxPOJO。

从本质上来说,POJO对象并非只有getter/setter的贫血对象,它的主要特征不在于它究竟定义了什么样的成员,而在于它作为一个常规的Java对象,并不依赖于除语言之外的任何框架。它的目的不是数据传输,也不是数据持久化,本质上,它是一种设计模式。

JavaBean解释

Java Bean是一种Java开发规范,要求一个Java Bean类必须同时满足以下3个条件:

  • 类必须是具体的、公共的;
  • 具有无参构造函数;
  • 提供一致性设计模式的公共方法将内部字段暴露为成员属性,即为内部字段提供规范的get和set方法。

认真解读这3个条件,你会发现它们都是为支持反射访问类成员而准备的前置条件,包括创建Java Bean实例和操作内部字段。

只要遵循Java Bean规范,就可以采用完全统一的一套代码实现对Java Bean的访问。这一规范并没有提及业务方法的定义,这是因为规范无法对公开的方法做出任何一致性的限制,意味着框架使用Java Bean,看重的其实是对象携带数据的能力,可通过反射访问对象的字段值来简化代码的编写。

例如,JSP对Java Bean的使用如下:

<jsp:useBean id="student" class="com.dddsample.javabeans.Student">
   <jsp:setProperty name="student" property="firstName" value="Bill"/>
   <jsp:setProperty name="student" property="lastName" value="Gates"/>
   <jsp:setProperty name="student" property="age" value="20"/>
</jsp:useBean>

JSP标签中使用的Student类就是一个Java Bean。如果该类的定义没有遵循JavaBean规范,JSP就可能无法实例化Student对象,无法设置firstName等字段值。

至于Session Bean、Entity Bean和Message Driven Bean则是Enterprise JavaBean的3个分类。它们都是Java Bean,但EJB对它们又有框架的约束,例如Session Bean需要继承自javax.ejb.SessionBean、Entity Bean需要继承自javax.ejb.EntityBean。

追本溯源,可发现POJO与Java Bean并没有任何关系。一个POJO如果遵循了Java Bean的设计规范,可以成为一个Java Bean,但并不意味着POJO一定是Java Bean。

反过来,一个Java Bean如果没有依赖任何框架,也可以认为是一个POJO,但Enterprise Java Bean一定不是一个POJO。

POJO可以封装业务逻辑,Java Bean的规范也没有限制它不能封装业务逻辑。一个提供了丰富领域逻辑的Java对象,如果同时又遵循了Java Bean的设计规范,也可以认为是一个JavaBean。

二、PO(persistence object 持久层对象)

对象字段持有的数据需要被持久化到数据表中,参与到持久化操作的对象就被称为持久化对象(persistence object,PO)。

通常,PO是在ORM(对象关系映射)中与数据表的一条记录相匹配,自身属性与数据表字段一一对应。可以将数据表中的一条记录作为一个对象处理,并可以转化为其它对象。

面对不同的数据源时,比如文档型数据库,对象型数据库等时,顾名思义PO是DAO层为进行持久化操作而准备的对象。

  • 包含getter、setter方法。
  • 一般不包含业务逻辑与数据库的访问方法。因为数据库本身不包含业务逻辑。
  • PO平常不一定需要实现序列化,只是当采用分布式存储或者需要作为前端输出及远程调用使用时,应该实现序列化
//示例代码
public class User implements Serializable {
    //序列化版本,通过IDEA自动生成
    private static final long serialiVersionUID = 1Lprivate Long id;
    private String username;
    private String password;    
    //省略getter和setter方法
}

注意,持久化对象并不一定就是ORM机制(对象关系映射)的数据对象,相反,在领域驱动设计中,持久化对象往往指的就是领域模型对象。

在《阿里巴巴开发手册》中,PO也叫DO(Data Object)数据对象,与数据库表结构一一对应,通过DAO层向上传输数据源对象。

三、DAO(data access object 数据访问对象)

包含对数据的访问,负责持久层的操作 。通常需要结合PO来访问数据库,主要用来封装对数据的访问,并不转化成其它对象。
在基于“事务脚本”的业务设计时,它包含业务逻辑。否则,一般只包含持久化的封装。

public interface Dao{
    int insert(User user);
    User selectById(long id);
}

注意这里数据访问对象封装了其它对象(PO)的持久化能力。那么对象可以包含自身的持久化能力吗?

实际上,如果是对象混入了持久化能力的领域模型,Martin Fowler将这种具有持久化能力的领域对象称为活动记录(active record)。

活动记录:一个对象,它包装数据库表或视图中某一行,封装数据库访问,并在这些数据上增加了领域逻辑。

对象既有数据又有行为。这些数据中大多数都是持久数据并且需要保存到数据库。活动记录(Active Record)使用最直截了当的方法,把数据访问逻辑置于领域对象中。这样大家都知道如何从数据库中读取数据或向数据库中写入数据。
在这里插入图片描述
在领域模型的初始设计中,主要是要从活动记录和数据访问对象(DAO)中选择其一。

活动记录的主要优点是简单。活动记录容易构建,并且易于理解。其主要问题在于,仅当活动记录对象和数据库中表直接对应,即二者为同构模式时,活动记录才比较有效。

如果业务逻辑复杂,就会想到使用对象的直接关系、集合和继承等。它们都难以映射到活动记录,逐渐添加它们会显得很凌乱。这也正是使用DAO的原因。

四、DTO(Data Transfer Object 数据传输对象)

数据传输对象,是在应用网络层需要传输的对象,是一个为了减少方法调用次数而在进程间传输数据的对象。

它用于在进程间传递数据的对象,目的是减少方法调用的数量。

在《阿里巴巴开发手册中》规定是Service 或 Manager 向外传输的对象。由于Service向外传输的接受方可能是UI前端界面,微服务RPC调用,消息中间件等,跨越了进程,所以使用DTO的方式进行传输。

DTO模式诞生的背景是分布式通信。考虑到网络传输的损耗与不可靠性,设计分布式服务需遵循一个总体原则:尽可能设计粗粒度的服务,每个服务的方法应代表一个完整的功能,而不是功能的一个步骤。粗粒度服务可以减少服务调用的次数,从而减少不必要的网络通信,同时也能避免对分布式事务的支持。
某些人称这个为“值对象”,当然还是有稍许区别。值对象追求对象不可变,DTO的对象是可修改,可改变的。

领域模型对象遵循面向对象设计原则,在细粒度上分离职责,因而无法满足粗粒度服务契约的要求。这就需要对领域模型对象进行封装,组合更多的细粒度对象形成一个粗粒度的DTO对象。

1.什么是DTO?

DTO模式满足一种叫消息契约模型的概念。通常定义在本地服务层,远程服务和应用服务都以消息契约模型对象作为接口方法的输入参数和返回值,使其不止限于进程间的数据传递,还能对领域模型提供保护。

从输入来看,在进行请求时,应用在接口接收传入对象,然后又转换成实体进行持久化。在此过程中,传入的对象就是DTO。
它的命名方式可能是Param、Query 、Command、等。Param 为查询参数对象,适用于各层,一般用做接受前端参数对象。Param 和 Query 的出现是为了不使用 Map 做为接收参数的对象。

从输出来看,在进行返回响应时,若数据表有100个字段,那么PO中就有100个属性,而界面可能只需要其中10个属性,那么查询数据库后,对象就需要由PO转化成DTO。
DTO可能还需要组合多个表查询到的对象成为一个大对象,以便减少网络的调用,或者给前端传输一些不在数据库中查到的属性,所以需要添加属性。

//示例代码,继承实体类,从查询到的PO中添加属性返回给前端
public class UserDTO extends User {
    // 因为要进行网络传输,应该要实现序列化
    private static final long serialiVersionUID = 2L//用户标识
    private String username;
    public String getUsername(){return username;}
    public void setUsername(String username){this.username= username;}
    //添加额外属性
    private HashMap<String, Object> extProperties;        
    public HashMap<String, Object> getExtProperties() {
        return extProperties;
    }
    public void setExtProperties(HashMap<String, Object> extProperties) {
        this.extProperties = extProperties;
    }
}
2.DTO里面有什么?

一般不建议创建DTO,因为里面就一些属性和getter、setter方法,业务价值小,作用仅仅是在一次调用中传输几部分信息。

其中,属性必须为原始数据类型,因为DTO需要被序列化以便能在连接中传播。

它只包含自身数据的存储,而不包含业务逻辑。

为了支持进程间的数据传递,DTO必须支持序列化。最好将其设计为一个Java Bean,即定义为公开的类,具有默认构造函数和getter/setter方法,这样就有利于一些框架通过反射来创建与组装DTO对象。

DTO通常还应该是一个贫血对象,因为它的目的是传输数据,没有必要定义封装逻辑的方法,但考虑到它与领域模型之间的映射关系,可能需要为其定义转换方法。

在不同的客户端之间,通常需要DTO组装器完成领域对象和DTO之间的转化。

public interface DTOAssembler {
	public DTO createDTO(DO domainObject);
	public void updateDO(DTO  dataTransferObject);
	public DO createDO(DTO  dataTransferObject);
}
3.DTO怎么使用?

对于不同的客户端展现,可以选择一次封装所有可能的数据组成DTO,也可以为每种展现创建不同的DTO。各有利弊。

  • 单个大DTO,减少调用次数,只用创建一次,但是难以掌握传输的数据

  • 不同的小DTO,要创建很多DTO,传输数据很清晰

对于输入方和输出方,可以共用一个DTO,也可以各准备一个DTO,视情况而定。

有部分团队 RPC 的请求和响应参数都通过 DTO 来承载,通过 XXRequestDTO 和 XXResponseDTO 来表示。

五、Entity(实体)

实体,顾名思义,实体需要给予一个唯一标识,以区分其它实体,而值对象VO不需要。
实体应该有一个生命周期,是有状态的,例如抽象订单有一个唯一识别号,订单有从下单创建到最后交货完成的生命周期,实体对象的状态可以变化。

1.与VO(值对象)的区别:
只要两个实体对象的唯一标识相等,就判断两个对象相等,即使其他属性不同。可以修改实体状态。
而VO(值对象)没有标识,所有属性相等,才判断两个对象相等。只能创建新的值对象,不能修改。

2.与PO(持久化对象)的区别:
PO与数据表的一条记录对应,通常为了方便存储,会给PO赋予了一个主键ID。
从而,PO也像实体一样具有了标识,Martin Flowler称之为委派标识,区别于实体标识。

例如身份证号作为身份证的实体标识,唯一区分其他身份证。但是存储在数据库中,依然可能分配自增主键ID(1,2,3,…)。若值对象需要持久化,也会被分配委派标识,方便查询,或与外键关联。
实体的标识与业务有关,PO的委派标识仅方便存储,与技术有关。

//示例代码
public class IDCardEntity {
	//委派标识
    private int id;
    // 身份证号,实体标识,唯一确定一个实体
    private String  cardCode;
    int getId(){return id;}
}

六、VO(value object 值对象)

值对象,通常用于业务层之间的数据传递,仅仅包含自身的数据。
与实体的区别是,没有唯一标识,无生命周期,内部值是不变的。
与PO的区别是,PO只在数据层,作为存储。VO在商业逻辑层和表示层,作为一个概念整体。

  • 值对象通常是小而简单的对象,如货币、日期或地址这样的对象,判断相等时不根据标识ID。比如,年月日相同,就判断这两个对象相等。
  • 值对象易于创建,参数传递时通常是传递值,而不是传递引用。
  • 值对象不应被持久化,这个对象被创建后只能被引用,当没有引用时交给垃圾回收自动处理。
//示例代码,比如Address这样无唯一标识的就是值对象
public class Address{
    private String country;
    private String province;
    private String city;
    public Address(String country, String province, String city) {
        this.country= country;
        this.province= province;
        this.city= city;
    }
    //省略equals方法
}
//调用值对象,若要修改值对象,值对象不用维护,直接创建一个全新的Address对象
//原对象直接被抛弃,而不是在原对象上进行修改
public Address changeAddress(String newCity, String newProvince, String newCity) {
    return new Address(newCity, newProvince, newCity);
}

自从java版本从14更新以来,引入了一种叫Record的类型。该类型就是典型的值对象,只能new对象,不再能set对象,它是不可变更的对象,能更好地保证并发访问的安全。

现在越来越有一种趋势,就是需要新的对象直接new就好。若不再使用,直接交给垃圾回收机制即可。

public record Person(String name, int age) {}
Person person = new Person("Tom", 25);
System.out.println(person.name()); // 输出:Tom
System.out.println(person.age()); // 输出:25

理解了VO的意思,也就明白了,某些DTO也是一种值对象。历史上,在Martin Flowler称“值对象”的术语,在J2EE社团中称为“数据传输对象”,这在设计模式界引起了一场混乱。

七、VO( View Object 显示层对象)

Value Object和View Object的简写都是VO,可能容易弄混。View Object的含义是通常是Web向模板渲染引擎层传输的对象,也叫视图对象。

视图对象(view object,VO)其实是消息契约模型中的一种,往往遵循MVC模式,为前端UI提供了视图呈现所需要的数据,我将其称为“视图模型对象”​。

当然,因为为前端UI展示数据,确实跨进程了,也可以把这个对象命名为DTO作为传输。

由于它主要用于后端控制器服务和前端UI之间的数据传递,这样的视图模型对象自然也属于DTO对象的范畴。

//Controller层
public class xxController {
	public XXResponseDTO query(@RequestBody XXRequestDTO requestDTO){
	    return xxService.query(XXResponseDTO);
	}
}

视图对象可能仅传输了视图需要呈现的数据,也可能为了满足前端UI的可配置,由后端传递与视图元素相关的属性值,如视图元素的位置、大小乃至颜色等样式信息。

{
	info:[
		{label:"用户名称",value:"张三",align:"center"},
		{label:"性别",value:"男",align:"right"}
	]
}

很多页面也需要额外数据,比如错误码、提示信息、分页信息等,查询的DTO之后需要再封装成View Object 显示层对象再显示出来

{
	errcode: "00000",
	errmsg: "ok",
	data: {
		pageNum: 1, 
		pageSize: 10, 
		totalPage: 1, 
		total: 4, 
		list: [...]
	}
}

系统分层架构规定边缘层承担了BFF(Backend For Frontend)层的作用,定义在边缘层的控制器会操作这样的视图对象。

《阿里巴巴开发手册》中建议把输出的显示层对象命名为VO。按此规定,在service层将结果转换为DTO输出,在controller层再转化为VO对象输出,以适配前端UI的需求。

public class xxController {
	public HttpResult query(@RequestBody XXRequestDTO requestDTO){
		XXResponseDTO reponseDTO = xxService.query(requestDTO);
	    HttpResult vo = HttpResultUtils.convert(reponseDTO);
	    return vo;
	}
}
public class xxService {
	public XXResponseDTO query(XXRequestDTO requestDTO) {
	    // 省略其他逻辑
		return reponseDTO;
	}
}

一般视图对象的命名可能是UserVO、UserVM、UserDTO、UserCO、UserReponse、UserInfo这样的后缀形式。

由于值对象(value object)的简称也是VO,因此在交流时,一定要明确VO的指代意义,避免概念的混淆。

八、BO(business object 业务对象)

业务对象(business object,BO)是企业领域用来描述业务概念的语义对象。这是一个非常宽泛的定义。

业务对象,就是把业务逻辑封装为一个对象(注意是对象本身的业务逻辑,而不是协调其它对象的逻辑),这个业务对象可以包括一个或多个其它的对象。

一些业务建模方法使用了业务对象的概念,如SAP定义的公共事业模型,就将客户相关信息抽象为合作伙伴、合同账户、合同、连接对象等业务对象。它是站在一个高层次角度的表述,并形成了高度抽象的业务概念。

如果系统采用经典三层架构,可认为业务对象就是定义在业务逻辑层中封装了业务逻辑的对象。

一般,实现业务的通常方式之一,是包括数据而不包含行为的领域对象(所谓“贫血模型”)+Service类来实现的。其中,业务逻辑是包含在service层里,随着业务不断演进,service类里的逻辑越来越复杂,越来越重,不利于重用。

而在《面向领域驱动设计》中,将对象本身的逻辑也封装在对象中(所谓“充血模型”),而service类仅仅起协调作用,比如对领域对象的调用及其它工具的调用,不包含业务本身的逻辑,是轻量级的薄薄一层,名为应用服务层。当业务不断演进时,通常只需要关注业务对象(BO)即可,而应用层面较少变化。

因此,业务对象,也是领域对象(Domain Object)的另一说法。

//示例代码
public class User {
    private String name;
    private String password;
    //对象自身的业务逻辑
    public void changePassword(String newPassword){
        this.setPassword(newPassword);
    }
    //省略getter和setter方法
}

业务对象(BO)通常是实体,或者是聚合根,包含多个实体或值对象,内部实现业务逻辑。

此对象在实际使用中有不一样的理解,有的团队将 BO 当作 Service 返回给上层的 “专用 DTO” 使用;而有的团队则当作 Service 层内保存中间信息数据的 “DTO” 或者上下文对象来使用(建议采用这种理解)。

九、QO(query object 查询对象)

数据查询对象,各层接收上层的查询请求。注意,【强制】如果超过2个参数的查询封装,则禁止使用Map类传输。

查询对象用于 Controller 层方法接收客户端的请求参数。

在实际应用当中数据不可能仅仅是以int find(int num1, int num2)这种简单的几个int的方式进行传输的,而是要封装成相对复杂的Request/Response对象,即用我们自定义的类来进行消息的传输,那么就需要一种规则来序列化/反序列化我们自己的对象成为某种标准格式。

public class MyQO {
  private String param1;
  private String param2;
  ...
}
@PostMapping("/post")
public String post(@RequestBody MyQO qo) {
  ......
}

十、AO (Application Object 应用对象)

一般用在控制层和服务层之间。有些团队会将前端查询的属性和保存的属性几乎一致的对象封装为 AO,如读取用户属性传给前端,用户在前端编辑了用户属性后传回后端。这种用法将 AO 用做 Param 和 VO 或 Param 和 DTO 的组合。

//Controller层
public HttpResult update(@RequestBody XXAO ao1){
	XXAO ao2 = XXService.update(ao1);
    HttpResult vo = HttpResultUtils.convert(ao2);
    return vo;
}

十一、总结

POJO:简单Java对象,它没有任何限制和特定的约定,是一个普通的Java对象。
DTO:数据传输对象,它是一个数据传输结构,通常用于不同进程间的数据传输,在不同层之间传递数据的对象。
DAO:数据访问对象,是一个数据访问模式,在应用程序中它通常扮演着对数据库的访问。
PO:持久化对象,通常指ORM(对象关系映射)中映射的数据库表对应的实体类。
BO:业务对象,是应用程序中业务逻辑的实现。
VO:值对象,它是一个用于存储数据的对象,通常是与UI/界面模型相关的对象。
QO(Query):查询对象,它主要用于定义查询条件和规则,用于接收前端传递的查询条件参数。
ENTITY:实体对象,是一个与业务相关的对象,通常是与应用程序领域模型相关的对象。
Param:表示请求参数,用于接收前端传递的参数
Command:表示命令,用于接收前端传递的命令参数

各对象的命名习惯:

  • PO通常放在名为bean、entity、model目录中。
  • DAO本身就是一层,通常是DAO、mapper、repository目录。
  • BO通常在service、manager、business,domain,model目录中。
  • DTO通常在command、representation、DTO目录中。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值