错误:传递给持久化的分离实体 – 尝试持久化复杂数据(Play-Framework)

我有一个问题,通过播放框架持久化数据。 也许不可能取得这样的结果,但如果它能起作用那将是非常好的。

简单 :我有一个复杂的模型(Shop with Addresses),我想立即用地址更改商店并以相同的方式存储它们(shop.save())。 但是detached entity passed to persist的错误detached entity passed to persist发生了。

Udate历史 05.11

  • 05.11

    • 使用属性mappedBy="shop"更新Model Shop
    • 更新谷歌用户组的链接
  • 09.11

    • 找到一个解决方法,但它不是通用的
  • 16.11

    • 更新示例html表单,感谢@Pavel
    • 将解决方法(更新09.11)更新为通用方法,感谢@ mericano1
  • 21.11
    • 我放弃了试图寻找解决方案并等待播放2.0 …

Dateil :我尝试将问题减少到最低限度:
型号

 @Entity public class Shop extends Model { @Required(message = "Shopname is required") public String shopname; @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="shop") public List
addresses; }

 @Entity public class Address extends Model { @Required public String location; @ManyToOne public Shop shop; } 

现在我的前端代码

 #{extends 'main.html' /} #{form @save(shop?.id)}  #{field 'shop.shopname'}   #{/field} Addressen #{list items: shop.addresses, as: "address"}    #{/list}  #{/form} 

我只有商店本身的ID和通过POST提供的商店名称: ?shop.shopname=foo

interssting部分是地址列表,我有地址ID和位置,结果将是somthin,如: ?shop.shopname=foo&shop.addresses[0].id=1&shop.addresses[0].location=bar

