DO、DTO和VO分层设计的好处
在Java中 VO、 PO、DO、DTO、 BO、 QO、DAO、POJO的概念中介绍过Java中的各种模型概念。
在这里简单再总结一下:
在日常的项目开发中,
VO对应于页面上需要显示的数据(表单),DO对应于数据库中存储的数据(数据表),DTO对应于除二者之外需要进行传递的数据。
很多人可能对VO和DTO并不是那么熟悉,相反对DO却比较熟悉,那是因为在很多项目中由于种种原因我们只使用了DO,原因可能有以下几种:
1、项目太小,对于一种业务实体,封装成一个DO就够了。
2、并不熟悉DTO、VO,更不知道他们之间的区别。
3、了解DO\DTO\VO之间的区别,但是懒得用。
那么,这里,博主再啰嗦一下为什么要引入这么多概念,为什么我要建议大家在自己的项目中使用这些实体对象。
为什么不能只用一个DO
我们来看这样一个例子。假如我们的项目中由User这样一个实体。我们在创建User表的时候一般包含一下属性:

针对这个实体,我们通常需要创建一个DO类,用来封装这个User实体。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
publicclass UserDO { privateInteger id; //唯一主键 privateDate createdTime; //创建时间 privateDate updatedTime; //最后更新时间 privateString name; //姓名 privateInteger age; //年龄 privateString gender; //性别 privateString address; //住址 privateString password; //密码 privateString nickName; //昵称 privateDate birthday; //生日 privateString politicalStatus; //政治面貌,1表示群众,2表示团员,3表示党员,4表示其他,100表示未知 privateInteger companyId; //公司的ID privateInteger status; //数据状态,1表示可用,0表示不可用 //setter and getter} |
然后,在代码中,从DAO一直到前端展示,我们都通过这个UserDO类的对象来进行数据传输。这样做会有什么问题嘛?
- 不需要的字段也会传递到前端页面。
- 如password、createdTime、updatedTime和status这几个字段我们可能在前端根本不需要展示,但是这些字段有可能也会被传递到前端(除非我们在SQL查询的时候没有查询出对应的字段)。这不仅使数据的传输量增大,还可能有安全性问题。
- 某些字段需要转换,但是无法支持。
- 对于上面例子中的政治面貌字段,我们在数据库中存储的是数字,但是在前端页面我要展示的是中文描述。这种情况只能在前端通过
if/else的方式来分情况展示。
- 对于上面例子中的政治面貌字段,我们在数据库中存储的是数字,但是在前端页面我要展示的是中文描述。这种情况只能在前端通过
- 某些字段要展示,但是并不希望出现在数据库中
- 在User表中我们只保存了这个用户的companyId,需要同时查询company表来查询出该公司的更多详细信息。对于User对象,如果我们想在前端同时展示他所属的公司,希望通过一次查询全都查出来怎么办?有几个简单的方案,第一个是让UserDO中包含一个Company类的属性,通过这个属性来传递。另外一种是把我们希望传到前端的Company的属性也写到UserDO中。但是,如果真的这么做了,那UserDO还能被称作DO了吗?
还有很多问题,这这里就不详细介绍了。
可见,使用一个DO从头用到尾(从数据库到前端页面)并不是一种好的设计。
如何正确的使用DO、DTO、VO
对于上面的例子,我们可以将他设计成以下类。由于模型并不复杂,这里只需要再引入VO就可以了。
UserDO已经和数据库字段一一对应了,这里不需要修改。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
publicclass UserDO { privateInteger id; //唯一主键 privateDate createdTime; //创建时间 privateDate updatedTime; //最后更新时间 privateString name; //姓名 privateInteger age; //年龄 privateString gender; //性别 privateString address; //住址 privateString password; //密码 privateString nickName; //昵称 privateDate birthday; //生日 privateString education; //学历 privateString politicalStatus; //政治面貌,1表示群众,2表示团员,3表示党员,4表示其他,100表示未知 privateInteger companyId; //公司的ID privateInteger status; //数据状态,1表示可用,0表示不可用 //setter and getter} |
引入UserVO,用于封装传递到前端需要展示的字段。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
publicclass UserVO { privateInteger id; //唯一主键 privateString name; //姓名 privateInteger age; //年龄 privateString gender; //性别 privateString address; //住址 privateString nickName; //昵称 privateDate birthday; //生日 privateString education; //学历 privateString politicalStatus; //政治面貌,群众、团员、党员等 privateCompany company; //公司 //setter and getter} |
UserVO中只包含了展示所需要的字段,并不需要展示的字段在这里不需要包含。
在引入了这个类之后,我们就可在进行数据库查询的时候使用UserDO,然后再需要传递到前端的时候把DO转换成VO。
总结
看到这里,你可能已经发现,UserDO和UserVO中包含了大量的相同字段。难道真的有必要再单独设计个VO嘛?我可以明确告诉你的是,当你的系统越来越大,表中的字段越来越多的时候,使用DO\DTO\VO等概念进行分层处理是绝对有好处的。至于如何进行有效的在不同的实体类间进行转换是我接下来要介绍的。
优雅的将DO转换成VO
Dozer 是一个对象转换工具。
Dozer可以在JavaBean到JavaBean之间进行递归数据复制,并且这些JavaBean可以是不同的复杂的类型。
所有的mapping,Dozer将会很直接的将名称相同的fields进行复制,如果field名不同,或者有特别的对应要求,则可以在xml中进行定义。
前面我们介绍的DO\DTO\VO不就是JavaBean嘛。正好可以使用Dozer进行转换呀。
除了使用Dozer,当然你还由其他选择:
典型的解决方案就是手动拷贝,弊端很明显,代码中充斥大量Set 和Get方法,真正的业务被埋藏值与值的拷贝之中。
另一种方案就是使用BeanUtil,但BeanUtil不够很好的灵活性,又时候还不得不手动拷贝。Dozer可以灵活的对对象进行转换,且使用简单。
Dozer 支持的转换类型
Primitive 基本数据类型 , 后面带 Wrapper 是包装类 Complex Type 是复杂类型
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
• Primitive to Primitive Wrapper• Primitive to Custom Wrapper• Primitive Wrapper to Primitive Wrapper• Primitive to Primitive• Complex Type to Complex Type• String to Primitive• String to Primitive Wrapper• String to Complex Type ifthe Complex Type contains a String constructor• String 到复杂类型 , 如果复杂类型包含一个 String 类型的构造器• String to Map• Collection to Collection• Collection to Array• Map to Complex Type• Map to Custom Map Type• Enum to Enum• Each of these can be mapped to one another: java.util.Date, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.util.Calendar, java.util.GregorianCalendar• String to any of the supported Date/Calendar Objects.• Objects containing a toString() method that produces a longrepresenting time in (ms) to any supported Date/Calendar object. |
在普通Java项目中使用Dozer
在pom.xml中增加依赖
|
1
2
3
4
5
|
<dependency> <groupId>net.sf.dozer</groupId> <artifactId>dozer</artifactId> <version>5.5.1</version></dependency> |
使用Dozer进行类转换
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
publicclass Main { publicstatic void main(String[] args) { DozerBeanMapper mapper = newDozerBeanMapper(); UserDO userDO = newUserDO(); userDO.setName("hollis"); userDO.setAddress("hz"); userDO.setAge(25); userDO.setCompanyId(1); userDO.setBirthday(newDate()); userDO.setGender("male"); userDO.setEducation("1"); userDO.setNickName("hollis"); userDO.setPoliticalStatus("3"); UserVO userVO = (UserVO) mapper.map(userDO, UserVO.class); System.out.println(userVO); }} |
特殊的字段转换
在使用mapper进行转换前,设置一个或多个mapping文件
|
1
2
3
|
List myMappingFiles = newArrayList(); myMappingFiles.add("dozer-mapping.xml"); mapper.setMappingFiles(myMappingFiles); |
配置dozer-mapping.xml文件
数据类型不一致,或者名称不相同或者有级联关系等情况下,可以通过文件dozer-mapping.xml来进行定制Bean的转换
|
1
2
3
4
5
6
7
8
9
10
|
<?xml version="1.0"encoding="UTF-8"?><mappings xmlns="http://dozer.sourceforge.net"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://dozer.sourceforge.nethttp://dozer.sourceforge.net/schema/beanmapping.xsd"> <mapping> <class-a>com.hollis.lab.UserDO</class-a> <class-b>com.hollis.lab.UserVO</class-b> </mapping></mappings> |
在JavaWeb项目中使用Dozer
在pom.xml中增加依赖
|
1
2
3
4
5
|
<dependency> <groupId>net.sf.dozer</groupId> <artifactId>dozer</artifactId> <version>5.5.1</version></dependency> |
使用Spring集成dozer
|
1
2
3
4
5
6
7
8
9
10
11
|
<?xml version="1.0"encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean id="baseMapper"class="org.dozer.spring.DozerBeanMapperFactoryBean"> <property name="mappingFiles"> <list> <value>classpath:mapping/dozer-mapping.xml</value> </list> </property> </bean></beans> |
使用baseMapper进行Bean的转换
|
1
2
3
4
5
6
7
8
|
@AutowiredprivateMapper baseMapper;privateUserVO doToVo(UserDO userDO){ if(userDO == null) returnnull; UserVO vo = baseMapper.map(userDO, UserVO.getClass()); if(userDO.getCompanyId != null) getCompany(vo); returnvo;} |
通过以上的代码加配置,我们就实现了从DO转换到VO的部分操作,之所以说是部分操作,是因为我们在dozer-mapping.xml并没有做多余的配置,只是使用dozer将DO中和VO中共有的属性转换了过来。对于其他的类型不同或者名称不同等的转换可以参考官网例子通过设置dozer-mapping.xml文件来实现。
上面还有一个getCompany()没有实现。这个方法其实就是通过companyId查询出company实体然后在赋值给UserVO中的company属性。
在使用了dozer之后,我们可以把UserDO中的部分属性赋值到UserVO中,其实,转化的过程是通过调用UserDO中的getter方法和UserVO中的setter方法来实现的。读者可以做个实验,对于UserVO中的部分属性不写Setter方法看看还能不能把属性值转换过来,博主已经测试过了,是不能的。
DO、DTO和VO分层设计的好处的更多相关文章
- VO(DTO)模式在架构设计中是否需要
DTO(VO):也就是一般意义上的VO,封装后的对象.一般用于Web层—Service层间的数据传输入. PO:也就是一般概念上的Domain Object,如hibernate 中的Entity.一 ...
- JavaWeb单体项目的分层设计与实现
1.概述 为什么要把一个完整的项目(Project)按层拆分成多个模块(Module)? 1)使项目层次更加的清晰: 2)提高代码的复用性: 3)细化分工: 4)解耦. 是不是听起来很高大尚,今天就简 ...
- ABP分层设计
ABP分层设计 一.为什么要分层 分层架构是所有架构的鼻祖,分层的作用就是隔离,不过,我们有时候有个误解,就是把层和程序集对应起来,就比如简单三层架构中,在你的解决方案中,一般会有三个程序集项目:XX ...
- 什么是JavaBean、bean? 什么是POJO、PO、DTO、VO、BO ? 什么是EJB、EntityBean?
什么是JavaBean.bean? 什么是POJO.PO.DTO.VO.BO ? 什么是EJB.EntityBean? 前言: 在Java开发中经常遇到这些概念问题,有的可能理解混淆,有的 ...
- linux设备驱动的分层设计思想--input子系统及RTC
转自:linux设备驱动的分层设计思想 宋宝华 http://blog.csdn.net/21cnbao/article/details/5615493 1.1 设备驱动核心层和例化 在面向对象的程序 ...
- Java中PO、DO、DTO、 VO、 BO、POJO 、DAO、TO的概念
1. PO(persistant object) 持久对象 在 O/R 映射的时候出现的概念,如果没有 O/R 映射,没有这个概念存在了. 通常对应数据模型 ( 数据库 ), 本身还有部分业务逻辑的 ...
- Web自动化框架之五一套完整demo的点点滴滴(excel功能案例参数化+业务功能分层设计+mysql数据存储封装+截图+日志+测试报告+对接缺陷管理系统+自动编译部署环境+自动验证false、error案例)
标题很大,想说的很多,不知道从那开始~~直接步入正题吧 个人也是由于公司的人员的现状和项目的特殊情况,今年年中后开始折腾web自动化这块:整这个原因很简单,就是想能让自己偷点懒.也让减轻一点同事的苦力 ...
- robot framework 使用四:分层设计和截图以及注意事项
再说一下眼下的主要环境信息和版本号: 操作系统:win7 64位 python版本号:2.7.6 RIDE版本号:1.2.3 selenium2library:1.5.0 selenium:2.40. ...
- RSF 分布式 RPC 服务框架的分层设计
RSF 是个什么东西? 一个高可用.高性能.轻量级的分布式服务框架.支持容灾.负载均衡.集群.一个典型的应用场景是,将同一个服务部署在多个Server上提供 request.response 消息通知 ...
随机推荐
- 如何给框架添加API接口日志
前言 用的公司的框架,是MVC框架,看了下里面的日志基类,是操作日志,对增删改进行记录, 夸张的是一张业务的数据表 需要一张专门的日志表进行记录, 就是说你写个更新,添加的方法都必须写一遍操作日志,代 ...
- 输入输出流ObjectInputStream、ObjectOutputStream(对象序列化与反序列化)
对象的输入输出流 : 主要的作用是用于写入对象信息与读取对象信息. 对象信息一旦写到文件上那么对象的信息就可以做到持久化了 对象的输出流: ObjectOutputStream 对象的输入流: Ob ...
- python面向对象之类的组合
一.python类的组合:给一个类的对象封装一个属性,这个属性是另一个类的对象二.组合的意义:让类的对象与另一个类的对象产生关系,类与类之间产生关系.三.例子模拟英雄联盟写一个游戏人物的类 要求:(1 ...
- Codeforces Round #542 [Alex Lopashev Thanks-Round] (Div. 2)
A. Be Positive 题意:给出一个数组 每个树去除以d(d!=0)使得数组中大于0的数 大于ceil(n/2) 求任意d 思路:数据小 直接暴力就完事了 #include<bits/s ...
- vue实战记录(六)- vue实现购物车功能之地址列表选配
vue实战,一步步实现vue购物车功能的过程记录,课程与素材来自慕课网,自己搭建了express本地服务器来请求数据 作者:狐狸家的鱼 本文链接:vue实战-实现购物车功能(六) GitHub:sue ...
- 2018年秋季学期《c语言程序设计》助教总结
<c语言程序设计>第七周助教总结 <c语言程序设计>第八周助教总结 <c语言程序设计>第九周助教总结 <c语言程序设计>第十周助教总结 <c语言程 ...
- openstack项目【day23】:keystone组件HTTP协议
阅读目录 一 为何要学习HTTP协议 二 用户上网过程 三 HTTP协议 part1 http协议概述 part2 请求协议 part3 响应协议 四 抓包分析HTTP协议 一 为何要学习HTTP协议 ...
- CMDB服务器管理系统【s5day90】:API验证
1.认证思路刨析过程 1.请求头去哪里拿? 1.服务器端代码: def test(request): print(request) return HttpResponse('你得到我了') 2.客户端 ...
- Mysql加锁过程详解(9)-innodb下的记录锁,间隙锁,next-key锁
Mysql加锁过程详解(1)-基本知识 Mysql加锁过程详解(2)-关于mysql 幻读理解 Mysql加锁过程详解(3)-关于mysql 幻读理解 Mysql加锁过程详解(4)-select fo ...
- MySQL学习笔记(一)Ubuntu16.04中MySQL安装配置(5.6优化、错误日志、DNS解决)
目录 第一部分.5.6安装.配置.自动备份 第二部分.5.7源码安装.配置.自动备份 第一部分.5.6安装 1.安装mysql sudo apt-get install mysql-server su ...