DTO/DO等POJO对象的使用场景和 orika-mapper 框架的使用
对于项目而言, 我们一般会有DAO->Service->Controller分层设计, 这些层次体现了每层的作用, 而层次之间的数据传递对象设计很少被提及, 下面是一个相对完整的数据转换过程:
Table层--(DO对象)-->DAO层--(DO对象)-->Service层--(DTO对象)-->Controller层--(VO对象)-->Web Template层
DO(domain object) 领域对象, 也有一种叫法是 entity object, 个人不推荐使用 entity object这个叫法, 因为Relationship 表也可以有DO 类.
1. 一般DO类和数据表是一一对应, 属性和字段也是一一对应的.
2. 对于 relationship 表, 一般也需要有一个对应的 DO 类.
3. DO 类中不应该包含复杂的逻辑, 仅仅是一些简单的 setter() 和 getter() 方法.
4. 比如 user 表有一个 deptId 字段, user entity 类必须有一个 deptId 属性, 不推荐有一个对应的 dept Entity 对象, 当然更不应该直接包含 dept entity 对象的属性, 比如 deptName 等.
5. 多个 DO 类之间的关系和数据模型一样, 是满足第三范式.
6. DO类名: 以DO作为后缀, 或者不加DO这样的后缀.
DTO(Data Transfer Object) 数据传输对象:
1. 用于"跨进程或远程"传输, 对象的序列化/反序列化的网络开销较大, 这时就需要加入 DTO 对象, 典型的使用场景是用来封装Rest API接口. 如果是一个单体应用, 专门维护一套DTO类, 成本和收益相比, 引入DTO意义就不大了.
2. DTO 是一个贫血对象, 它不应包含"业务"处理逻辑, 主要是一些属性和getter和setter访问器, 也可包含一些属性重组逻辑.
3. 多个 DTO 类之间的关系一般不再满足第三范式, 而是反范式.
4. DTO类名: 应该以DTO作为后缀.
5. 用于解耦实体对象的存储层和上层, 这包含下面的好处:
(1): 隐藏部分底层表的属性, 以减少网络传输的代价.
(2): 在存储层和上层增加一个隔离, 屏蔽相互之间的影响.
(3): 用来可封装多个DTO对象, 比如user DTO对象可以包含一个Dept DTO对象; 或者将Dept DTO某些常用属性直接flatten到User DTO对象上, 方便上层的使用. 所以, 一般情况下DTO类的数量要比DO类要少.
(4): 如果没有DTO, 很多时候 Controller 层不得不直接最低层的 Entity 类, 跨层数据对象依赖将使得分层设计大打折扣.
VO(View Object/Value object)视图对象:
专门用于展现层(比如页面展现等).
对于一般的项目, VO 和 DTO 属性基本一致, 没有必要再维护一套VO类.
在前后端分离的大背景下, 即使是大型项目, 也没有必要再维护一套VO类.
Pojo(plain old java object) 普通java对象:
上述的DO/DTO/VO对象都属于Pojo对象. 阿里巴巴规范中有如下要求:
1. Pojo 类属性必须使用包装数据类型, 而不是基本数据类型, 理由是: 数据库中由可能是null值, 如果使用基本类型, 由可能导致自动拆箱异常.
2. 不能为任何属性设定默认值. 理由是: 强制使用者在使用时显式赋值, 任何NPE问题都应由使用者来保证.
3. Pojo 类都必须实现 toString() 方法, 可以使用 IDE 的 source/generate toString() 功能
4. Pojo 类都必须实现 Serializable 接口.
下图能形象展现 DO 类和 DTO 类的区别:
下图以基于角色的权限管理模块为例, 展现了 table/DO对象/DAO层/Service层 对应关系:
(1) table 是采用范式建模, 共三个实体表, 两个关系表.
(2) pojo和table直接对应,
(3) 在Dao层重点关注实体的操作, 所以就只有三个dao对象, 关系表的维护退化到实体操作中.
(4) 在service层, 是面向顶层使用, 重点考虑的是使用如何方便, 一般也都和Dao的类数量一致.
=====================================
object-object mapping framework 清单
=====================================
正如ORM过程, 我们可以手写代码, 也可以使用MyBatis这样的ORM 工具, 对于DO/DTO的转换也是一样, 可以手写代码转换, 也可以使用object-object mapping framework完成自动转换, 当然也能用在对象之间需要深度拷贝的时候.
当前活跃的框架清单:
https://github.com/mapstruct/mapstruct
https://github.com/DozerMapper/dozer
https://github.com/orika-mapper/orika
https://github.com/modelmapper/modelmapper
性能对比:
https://github.com/arey/java-object-mapper-benchmark
https://www.baeldung.com/java-performance-mapping-frameworks
综合考虑性能/流行度/项目活跃程度, mapstruct 明显领先, 不过我这里更推荐使用 orika-mapper, 原因是:
1. 使用 mapstruct的话, IDE中需要引入 mapstruct 编译插件, 我们自己负责编写mapper interface, mapstruct 负责生成mapper实现类的class文件, 工程化成本较高, 可控性较差.
2. orika-mapper 相对更容易推广, mapping 逻辑可控性较强, 性能也算不错.
=====================================
orika-mapper 使用
=====================================
pom.xml 引入依赖
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>${orika.version}</version>
</dependency>
--------------------------------
简单示例:
--------------------------------
Orika 要求 Pojo 类必须是public级别,否则会在运行时报错 java.lang.IllegalAccessError.
只要src/target属性名一致, Orika 能自动完成所有的属性的深拷贝, 甚至是 List<T> 这样的属性. 需要说明的是, 默认情况下定义的映射规则是双向的, 也就是说, 如果从 target object 也能得到 src object.
//准备 src 对象
PersonSource source = new PersonSource(); //生成 MapperFactory 对象, 然后使用 MapperFactory 对象注册 src/target 映射关系, 本例 src/target 的属性名完全一致, 所以不需要再设置映射关系.
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); // MapperFacade 对象负责对象之间的映射
MapperFacade mapper = mapperFactory.getMapperFacade(); //完成DO->DTO的映射
PersonDest destination = mapper.map(source, PersonDest.class);
--------------------------------
更复杂的示例:
--------------------------------
如果src/target属性名不一致, 或者要重组DO属性, 这时需要自定义src/target的映射规则.
//自定义 src/target class的映射规则
mapperFactory.classMap( Source.class, Destination.class)
.field(......) //src/target 双向映射
.fieldAToB(......) //src->target的单向映射
.fieldBToA(......) //target->src的单向映射
.exclude(......) //不考虑指定的属性
.byDefault()
.register();
mapperFactory 除了负责注册src/target 映射规则, 还可以 mapperFactory.getConverterFactory().registerConverter()来注册自定义类型转换器, 比如要将 Date 类型转换为 String类型, 实际项目中, 一般不需要自定义类型转换器, 因为 json 框架也有这样的功能.
//定义一个类型转换器 MyConverter
public class MyConverter extends CustomConverter<Date,MyDate>{} //注册类型转换器
mapperFactory.getConverterFactory().registerConverter(new MyConverter()); // 然后仍然通过 mapper 完成src/target对象的深度拷贝.
MapperFacade mapper = mapperFactory.getMapperFacade();
=====================================
参考
=====================================
https://cloud.tencent.com/developer/article/1110666
http://tech.dianwoda.com/2017/11/04/gao-xing-neng-te-xing-feng-fu-de-beanying-she-gong-ju-orika/?utmsource=tuicool&utmmedium=referral
DTO/DO等POJO对象的使用场景和 orika-mapper 框架的使用的更多相关文章
- POJO对象
POJO(Plain Old Java Objects)简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称. 使用POJO名称是为了避免和 EJB混淆起来, 而且简 ...
- jface databinding:部分实现POJO对象的监测
在前一篇博文<jface databinding/PojoBindable实现对POJO对象的支持 >中,已经知道直接对POJO对象进行修改,是不能被绑定的UI组件知道的,在上一篇文章中 ...
- 【SpringMVC】SpringMVC系列7之POJO 对象绑定请求参数值
7.POJO 对象绑定请求参数值 7.1.概述 Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值.而且支持级联属性.如:dept.deptId.dept ...
- 自动封装Servlet HttpServletRequest请求成为一个POJO对象
自己写了个小工具类,将Servlet里面的HttpServletRequest请求封装成为一个POJO对象,可以复习一下Java的反射原理,开发中这个没什么用,毕竟都用MVC框架,框架都自带这种功能, ...
- Hibernate(二)——POJO对象的操作
POJO对象其实就是我们的实体,这篇博客总结一下框架对POJO对象对应数据库主键的生成策略,和一些对POJO对象的简单增删改查的操作. 一,Hibernate框架中主键的生成策略有三种方式: 1,数 ...
- 使用 POJO 对象绑定请求参数
概述 Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值并且支持级联属性.这一特性在日常开发过程中使用频率比较高,开发效率也高,本文主要对 POJO 对象绑定 ...
- 漫淡面向对象——POJO对象
产品或者服务由数据存储和数据计算组成.pojo对象就是用于数据存储.一旦确定后,整个应用或者产品的数据来源就确定.比如一个页面或者功能需要使用什么数据就可以快速找到对应的对象或者通过对象的关系找出来. ...
- Hibernate三种状态;query查询;ResultTransformer转换为pojo对象;能够将query语句写在xml中;Criteria查询;ProjectionList总和/f分组等函数
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/u010026901/article/details/24256091 Session操作过程中的po ...
- mybatis由浅入深day01_8输出映射_8.1resultType输出类型(8.1.1输出简单类型_8.1.2输出pojo对象和pojo列表_8.1.3输出hashmap)
8 输出映射 8.1 resultType(输出类型) 使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功. 如果查询出来的列名和pojo中的属性名全 ...
随机推荐
- iOS Accessibility指南
开发者经常会为用户开发一些令人充满惊喜的App.但是,开发者真的为每一个潜在的用户都做适配了么?是否每个人都可以真正使用你的APP呢? 设计APP.产品或者任何类型的服务,都要考虑到所有用户,包括视力 ...
- Python开发【第一篇】基础题目一
1.求1-2+3-4+5.....99的所有数的和 n = 1 s = 0 while n<100: temp = n%2 if temp == 0: #偶数 s = s-n else: s = ...
- Kubernetes - kubectl proxy
最近在玩flink部署在k8s上,但是k8s以前没玩过,参照前几天写的文章可部署一个简单的k8shttps://www.cnblogs.com/felixzh/p/9726244.html 在参照fl ...
- Mac进行 usr/bin 目录下修改权限问题,operation not permitted
一般情况下我们在使用mac系统过程中下载一些文件.新建一些项目之后,这些文件都会默认是只读状态,这时我们只需要简单的一句权限设置命令就可以解决 你要修改文件上层目录的路径 但是我们在对 usr/bin ...
- enex 转 md 格式的几种方式(免费版/氪金版)
因为最近有读者投稿,用的是印象笔记,文件格式为 .enex ,一般发文章都用 markdown 格式,这叫我好生苦恼,于是乎,Google 搜了一下,找到了如下解决办法. 氪金版: 我只找到了一款比较 ...
- Java中,尽量相信自己,使用自己写的方法,不要使用底层提供的方法。都是坑。
Date转LocalDate时,调用toInstant()报UnsupportedOperationException异常. https://www.jianshu.com/p/11d8ed48f7a ...
- vue router mode模式在webpack 打包上线问题
vue-router mode模式有两种 hash和history. 1.hash —— 即地址栏 URL 中的 # 符号.比如这个 URL:http://www.abc.com/#/hello,ha ...
- [Alpha阶段]第一次Scrum Meeting
Scrum Meeting博客目录 [Alpha阶段]第一次Scrum Meeting 基本信息 名称 时间 地点 时长 第一次Scrum Meeting 19/04/01 大运村寝室6楼 40min ...
- php面向对象之构造函数作用与方法
什么是构造函数呢?构造函数又有什么作用呢? 构造函数 ,是一种特殊的方法.主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中.特别的一个类可以有多个 ...
- setData优化过程
https://blog.csdn.net/rolan1993/article/details/88106343 在做一个小球跟随手指移动的效果时候,由于在touchmove事件中频繁调用setDat ...