现在数据的控制器部分:

 public class Shops extends CRUD { public static void form(Long id) { if (id != null) { Shop shop = Shop.findById(id); render(shop); } render(); } public static void save(Long id, Shop shop) { // set owner manually (dont edit from FE) User user = User.find("byEmail", Security.connected()).first(); shop.owner = user; // Validate validation.valid(shop); if (validation.hasErrors()) render("@form", shop); shop.save(); index(); } 

现在的问题是 :当我更改地址数据时,代码到达shop.save(); 对象商店充满了所有数据,一切看起来都很好,但是当hibernate尝试持久化数据时, detached entity passed to persist的错误detached entity passed to persist发生:(

我试图改变获取模式,cascadetype,我也试过:

 Shop shop1 = shop.merge(); shop1.save(); 

不幸的是,没有任何工作,无论是错误发生,还是不会存储地址数据。 有没有办法以这种方式存储数据?

如果有什么不清楚请写信给我,我很乐意提供尽可能多的信息。

更新1我也将问题放在谷歌用户组

更新2 + 3在用户组的帮助下(感谢bryan w。)和mericano1的答案,我发现了一个通用的解决方法。

首先,您必须从shop.class中的属性addresses中删除cascade=CascadeType.ALL 。 然后你必须在shops.class中更改方法save

 public static void save(Long id, Shop shop) { // set owner manually (dont edit from FE) User user = User.find("byEmail", Security.connected()).first(); shop.owner = user; // store complex data within shop storeData(shop.addresses, "shop.addresses"); storeData(shop.links, "shop.links"); // Validate validation.valid(shop); if (validation.hasErrors()) render("@form", shop); shop.save(); index(); } 

存储数据的通用方法如下所示:

 private static  void storeData(List list, String parameterName) { for(int i=0; i<list.size(); i++) { T relation = list.get(i); if (relation == null) continue; if (relation.id != null) { relation = (T)Model.Manager.factoryFor(relation.getClass()).findById(relation.id); StringBuffer buf = new StringBuffer(parameterName); buf.append('[').append(i).append(']'); Binder.bind(relation, buf.toString(), request.params.all()); } // try to set bidiritional relation (you need an interface or smth) //relation.shop = shop; relation.save(); } } 

我在Shop.class中添加了一个链接列表,但我不会更新其他代码片段,因此如果发生编译错误则会收到警告。

当您在Hibernate中更新复杂实例时,您需要确保它来自数据库(首先获取它,然后更新同一个实例)以避免这种“分离实例”问题。

我通常更喜欢始终先获取,然后只更新我期望从UI中获取的特定字段。

您可以使用更加通用的代码

 (T)Model.Manager.factoryFor(relation.getClass()).findById(relation.id); 

不确定这是答案,因为我不知道Play,但Shop-Address双向关联不正确:必须使用@OneToMany(mappedBy="shop", ...)将商店一侧标记为另一方的反面。 @OneToMany(mappedBy="shop", ...)

此外,如果分别savemerge corrspond到Session.saveSession.merge ,合并后执行保存是没有意义的。 保存用于将新的瞬态实体插入到会话中。 如果已调用merge,则在调用save它已经持久化。

这不会让你开心。 我已经纠结了相同的错误,即将在SO上提出相同的问题并看到了你的问题。 这件事不起作用,这是一个主要的错误。 在我发现的文档中“因为在大对象图上显式调用save()可能很繁琐,save()调用会自动级联到用cascade = CascadeType.ALL属性注释的关系。” 但它根本行不通。

我甚至调试了SQL,你和(和我)的关联发生了什么,它们被删除并重新关联到父级,但它们永远不会用新值更新。 它是这样的:

 //Here's it's updating a LaundryList where I've modified the address and one of the laundry items //it first updates the simple values on the LaundryList: update LaundryList set address=?, washDate=? where id=? binding parameter [1] as [VARCHAR] - 123 bong st2 binding parameter [2] as [DATE] - Fri Mar 11 00:00:00 CET 2011 binding parameter [3] as [BIGINT] - 413 //then it deletes the older LaundryItem: delete from LaundryList_LaundryItem where LaundryList_id=? binding parameter [1] as [BIGINT] - 413 binding parameter [2] as [BIGINT] - 407 //here it's associating the laundry list to a different laundry item insert into LaundryList_LaundryItem (LaundryList_id, laundryItems_id) values (?, ?) binding parameter [1] as [BIGINT] - 413 binding parameter [2] as [BIGINT] - 408 //so where did you issue the SQL that adds the updated values to the associated LaundryItem?? 

我看到了你的解决方法,我真诚地感谢你的努力,以及你煞费苦心地发布它的事实(因为它会帮助那些坚持这个的人),但这违背了快速开发和ORM的目的。 如果我被迫做一些通常应该是自动的操作(或者甚至不需要,为什么它会删除旧的配置并将父级关联到一个新的,而不是简单地一次性更新该关联?)那么它并不是真的“快速”也不是有效的“对象关系映射”。

就我而言,他们采用了完美的工作框架(JPA),并通过修改其行为将其变为无用的东西。 在这种情况下,这反映了这样一个事实:JPA的merge调用不再符合它的预期,他们告诉你他们已经添加了他们自己的方法,称为“ save ”,这有助于(为什么???)那个东西没有按照他们网站上的文档和示例中描述的方式工作(在我发布的这个问题中更多关于此问题。

更新:

嗯,这也是我的解决方法:

我现在简单地忽略了将更新的关联的ID发送到控制器,这样Play会认为它们是要添加到数据库的新实体,并且在其父级上调用merge(…)和save()时实体all daya将被正确保存。 然而,这会给您带来另一个错误:从现在开始,每次修改某些关联并保存父关联时,这些关联都被视为要创建的新实体(它们具有id = null),因此旧的实体在其父项中是孤立的。保存所有这些,要么在数据库中留下一大堆孤立的,无用的实体,要么强迫您编写更多的解决方法代码以清除您要保存的父实体上的孤立关联实体。

更新2:

在这一点上,我认为最好等待Play 2.0,它目前正处于测试阶段,并将很快推出。 这并不太酷,因为引用monsieur Bort(来自内存)“你将无法直接将Play 1.x项目迁移到Play 2,但是一些轻量代码复制粘贴应该足以传输它们”。 复制/粘贴您的代码以获得胜利! 并考虑其他框架制造商花费多少时间使他们的新产品版本向后兼容! 无论如何,在他们引入Play 2.0路线图的文章中 ,他们说他们将用其他一些ORM框架取代Hibernate / JPA,同时承认他们已经攻击了Hibernate完成的标准JPA实现,以便实现……好吧,我们都看到了所取得的成就。 这是引用:

“今天,Play Java应用程序访问SQL数据库的首选方式是由Hibernate提供支持的Play模型库。但是,因为在像Play这样的无状态Web框架中管理有状态实体(例如官方中定义的实体)很烦人。在JPA规范中,我们提供了一种特殊的JPA风格,使事物尽可能无状态。这迫使我们以一种长期可能无法持续的方式破解Hibernate。为了解决这个问题,我们计划转向现有的无国籍的JPA称为EBean。“

这可能是潜在的好消息,因为新的ORM似乎更适合他们的要求,并且更有可能避免他们现在的坏事。 祝他们好运。

根据Play文档,您应该提供如下的查询字符串:

 ?shop.addresses[0].id=123 &shop.addresses[1].id=456 &shop.addresses[2].id=789 

我不确定你是否正确提供。 尝试这个: