【问题标题】:What's the point of having DTO object when you have the same object as POJO (Entity)?当您拥有与 POJO(实体)相同的对象时,拥有 DTO 对象有什么意义?
【发布时间】:2020-11-16 07:37:03
【问题描述】:

我想了解当您已经拥有 POJO 对象(作为实体)时创建 DTO 对象有什么好处。

在我的项目中,我有两个:

  • DTO 类用于在 Web 服务和应用程序之间进行通信
  • POJO 实体类 (JPA) 用于数据库和应用程序之间的通信

如果我查看一个 DTO 对象类(我们称之为 MyObjDTO)和同一个类,但 POJO 端(我们称之为 MyObjPOJO)除了 MyObjPOJO 作为注释之外没有任何区别,因为它是一个 @Entity。

所以事实上,我的项目中有 2 个类看起来相同(相同的属性、相同的方法)但用于不同的目的。

IMO,在这种情况下,DTO 类是无用的,并且会增加应用程序的复杂性,因为我对 DTO 类所做的所有事情都可以使用我的 POJO 类来完成,此外,对于单一类型的对象,我必须维护至少 2 个类( DTO 和 POJO),例如,如果我添加一个属性,我必须在两个类中添加这个属性。

我不是专家,我在质疑我的想法;你怎么看?

【问题讨论】:

  • 你不是第一个to ask。看起来像另一个软件意识形态游乐场。我个人使用过一次 DTO,因为商店总是使用它们,但在这种情况下,它们的结构也与实体不同。
  • Google diff b/w POJO 和 DTO

标签: java pojo dto


【解决方案1】:

此答案是stack exchange 上的内容的复制品。恕我直言,应该关闭 OP 以在错误的论坛上发布。它目前也吸引了固执己见的答案,尽管不一定如此,并且与 Java 没有任何特定的联系。

DTO 是一种模式,它独立于实现 (POJO/POCO)。 DTO 说,由于对任何远程接口的每次调用都很昂贵,因此对每次调用的响应都应该带来尽可能多的数据。因此,如果需要多个请求为特定任务带来数据,则可以将要带来的数据组合在一个 DTO 中,以便只有一个请求可以带来所有需要的数据。 Patterns of Enterprise Application Architecture的目录有更多细节。

DTO 是一个基本概念,并不过时。

有点过时的是让 DTO 完全不包含任何逻辑的概念,仅用于传输数据并在传输到客户端之前从域对象“映射”,并在将它们传递给视图模型之前映射到视图模型显示层。在简单的应用中,领域对象往往可以直接作为DTO复用,直接传递到展示层,这样就只有一个统一的数据模型。对于更复杂的应用程序,您不想将整个域模型公开给客户端,因此需要从域模型到 DTO 的映射。拥有一个从 DTO 复制数据的单独视图模型几乎没有任何意义。

然而,这个概念已经过时而不是完全错误的原因是一些(主要是较旧的)框架/技术需要它,因为它们的域和视图模型不是 POJOS,而是直接与框架相关联。

最值得注意的是,在 EJB 3 标准之前的 J2EE 中的实体 Bean 不是 POJO,而是由应用服务器构造的代理对象 - 根本不可能将它们发送到客户端,因此您别无选择单独的 DTO 层 - 这是强制性的。

虽然 DTO 不是一个过时的模式,但它经常被不必要地应用,这可能使它显得过时。

来自 Java 大师 Adam Bien:

Java Enterprise 社区中被滥用最多的模式是 DTO。 DTO 被明确定义为分配问题的解决方案。 DTO 原本是一个粗粒度的数据容器,可以在进程(层)之间有效地传输数据。 ~ 亚当·比恩

来自马丁·福勒:

DTO 被称为数据传输对象,因为它们的全部目的是在昂贵的远程调用中传输数据。它们是实现远程接口性能所需的粗粒度接口的一部分。您不仅在本地环境中不需要它们,而且它们实际上是有害的,因为粗粒度 API 更难使用,而且您必须完成将数据从域或数据源层移动到 DTO 的所有工作。 ~ 马丁·福勒

这是一个 Java EE 特定示例,说明了 DTO 模式的常见但不正确使用。如果您对 Java EE 不熟悉,只需了解 MVC 模式即可:“JSF ManagedBean”是 View 使用的类,“JPA Entity”是 MVC 模式中的 Model。

例如,假设您有一个 JSF ManagedBean。一个常见的问题是 bean 是应该直接持有对 JPA 实体的引用,还是应该维护对稍后转换为实体的中间对象的引用。我听说这个中间对象被称为 DTO,但是如果您的 ManagedBeans 和实体在同一个 JVM 中运行,那么使用 DTO 模式几乎没有什么好处。

此外,请考虑 Bean Validation 注解(同样,如果您不熟悉 Java EE,请知道 Bean Validation 是一种用于验证数据的 API)。您的 JPA 实体可能使用 @NotNull 和 @Size 验证进行注释。如果您使用的是 DTO,您需要在 DTO 中重复这些验证,以便使用您的远程接口的客户端不需要发送消息来发现他们未通过基本验证。想象一下在 DTO 和实体之间复制 Bean Validation 注释的所有额外工作,但是如果您的视图和实体在同一个 JVM 中运行,则无需承担这些额外工作:只需使用实体即可。

