JPA 模式:从实体生成数据传输对象 DTO 并将 DTO 合并到数据库

Posted

技术标签:

【中文标题】JPA 模式:从实体生成数据传输对象 DTO 并将 DTO 合并到数据库【英文标题】:Pattern for JPA: Generating Data Transfer Object DTO from Entity and merging DTO to database 【发布时间】:2014-01-25 17:45:04 【问题描述】:

我正在寻找一种从 JPA 实体创建数据传输对象 (DTO) 的好方法,反之亦然。 我想将 DTO 作为 JSON 发送到客户端,然后接收修改后的 DTO 并将其保存回数据库。 在将接收到的对象从 JSON 解析到其 Java 类之后,从 EntityManager 对接收到的对象执行合并方法将是最容易的。

例如有如下Entity和Rest方法用于保存修改后的对象:


@Entity
@Table(name="CUSTOMER")
public class Customer 
    @Id
    Long id;
    @Version
    Long version;
    String name;
    String address;
    String login;
    String password;
    String creditCardNumber;
    @OneToMany(cascade = CascadeType.ALL)
    List<Foo> fooList;

    ... Getter() and Setter()


private EntityManager em;
@POST
@Path("/saveCustomer")
public void saveCustomer ( Customer  customer)                  
   em.merge(customer);
   return;
  

只要我将整个实体类作为 JSON 发送并接收整个实体,这就可以正常工作。然后 EntityManager 会将修改后的对象合并到数据库中。但是当我只想提供实体的一个子集(比如只提供客户的姓名和地址)时,就会出现问题:

    创建实体子集的最佳方法是什么?

        - 手动为实体编写 DTO?这将为实体的每个子集生成重复代码,必须对其进行维护。

    如何将作为实体子集的 DTO 合并回数据库?

        - 使用 EntityManager 的 merge() 方法不起作用。起初,DTO 不是实体,因此无法合并。并且只是从 DTO 创建一个实体,将在实体中有一些未设置的值。合并后,数据库中的值将为 NULL。


我想出的一个想法是,为我想要为实体拥有的每个子集指定额外的实体。 (就像数据库视图)这将是重复的代码,但它可以解决将 DTO 合并到数据库的问题。 (也许这段代码可以自动生成)

例如,实体 CustomerView1 链接到与 Customer 类相同的表,但仅提供客户的姓名和地址。它是真实 Customer 类的 DTO,可以作为 JSON 发送并在服务器外部进行修改。然后这个类也可以通过 EntityManager 合并到数据库中。

@Entity
@Table(name="CUSTOMER")
public class CustomerView1 
    @Id
    Long id;
    @Version
    Long version;
    String name;
    String address;
    
        ... Getter() and Setter()
    

但是我对这个解决方案有疑问,我不知道这是否会混淆 JPA 的实体缓存并可能导致一些问题。


我的问题是,是否有一种模式可以解决 DTO 的代码重复并将 DTO 合并回数据库?

或者是否有为此目的的图书馆? - 诸如自动生成 DTO 并将 DTO 复制回真实实体,以便将它们与 EntityManager 合并成为可能。

【问题讨论】:

为了节省一些代码行可以使用apache commons-BeanUtils库。看看这里commons.apache.org/proper/commons-beanutils/apidocs/org/apache/… 它有实用方法,可以通过反射将属性从 / 复制到 pojo 【参考方案1】:

如果实体和DTO的大小差异不大,可以选择发送实体。

在使用 DTO 时,要克服像 lost update 这样的并发问题,您必须将实体版本合并到您的 DTO 中。

如果您不使用实体版本,并且在 REST GET 和 PUT 方法之间更改了底层行,您将覆盖最终用户并未真正意识到的更改。

每当我必须更改实体(创建、更新、删除)时,我都依赖JPA and Hibernate Optimistic Locking mechanism。

对于 UI 列表、表格、搜索结果 DTO 是一个可行的选择,因为您只对原始实体的投影感兴趣。这样可以加快检索速度,并且可以从 JPA 不支持的其他 SQL 功能(窗口函数)中受益。

【讨论】:

有时,发送整个实体并将其合并回来可能很危险。想象一个客户实体,它具有诸如 creationDate 或 login 之类的字段,这些字段不会被该特定屏幕修改(​​但它们并未在实体上定义为只读)。如果整个实体作为 JSON 发送,然后接收回来并合并(),你怎么能保证只更改了相应的字段?在这种情况下(我认为这很常见),带有实体“视图”的原始解决方案似乎更合适,不是吗? @Version 可以防止更新丢失,不是吗? 您可以在这些属性上设置 updatable=false【参考方案2】:

查看直接解决您的问题的值对象设计模式。

本教程很好地介绍了值对象。

http://www.javastuff.in/2012/04/value-object-pattern.html

【讨论】:

值对象通常意味着链接文章中描述的其他内容。虽然这些术语没有编纂,但最好遵循主流。例如。见adam-bien.com/roller/abien/entry/value_object_vs_data_transfer 感谢您的评论 +1【参考方案3】:

听起来您所描述的正是Blaze-Persistence Entity Views 的用途。当前版本仅支持创建读取模型,即您发送给客户端的模型,但写入模型部分已接近完成。实体视图在接口/抽象类 DTO 表示和实体模型之间映射。该库尽可能利用映射信息来实现各种性能优化。

【讨论】:

以上是关于JPA 模式:从实体生成数据传输对象 DTO 并将 DTO 合并到数据库的主要内容,如果未能解决你的问题,请参考以下文章

具有业务对象、DTO 和实体/域对象的数据转换模式

从 JPA 注释的实体类自动生成数据模式

是否有任何工具可以从实体生成 DTO?

Spring Data JPA 原生查询结果实体

将 DTO 转换为实体,反之亦然

Spring Boot,决定为 REST 和 JPA 分别创建 DTO 对象