当我们谈论Java应用程序的开发时,数据传输对象(被亲切地称为DTOs)是许多讨论的主题。DDO诞生于EJB 2的Java世界,有两个目的。
首先,规避EJB序列化问题;其次,它们隐式定义一个程序集阶段,其中将用于表示的所有数据在实际进入表示层之前都会进行封送。由于 EJB 不再大规模使用,因此是否可以丢弃 DT?本文的目的是谈谈 DTO 的有用性并解决此问题。
毕竟,在几个新主题(例如,云和微服务)的环境中,此层是否有意义?当涉及到良好的软件体系结构时,答案几乎一致:这取决于您希望实体与可视化层耦合的紧密程度。
思考层中的基础架构,并本身分为三个相互关联的部分,我们有著名的MVC。
值得注意的是,此策略并非仅限于 Spring MVC 和 JSF 等 Web 应用程序堆栈,使用 JSON 在宁静的应用程序中公开数据,JSON 数据用作可视化效果,即使它对典型用户不友好。
简要说明一下 MVC 后,我们将讨论使用 DTO 的优缺点。从分层应用的思维来看,DTO首先的目标是将模型与视图分开。思考 DTO 的问题:
- 增加复杂性
- 有可能重复代码
- 添加新图层会影响延迟层,即可能的性能损失。
在不需要丰富模型作为前提的简单系统中,不使用 DTO 最终为应用程序带来巨大好处。有趣的是,许多序列化框架最终迫使属性具有访问器或 getter 和 setter 方法,这些方法始终作为强制性的和公开的,因此,在某些时候,这将对应用程序封装和安全性产生影响。
另一个选项是添加 DTO 层,这基本上保证了视图和模型的分离,如前所述是的,各种框架中有几个注释指示哪些字段不会显示。但是,如果您忘记写下来,可能会意外导出关键字段,例如,用户的密码。
便于以对象方向绘制。干净代码明确说明对象方向的一点是 OOP 隐藏数据以公开行为,封装有助于此。
便于更新数据库。通常必须重构、迁移数据库而不此更改影响客户。这种分离有助于优化,修改数据库,而不会影响可视化效果。
版本控制、向后兼容性是一个重要点,尤其是在具有供公众使用的 API 和多个客户时,因此可以为每个版本配备一个 DTO,并无需担心地发展业务模式。
另一个好处是,使用丰富的模型和创建一个 API 是项目符号批准的易用性。例如,在我的模型中,我可以使用货币 API;但是,在我的可视化层中,我导出为一个简单的对象,仅具有可视化的货币价值。这是Java中正确的旧字符串。
CQRS。是的,命令查询责任分离是否有关分离写入和读取数据的责任以及如何在没有 DTO 的情况下执行此操作?
通常,添加图层意味着分离和促进维护,而牺牲了添加更多类和复杂性,因为我们还必须考虑这些层之间的转换操作。例如,这是 MVC 存在的原因,因此了解一切都基于影响和权衡,或者在特定应用程序或情况下受到伤害,这一点非常重要。
缺少这些层是非常坏的,它可能导致一个高地模式(只有一个)有一个类的所有责任。同样,多余的层成为洋葱模式,开发人员在通过每个层时会哭。
DTO 中更频繁的批评是执行转换。好消息是,有几个转换框架,也就是说,没有必要手动进行更改。在本文中,我们将选择一个模型映射器。
第一步是定义项目的依赖项,例如,在 Maven 中:
<依赖>
<组 Id>org.modelmapper</组 Id>
<工件Id>模型映射器<artifactId>
</版本>5</依赖项 >
6为了说明 DTO 的概念,我们将使用连接到 MongoDB 的 JAX-RS 创建应用程序,所有这些都归功于雅加达 EE,使用 Payara 作为服务器。我们使用用户名、工资、生日和用户可以讲的语言列表来管理用户。由于我们将在雅加达 EE 与 MongoDB 合作,我们将使用雅加达 NoSQL。
Java
xxxxxxx
1351nosql.映射.列;2进口雅加达。nosql.映射.转换;
3进口雅加达。nosql.映射.实体;
4进口雅加达。nosql.映射.ID;
5导入我的。公司。基础设施。货币货币属性转换器;
67导入javax。钱。货币量;
8时间。本地日期;9导入java。乌蒂尔.集合;
10导入java。乌蒂尔.列表;
11导入java。乌蒂尔.地图;
12导入java。乌蒂尔.对象;
1314
15公共类用户|
1617私有字符串昵称;1920
21货币货币转换)。类)
22私人货币金额工资;
2324
25私人列表<字符串>语言;
2627私人本地日期生日;2930
31私人地图<字符串String>设置;
3233只有getter
34}
35通常,实体具有所有属性的获取者和设置器是没有意义的;毕竟,这将是相同的,离开属性直接公共对于我们的 DTO,我们将具有实体的所有字段;但是,对于可视化,我们的"货币量"将是一个"字符串",周年日期将遵循相同的行。
Java
xxxxxxx
1181导入java。乌蒂尔.列表;
2乌蒂尔.地图;34公共类用户DTO |
56私人字符串昵称;
78私人字符串工资;
910私人列表<字符串>语言;
1112私人地图<字符串,字符串> 设置;1516//获取器和设置器
17}
18映射器最大的好处是,我们不必担心手动执行此操作。唯一需要注意的是,特定类型,例如货币 API 的"货币量",将需要创建一个转换,成为"String",反之亦然。
Java
xxxxxxx
1321导入组织。模型映射器。摘要 转换器;
23导入javax
货币量;
45公共类货币字符串转换器扩展抽象转换器<货币金额字符串> |
67
8受保护的字符串转换(货币金额源) |
9如果(源=空) |
10返回null;
11返回源。到弦();13}
14}
151617导入组织。贾瓦钱.莫内塔.钱;
18导入组织。模型映射器。摘要 转换器;
1920导入javax。钱
1px;"•公共类字符串货币转换器扩展抽象转换器<字符串,货币金额> |
2324
25受保护的货币量转换(字符串源) |
26如果(源=空) |
27返回null;
28}
29解析(源);30}
31}
32转换器已准备就绪;我们的下一步是实例化执行"ModelMapper"转换的类,使用依赖项注入的一个要点是我们可以将其定义为应用程序级别。从现在起,整个应用程序可以使用相同的映射器;为此,只需使用注释"注入",我们将向前看。
Java
xxxxxxx
1351模型映射器。模型映射器;23导入javax。注释.后构造;
4导入javax。企业。上下文。应用程序范围;
5导入javax。企业。注入。正在生产;
6导入java。乌蒂尔.函数.供应商;
78导入静态组织
配置.配置.访问级别。私人;
910
11公共类地图制作人实现供应商<模型映射器> |
1213私有模型映射器映射器;
1415
16这个。映射器 = 新模型映射器();18这个。映射器。获取配置()
19.设置场匹配启用(true)
20.设置场访问级别(私人);
21这个。映射器。添加转换器(新字符串货币货币转换器());
22这个。映射器。加比:(新货币字符串转换器());
23映射器。添加转换器(新字符串本地数据转换器());24这个。映射器。添加转换器(新的本地日期字符串转换器());
25这个。映射器。添加转换器(新用户DTO转换器());
26}
272829
30
31返回映射器;33}
34}
35使用 Jakarta NoSQL 的一个显著优势是易于集成数据库。例如,在本文中,我们将使用存储库的概念,从中我们将创建一个接口,雅加达 NoSQL 将为其处理此实现。
Java
xxxxxxx
1111nosql.映射.存储库;23导入javax。企业。上下文。应用程序范围;
4导入java。乌蒂尔.流。流;
56
7公共接口用户存储库扩展存储库<用户字符串> |
8}
1011在最后一步中,我们将与 JAX-RS 发出呼吁。关键是数据公开全部由 DTO 完成;也就是说,由于 DTO,可以在客户不知情的情况下在实体内进行任何修改。如前所述,映射器被注入,"映射"方法极大地促进了 DTO 和实体之间的集成,而无需为此提供多少代码。
Java
xxxxxxx
1571注入。注射;2导入javax。ws.rs.消耗;
3导入javax。ws.rs.删除;
4导入javax。ws.rs.获取;
5导入javax。ws.rs.帖子;
6导入javax。ws.rs.路径;
7导入javax。ws.rs
1px;"*导入 javax。ws.rs.正在生产;
9导入javax。ws.rs.网络应用程序例外;
10导入javax。ws.rs.核心。媒体类型;
11导入javax。ws.rs.核心。回应;
12导入java。乌蒂尔.列表;
13导入java。乌蒂尔.流。收藏家;
14乌蒂尔.流。流;1516"用户") (
17媒体类型)。APPLICATION_JSON)
18媒体类型)。APPLICATION_JSON)
19公共类用户资源|
2021
2225私有模型映射器映射器;
2627
28公开列表<用户DTO>获取所有() |
29流<用户>用户=存储库。查找所有();
30返回用户。地图(u->映射器.地图(uUserDTO)
1px;"> .收藏(收藏家)。列表());
32}
3334
35公共空位插入(用户 DTOdto) |
36用户映射=映射器。地图(dto用户)。类;
37存储库.保存(地图);
383942"id") (
43公共无效更新( ("id"字符串id用户DTOdto) |
44用户user=存储库。findById(ID).或ElseThrow->
45新的Web 应用程序例外(响应.状态.NOT_FOUND));
46用户映射=映射器。地图(dto用户)。类;
47更新(地图);48存储库.保存(地图);
49}
5051
52"id") (
53公共无效删除( ("id"字符串ID) |
54存储库.删除ById(ID);