假如有一张user表,里面有几个字段:
user_id | user_name | pass_word | create_time | dept_id |
假设就这么多吧,一张很基础的表,那么对应Java的写法就是一个类:
class User{
private Long userId;
private String userName;
private String passWord;
private LocalDateTime createTime;
private Long dept id;
//getter setter (lombok)
那么我现在需要对于User这张表进行增删改查,那么如果我假设你学过Mybatis?还是JPA这种持久 层框架的话,或者你只要学过JDBC的话,你就应该知道,Java中肯定需要一个对象来映射数据库的 这张表。User类 的每个属性就是数据库表的每一个字段,该类的每一个对象就代表数据库中的每 一行,List就代表该表的所有记录。 这个User类就被称为PO,一般也不会加PO的说法,这个类一般就是User。
下面来说说DTO和VO。Data Transfer Object和View Object(不知道拼错没有?大概就这个意 思)
下面用一个实际场景解释一下: 比如,User这张表,一般在前端我们会进行分页+条件查询,然后在前端用一个列表组件 ,对于后 端请求过来的数据做渲染。对于CRUD来说,这也是一个基本的需求。
那么假设现在有这样一个情况:假设我们再简化一下情况,我们只做条件查询 ,我需要按照用户 名userName模糊匹配,用户创建时间createTime区间匹配 的时候,我后端应该如何写? 或者换言之,我后端应该用一个什么对象来接收前端传过来的请求参数? 这时候我们来想想,User类对象,他还能胜任吗?userName还好说,那create Time的区间怎么 表示呢? 显然这时候我们就不能在后端这样写了:
public XXX【返回值】 selectUserByCondidition(@RequestBody @Validated User user);
光靠一个User对象我们是不足以接收前端传过来的请求参数的,那你说我可以这样啊:
public XXX【返回值】 selectUserByCondidition(String userName, LocalDateTime startTime, LocalDateTime endTime)
。。。。。
这还只是两个字段的条件,如果字段一但多起来了,你确定这样写?你确定老板看了以后不会让你 当场毕业? 。。。。。 所以这时候我们就要用到DTO了,数据传输对象,用于在网络中涉及到数据传输的封装对象。那么 此时我们就可以在后端定义这样一个DTO:UserQueryConditionDTO【用户条件查询DTO】
public class UserQueryConditionDTO{
private String userName;
private LocalDateTime startTime;
private LocalDateTime endTime;
//getter setter validation_annotation constrcutor【这些答主就不写了】 ....
}
然后后端接口就可以这样写:
public XXX【返回值】 selectUserByCondidition(@RequestBody @Validated UserQueryConditionDTO dto};
简单明了,别人一看代码,点进去一看,你再给字段打上备注,一下就清晰了。这就是DTO的用处。 下面来说VO的用处:VO意思就是View Object,View这个单词学过MVC思想的都知道,【都能看到这个 问题了,不会还有人MVC都不知道吧】,叫视图层,那么View Object呢?顾名思义,那就是返回给视图 层【前端】需要用到的对象。 这时候又有人不懂了,返回给前端我需要啥VO啊,什么意思?还是用这个User举例子。 我通过刚才的DTO,然后通过一系列JDBC操作,我查询到了List列表,里面就包含每一个用户的 完整信息【用户Id,用户名,密码,部门Id,创建时间】,也就是上面表的五个字段。 但是你准备就把这个List返给前端吗??前端展示的用户所属部门,至少是展示部门名吧?怎么可 能直接在前端列表去展示每个用户的所属部门Id呢? 不会吧,不会真有兄弟给前端说,你获取到List这个数组以后,你对于每一个dept_Id再分别发请 求,我给一个接口,你给我dept_Id,我给你返回dept_Name,不就完了吗。你确定前端兄弟不想打屎 你?这样的网络IO有考虑过吗,数据量多了一个页面都是几十个请求到时候,这样是肯定不行的。 那部门名去哪里找呢?那肯定有一张部门表 咯,这不就是表的关联关系吗,一般来说,最简单的情况下, 都是通过Id关联,假设有一张部门表dept:
id | dept_name | create_time | deleted |
Id就是部门的Id,和User表的dept_id有映射关系,deleted就是逻辑删除 ,create_time和dept_name就 不多说了。逻辑删除不懂的请自行百度或谷歌。 大概提一下,逻辑删除就是当前端删除该数据的时候,用这个标识字段来表示该数据的删除情况,假设0就 是未删除,1就是已删除。在任意查询的时候我们也会避开逻辑删除字段为1的数据。 那么此时我们就应该用关联来查询出用户的所属部门名了:SQL伪代码,看懂意思就行。
select
u表的所有数据,d.dept_name
from user u
left join dept d
on u.dept_id = d.Id
//后面你排不排序啥的我也不管了,这里自行按照业务逻辑添加
那么此时,我们通过该SQL返回的数据还能用User对象接收吗? 当然就不行了,因为User对象没有DeptName这个属性,但现在我们需要一个对象封装带有deptName 属性的user。 那么我们考虑该对象是需要发送给前端来展示的,也就是视图层,所以我们就需要一个对象,来传输给视 图层,那么,就是我们的VO了。UserVO来返回给前端。
class UserVO{
Long userId;
String userName;
String deptName;
String password;
Long deptId;
LocalDateTime createTime;
//Getter Setter etc ...
}
这就是VO的用处。
下面来说一下DAO,DAO一般在刚学MVC的时候会涉及到,Data Access Object,数据访问对象 ,他就 是用来访问数据库的对象,我们一般就会在这里面写上很多查询数据库的方法。 比如UserDAO【User的CRUD操作】,一般增删改查会有五个方法:
1.条件分页查询
2.按照批量Id【也可以传单个】删除
3.新增
4.按照Id查询某一条的数据【修改时候数据前端回显】
5.修改接口
interface/class UserDAO {
public List<UserVO> queryUserByCondition(UserDTO dto,PageCondition page);
public User getUserById(Long userId);
public int deletedUserByBatchIds(List<Long> userIds);
public int editUser(UserEditDTO dto);
public int addUser(UserAddDTO dto);
}
如果看到这里都没问题,那么有些靓仔可能又有问题了,你这UserDAO为啥类型又写interface又写class 啊?
这个其实是接口的一种思想,如果你把查询数据库的代码【假如是JDBC逻辑,不用持久层框架哈】,那么 这个UserDAO会变得很大,如果别人想来看一下你这个UserDAO里面到底有什么方法的时候,别人就会 看着很头疼。
所以我们一般把UserDAO写成一个接口,里面定义UserDAO操作数据库的方法的规范,然后会有一个类 UserDAOImpl去实现UserDAO接口,然后在Impl类 中写上真正操作数据库的代码。这样别人想了解 UserDAO到底提供了哪些功能的时候,可以,看接口即可。想看功能到底怎么实现的时候,也可以,看 Impl实现类的业务逻辑即可。
DAO其实就是用来操作数据库的,封装所有对于数据库的数据CRUD操作,然后让业务层直接注入DAO对 象,然后使用该方法即可。
不过在后面,我们更倾向于把DAO对象叫做Mapper【Map不是地图的意思,在后面开发中他更多的意思 在于映射】,以后就不叫UserDAO了,我们一般叫UserMapper【mapper的意思和dao一样】, UserMapper就是表示用于封装数据库操作的一个映射对象,可以这样理解吧,不过mapper的意思和 DAO八九不离十,后面开发中就是一个概念了。【下文中就把DAO称为Mapper了】
那么这时候又有靓仔想问了,假如我有很多表,User,Dept,SysJob【系统任务表】,SysLog【系统日 志表 】,SysDict【系统字典表 】等等很多表,每一张表我都想提供基本的CRUD功能。
那么我难道要把上面五个逻辑几乎一样的CRUD方法还要在每一个XXXMapper中写一次吗?
比如UserMapper,DeptMapper,SysJobMapper,SysLogMapper,SysDictMapper,我每一个 Mapper都要去写通用的五个CRUD逻辑吗?显然是太冗余了,那我能不能抽出一个公用的BaseMapper, 来封装单表的CRUD操作呢?
如果上面思路能懂,那你学MybatisPlus也就不难了,MybatisPlus【简称MP】,就是提供了一个Base Mapper接口,这个接口里面封装了大量的单表CRUD便利操作,相当于以后,我们只需要这样写:
public interface UserMapper extends BaseMapper<User>
我们就可以在Mapper层写0代码,直接快速完成对于user单表的CRUD了,包括单表的各种复杂查询,直 接0SQL开发【就是不用写一句SQL】,快不快。
BO就是Business Object了,业务对象,这个就不用怎么解释了,就是封装了业务逻辑的对象,一般开发 中,BO估计就是代指Service层吧?业务层对象,MVC的设计模式一般是前端请求,来到后端的 Controller【Provider】控制器层,控制器做一些最基本的校验【比如身份校验,入参校验,日志AOP等 等】,然后分发请求到相应的业务层【也就是Service组件】来做处理。
业务层对于请求进行业务处理,如果要涉及到和数据库交互的,就是用Mapper【也就是DAO】层对象和 数据库交互即可,大概就是这样一个逻辑。按照这样来说,Service层对象,比如User Service,应该算 BO对象吧。
BO ,一个用户下面 肯定会关联很多其他的表 比如用户设置 用户信息 等,那么这个BO 下 不但有用户本身的一些属性,还包含了用户设置 和用户信 息这两个类。
POJO就是Plain And Ordinary Java Object,简单Java对象,这就没啥 好讲的。
DO,Data Object,数据对象,这概念吧,其实有是有,但是用的,我个人感觉不多,有一种情况就是微 服务 之间互相传输对象数据的时候,我们就会将该数据抽取出来。
比如商品下单以后,大量的JSON数据会传输到后台,我们会在后台调用很多模块,比如OMS/订单管理模 块,又或是ERP模块,又或是WMS/库存物流模块,等等等等,会调用很多其他微服务模块。不同服务之 间传输对象,要不然就将对象定义为DO【或者TO,不同叫法而已,Transfer Object或者Data Object】,然后两个模块之间RPC调用,传输的对象要被两个模块共享,要不然就将每一个模块涉及的各 种实体对象单独抽出来放在一个entities模块下啊,要不然就是将这些微服务模块之间的交互传输对象定义 在Common模块中,作为DO或者TO,来进行微服务之间的数据传输所用。
图示直观感受: