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 合并到数据库的主要内容,如果未能解决你的问题,请参考以下文章