Catalog of Patterns of Enterprise Application Architecture 提供了 DTO 的简明解释,以下是我发现的更多参考资料:

【讨论】:

    【解决方案2】:

    其中大部分归结为清洁架构和关注点分离

    我对实体的最大用例是,我不会在 DTO 中乱扔运行时变量或为方便而添加的方法(例如显示名称/值或后计算值)

    如果它是一个非常简单的实体,那么没什么大不了的,但是如果你对 Clean 非常严格,那么就会出现很多冗余模型(DTO、DBO、Entity)

    这确实是您希望对严格的清洁架构投入多少的偏好

    https://medium.com/android-dev-hacks/detailed-guide-on-android-clean-architecture-9eab262a9011

    【讨论】:

    • 谢谢你的回答亲爱的,但它看起来像干净的架构 VS 简单的代码。我想认为它更深。就我而言,我认为 DTO 是在没有考虑的情况下实施的;开发人员认为“DTO 是传输对象,然后我正在制作 DTO”,而不是认为他可以使用 POJO 类代替 DTO(至少对我而言)
    • 不,它没有。提供的链接没有说明 DTO。一旦您注意到这一点,您就会认为 DTO 的概念与他们声明的目的不符。并非地球上的所有事情都是为了清洁而进行的,至少对某些人来说,鲍勃·马丁叔叔就是其中之一。顺便说一句,我们在错误的论坛中,应该关闭问题。
    【解决方案3】:

    有一个优势,即使非常小,在您的架构中分离层,并让对象在穿过层时“变形”。此解耦允许您以最小的更改替换软件中的任何层,只需更新 2 个对象和您的所有集合之间的字段映射即可。

    如果 2 个对象具有相同的成员...嗯,那么这就是 Apache Commons BeanUtils.copyProperties() 的用途;)

    【讨论】:

    • 是的,当然,交换层。每两周左右发生一次。
    【解决方案4】:

    其他人已经告诉过你DTO的好处了,这里我就说说如何解决多维护一个DTO版本对象的麻烦。 我开发了一个库beanKnife 来自动生成一个dto。它将创建一个基于原始 pojo 的新类。您可以过滤继承的属性、修改现有属性或添加新属性。您只需要编写一个配置类,该库将为您完成剩下的事情。配置支持继承功能,因此您可以提取通用部分以进一步简化配置。 这是一个例子

    @Entity
    class Pojo1 {
       private int a;
       @OneToMany(mappedBy="b")
       private List<Pojo2> b;
    }
    
    @Entity
    class Pojo2 {
       private String a;
       @ManyToOne()
       private Pojo1 b;
    }
    
    // Include all properties. By default, nothing is included.
    // To change this behaviour, here use a base configuration and all other final configuration will inherit it.
    @PropertiesIncludePattern(".*")
    // By default, the generated class name is the original class name append with "View",
    // This annotation change the behaviour. Now class Pojo1 will generate the class Pojo1Dto
    @ViewGenNameMapper("${name}Dto")
    class BaseConfiguration {
    }
    
    // generate Pojo1Dto, which has a pojo2 info list b instead of pojo2 list
    @ViewOf(value = Pojo1.class)
    class Pojo1DtoConfiguration extends BaseConfiguration {
        private List<Pojo2Info> b;
    }
    
    // generate Pojo1Info, which exclude b
    @ViewOf(value = Pojo1.class, genName="Pojo1Info", excludes = "b")
    class Pojo1InfoConfiguration extends BaseConfiguration {}
    
    // generate Pojo2Dto, which has a pojo1 info b instead of pojo1
    @ViewOf(value = Pojo2.class)
    class Pojo2DtoConfiguration extends BaseConfiguration {
        private Pojo1Info b;
    }
    
    // generate Pojo2Info, which exclude b
    @ViewOf(value = Pojo2.class, genName="Pojo2Info", excludes = "b")
    class Pojo2InfoConfiguration extends BaseConfiguration {}
    

    会生成

    class Pojo1Dto {
       private int a;
       private List<Pojo2Info> b;
    }
    
    class Pojo1Info {
       private int a;
    }
    
    class Pojo2Dto {
       private String a;
       private Pojo1Info b;
    }
    
    class Pojo2Info {
       private String a;
    }
    

    那就这样用吧

    Pojo1 pojo1 = ...
    Pojo1Dto pojo1Dto = Pojo1Dto.read(pojo1);
    Pojo2 pojo2 = ...
    Pojo2Dto pojo2Dto = Pojo2Dto.read(pojo2);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-08-10
      • 2017-07-16
      • 1970-01-01
      • 2012-07-01
      • 2011-10-04
      • 1970-01-01
      • 2018-02-23
      • 1970-01-01
      相关资源
      最近更新 更多