Java Bean 是一个很常见的概念,简单来说就是一个 Java 类,其中的内容就是各种属性,以及各个属性的
getter/setter 。例如:
class Student {
private String name;
private int age;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int setAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
除了 Java Bean 这样的概念之外还有很多类似和相关的概念,《码出高效——阿里巴巴Java开发手册》中就还有
DO、DTO、VO 的概念,另外,在更高级的建模领域还有 POJO、Entity、Value Object 的概念。
大概念上,上面的各种 O 都是 Java Bean 的具体情况,或者说是更细化的情况。在高级的程序员手里,实际上直接
去使用 Java Bean 的情况并不多,更多的情况是在不同地方使用不同的 O 。
在本片笔记中,我们就来看下各种 O 的概念、使用场景以及之间的异同。当然,实际上还不止上面这些 O,我们仅
介绍最常见的几种 Java Bean 。
1. DO / PO
DO,Database Object 数据库对象。由于单词 do 在 Java 中是关键字(do-while 循环),所以在有些地方会避免
使用 DO 这个称呼,转而叫它 PO,Persistence Object 持久化对象。我个人也倾向于叫它 PO 。
无论是赤裸裸地叫数据库对象,还是委婉地叫持久化对象,很显然,这个 O 的概念一定是和数据库有关系的。
简单来说,一个 DO 类是和一个数据库中的表一一对应的 。它也是我们最常见的一种 Java Bean 。无论我们的项目
的持久层框架使用的是 Mybatis 还是 JPA/Hibernate,我们的项目中一定会有 DO / PO 的身影。
理论上来说,不考虑故意不对外暴露的情况,你有多少张表,你就会有多少个 DO 类;你的表有多少个字段,你的
DO 类就会有多少个属性;你的表的字段是什么类型,你的 DO 类的属性就是对应的什么类型。
2. DTO
DTO,Data Transfer Object 数据传输对象。它是用于 Service 层的。
Service 层调用 DAO 层时,DAO 层返回的时 DO/PO 对象,这个大家都能想到,相信大家平时的代码中也是这么写
的。但是 Controller 调用 Service 层,或者 Service 调另一个 Service 时返回的则是 DTO 对象。
为什么 Service 层不直接返回 DO/PO 对象,而又要再弄出一个 DTO 对象呢?原因有二。
1. 如果 Service 层直接返回了 DO/PO 对象,那么就会对外暴露你的数据库的表结构。
有时候,你返回的只是部分数据(数据库中的数据的部分字段),这样的话,返回 DO/PO 对象即有风险又没有
必要。
这样的话,你可以重新定义一个 DTO 类,这个类中只包含你有必要返回的那些字段,这样,调用 Service 的人
就无法知道数据库中除了 DTO 中定义的字段之外还有什么字段。
2. 简化数据的关系。
对于数据库中有一对多的情况,例如,你的数据库中有学生表和班主任表,学生和班主任是一对多的关系。那
么,你的学生类肯定是这样定义的。
class StudentPO {
private String name;
private int age;
...
private TeacherPO teacher;
}
如果你要查询一个学生的信息,并且要【顺带】查出它的班主任的信息,那么你在获得这个学生信息之后打印他的老
师的信息时,你使用学生对象的代码肯定是这样的:
System.out.println(tom.getName());
System.out.println(tom.getAge());
System.out.println(tom.getTeacher().getName());
System.out.println(tom.getTeacher().getPhone());
对于 DO/PO 对象,你需要【说】:学生的老师的名字,学生的老师的电话。通过,重定义一个 DTO 对象,你完全可以写出这样的代码:
class StudentDTO {
private String name;
private int age;
private String teacherName;
private String teacherPhone;
}
使用 DTO 的代码就可以是这样的:
System.out.println(tom.getName());
System.out.println(tom.getAge());
System.out.println(tom.getTeacherName());
System.out.println(tom.getTeacherPhone());
需要注意的是,如果你的 Service 层中多了 DTO 的定义,那么你的 Service 的职责中就多了一项:将 DAO 层返回的 DO/PO 对象转换成 DTO 对象。
3. VO
实际上 VO 对象相较而言比 DO / PO / DTO 对象要少见一些。大多数情况下,需要使用 VO 对象时,可能直接就使用了 DTO 对象了。不会再额外多定义一个类。
VO,View Object 从名字上看,它适合视图层有关的。
VO 对象面对的情况和 DTO 对象的情况类是。Controller 在调用 Service 层获得 Service 返回的 DTO 对象后,需要
将 DTO 对象转换成 VO 对象,再在 JSP 页面上展示,或者以 JSON 数据返回。
那么,问题来了:为什么需要 VO 对象,多定义一个类,而不是直接利用 DTO ,甚至是 DO / PO 对象?
不直接使用 DO / PO 对象的原因在上个章节这样已经介绍过。不直接使用 DTO 对象的原因是,DTO、DO/PO 对象中存的是【纯粹的数据】而当把数据展示在页面上时,可能需要对它进行【美化】。
什么意思呢?以 性别 为例,在数据库中人的性别大多数情况加就是用 0、1、2 这样的数值代表男、女、未知。那么,在你的 DTO、DO/PO 类中 性别 的类型就是一个简单的 int 类型。
但是在页面显示上你不可能直接显示 性别:0 、 性别:1 ,你需要将 0 和 1【美化成】男/女、先生/小姐、帅哥/美女等等个性化的内容。也即是说,至少你的 性别 要是一个字符串。
所以,Controller 要将 DTO 对象转换成 VO 对象再返回给页面或者是前端。这里就有一个转换的工作要做。
不过,上面所说的【美化】的工作其实可以转交给前端/请求方来做,没必要让 Java 后台来做这个和业务逻辑并没有太大关系的工作。因此 VO 对象其实比较少见,通常 Controller 层获得 Service 层获得到 DTO 后就返回DTO 对象,让前端/请求方自己把 0、1 【美化成】它想要显示的效果。此时,你需要在文档中和前端/调用方约定好 0、1 谁表示男,谁表示女。
4. POJO
POJO,Plain Old Java Object,很多地方都将 POJO 等同于 Java Bean,实际上 POJO 的概念要比 Java Bean 高级。
那么什么样的 Java Bean 算是一个 POJO ?
简单来说,一个 POJO 的 Java Bean 中会包含业务逻辑。这句话要是落实到代码上,那就是你的 Java Bean 除了属性的 getter / setter 方法之外还有别的(大量的)其它的方法。
例如,你定义一个钱包类:
class Wallet {
private double money;
...
}
通常,我们可能只是很简单地将钱包 Wallet 类定义成这个样子,当我们的需要判断钱包中的钱是否够用时,我们是自己去【亲自写】类似这样的代码:
if (wallet.getMoney() >= xxx) {
// 钱够用,怎么怎么着
} else {
// 钱不够用,怎么怎么着
}
但是如果是将 Wallet 定义为 POJO 的话,那么 Wallet 的设计思路要发生变化:将【判断钱包中钱是否够用】的代码移入到 Wallet 类中,即,由 Wallet 类自己去判断钱够不够用。代码如下:
class Wallet {
private double money;
...
public boolean isEnough(double money) {
return this.money >= money;
}
}
而你再使用它的时候,就只需要调用这个方法,获取结果,而不用自己去编写判断逻辑:
if (wallet.isEnough(xxx)) {
// 钱够用,怎么怎么着
} else {
// 钱不够用,怎么怎么着
}
概念上相当于,你不需要亲自去知道钱包中具体有多少钱,在你需要钱的时候你只需要去【问】钱包:我要 xxx 钱,你里面够不够这个数?
表面上来看,好像只是代码是在【这里】还是在【那里】的【小】问题,但是实际上这是一个类的设计思路的转变。而这个设计思路则是 DDD 建模的理论基础之一。
当然考虑到国内的普通的中小型项目极少用到 DDD 这么高级的建模方式,所以 POJO(以及基于这个概念的 Entity和 Value Object)实际上是很少见的。
你看到的叫 POJO 的类 99.99% 都是叫错了名字。POJO 类在有些地方也被叫做 Domain 类。
5. Entity 和 Value Object
简单说,Entity 和 Value Object 是 POJO 的两种具体情况。如果 POJO 类中有起 唯一性标识 作用的属性,那么这个POJO 类就是一个 Entity 类;反之则是 Value Object。
注意,此 VO(Value Object)非彼 VO(View Object),不要搞混淆了。
仍以上述的钱包 Wallet 类为例:
如果它是 Entity,那么它应该是这样的:
class Wallet {
private int id; // 唯一性标识
...
}
强调一点,并不是说有唯一性标识就一定是 Entity,首先它必须是 POJO。也即是说,这个类中要有业务逻辑方法,
不能净是些 getter/setter 这种没营养的方法。
另外,这个属性名既非必须是 int ,也非必须叫 id。无论是什么类型什么名字,这个属性是起到了唯一性标识的作用,那都行。
如果它是 Value Object,那么它里面就没有这个 id 属性。
至于什么时候用 Entity,什么时候用 Value Object 我们这里就不展开说了,毕竟非 DDD 建模的项目中用不上它们。