在Java开发中,我们都知道有这么一个O家族,其中有些平时很常用,也有一些比较少用。有的时候,我们可能会混淆其中的几个成员,今天就来整理一下,争取用最简单的方式讲清楚。
为了更形象地理解,下面的全部例子都会围绕着用户模块来展开。
POJO(Plain Ordinary Java Object),简单Java对象。很形象,就一个特点那就是简单。没有花里胡哨的其他东西,对象只有属性和getter, setter方法,没有任何业务逻辑,所以PO, DTO, VO都属于POJO。下面这个就是一个最简单的POJO。
public class User {
private Long id;
private String nickname;
/* getter, setter方法 */
...
}
DAO(Data Access Object),数据访问对象。简单来说就是提供了一组接口来访问持久化数据。说到DAO就有必要同时说一下ORM和Mapper。
一句话说一下DAO ,Mapper和ORM这三者的关系:
ORM框架实现了使用数据映射模式的DAO层(因此也称作Mapper层)与数据库交互
接下来详细地说一下。
第一, DAO是数据访问对象,这个对象有若干个接口用来访问操作持久化数据。注意是持久化数据,不仅仅是数据库,还包括XML、文件等数据持久化形式。DAO的实现多种多样,Mapper仅仅是实现方式之一。简单来说,只要你有一个对象,这个对象对外提供了一组方法来操作持久化数据,不管它底层是怎么实现的,那么它就是一个DAO。下面是一个简单的DAO的例子:
public interface UserDao {
int createUser(User user);
User getUserById(Long id);
}
第二, Mapper其实我认为有两种。第一种是广义上的Mapper,全称叫做数据映射模式(Data Mapper Pattern),它是一种使实体类和数据库相互映射的模式,仅仅是一种设计模式而已,它和DAO层并没有直接关联。第二种就是我们常说的Mapper层,是如Mybatis, Hibernate等ORM框架通过数据映射模式所实现的DAO层。
第三,ORM(Object Relational Mapping),对象关系映射。一听名字就知道了,就是实现了上面第二点所说的数据映射模式的数据持久层框架。Java的Mybatis和Hibernate就是两个常见的ORM框架。
通过以上三点,我们总结一下。ORM的范围最大,做的事最多,它实现了数据映射模式的DAO层来和数据库交互。ORM替你做了与数据库打交道的工作,让你不用手写代码实现DAO层,并且它所实现的DAO层由于使用了Mapper映射模式,因此通常叫它Mapper层,就是这么简单。所以我们有些时候DAO和Mapper不分也是有道理的,因为现在基本都使用这些ORM框架了,而ORM框架的特点就是用Mapper映射模式实现DAO层来和数据库交互,本来我们需要写DAO层的实现类代码,现在只需写Mapper层接口即可。接近的概念还有Table Data Gateway和Repository Pattern,这两个好像并不常用,感兴趣的可以再去了解一下。
PO(Persistant Object),持久化对象。没什么好说的,最简单的POJO,属性和数据库中的表字段一一对应,没有任何数据操作和业务逻辑,仅有属性和getter, setter方法。
DTO(Data Transfer Object),数据传输对象,常常被用作前端/客户端和后端通信的入参和返回值。那么为什么需要DTO而不直接用PO代替呢,因为DTO更偏向前端或客户端,而不是后端。大部分情况下,前端/客户端所需要的数据都不会和数据库完全对应,总要对部分数据进行处理。比如前端的数据请求涉及几张不同的数据表,或是不需要全部数据,或是需要对部分数据进行加工,所以使用加工后的DTO来替代PO会更加合理。总而言之,DTO是看前端要什么,而不是看后端有什么。
VO(View Object),视图对象。现在来考虑这么一种情况,在个人信息页上需要展示用户的个人信息,其中一个字段是gender性别。我们假设后端在数据库中使用tinyint(1)类型存放gender字段,在Java实体类中使用Integer类型表示gender属性,并规定0是女性,1是男性。如果后端直接将带有Integer类型的gender的DTO传输给前端,那么前端还需要再对性别做一个数值判断,再决定显示在页面上的是男还是女。如果前端和后端没有规范的接口文档来指定这个字段的含义呢?如果是客户端拿到原始数据,写死了显示的样式,而之后需求改变呢?VO做的就是这个工作,将DTO中的原始数据转化为前端所需的显示值,以便能够更加灵活地展示数据。在这个例子下,VO仅仅需要把DTO中Integer类型的gender做个判断然后修改成String类型的"男"或"女"就可以了。
BO(Business Object),业务对象。BO可以算是比较抽象,比较灵活,也比较少使用的一个对象了。所谓业务对象,顾名思义,一个对象中的属性都和应用中的某个业务相关,但彼此之间却并不一定来自同一张数据表。还是用户的例子,现在我们要获取名为用户行为记录的信息(BO),其中包括了搜索记录(PO)、浏览记录(PO)和购买记录(PO)。可以看到,用户行为记录这个BO中包括了三个PO,它们由于某个特定的业务而被BO组合起来形成一个单一的实体,如下:
public class UserBehaviour {
private Long id;
private String nickname;
private List<SearchingHistory> searchingHistory;
private List<BrowsingHistory> browsingHistory;
private List<PurchaseHistory> purchaseHistory;
/* getter, setter方法 */
...
/* 业务方法 */
...
}
可以看到在BO中还可以包含一些业务方法,这些方法可以对BO内的各个PO进行一些修改整合之类的操作。由于BO可能会包含一些业务方法,所以BO并不能算作是POJO。
那么BO和DTO又有什么区别呢,看起来也可以通过在业务层将多个PO组合而形成单一的DTO,为什么需要BO呢?这也十分困扰我,因为说实话我几乎没有使用过BO,都是在业务逻辑中进行DTO的装配。就我个人来看,BO与其说是一个业务对象,更像是一小段装配不同PO的业务逻辑,与DTO相比BO更加偏内部,处在PO和DTO之间。打个比方,现在要将搜索记录、浏览记录和购买记录装配成一个对象,不使用BO的方式是在service层中获取三个PO,然后进行某些业务相关的操作将它们和其他用户信息合并成一个DTO。而如果使用BO,以上的业务操作都可以放入BO中,让BO既实现业务计算,又装配多个PO,也就是在PO和DTO中夹了一层BO。
困扰我的地方在于,BO最后其实还是得通过DTO进行传输,而如果BO内带有部分业务逻辑,可能会和业务层产生耦合,显得不那么优雅。而如果BO内部不存在业务方法,仅有数据本身,那么又和DTO没有什么区别。这也是我较少用BO的原因。
PO, DTO, VO都属于POJO,因为他们仅有属性和getter, setter方法。
BO组合了多个PO,并可能包含相关的业务计算方法
当我们的需求简单时,不需要所谓的VO和BO,一个DTO就足够了。如果前端需要额外的灵活展示,那么将DTO转化成VO。如果业务逻辑复杂,在内部可以用BO组合多个PO(对这一点我目前持保留态度)。
DAO和以上并没有什么关系。DAO是一组数据访问接口,是应用访问数据库的必经之路。DAO可以通过数据映射实现,所以DAO层在ORM框架中也被叫作Mapper层。
概念都是人造的,很多时候这么多名词的诞生都是由于业务的需要,不断地细分出新的层面来系统性地解决问题。我本人是相信奥卡姆剃刀原理的,如无必要,勿增实体。我认为如果事情真的复杂到需要再分层再解耦的地步,我会感觉得到。而在此之前,简单就是效率,过早的优化是万恶之源。这些分层和设计模式并不是万能的银弹,都脱离不了实际的需求,我认为当我们意识到需要它们的时候,才是我们真正需要它们的时候。
矩阵空间