【问题标题】:GSON + Hibernate: Identical objects causing "An entity copy was already assigned to a different entity"GSON + Hibernate:导致“实体副本已分配给不同实体”的相同对象
【发布时间】:2013-11-22 14:07:34
【问题描述】:

我有以下 JSON:

{
    id: 123,
    subObjects: [
        {
            id: 564,
            name: "foo",
            contry: {
                id: 1,
                name: "Germany"
            }
        },
        {
            id: 777,
            name: "bar",
            contry: {
                id: 1,
                name: "Germany"
            }
        }
    ]   
}

并使用 Gson 对其进行反序列化。之后我需要合并 JPA 实体:

Model model = new Gson().fromJson(json, modelClass);
model = entityManager.merge(model)

刷新从模型级联到子对象,再到国家。 这会导致 Hibernate 出现异常“实体副本已分配给不同的实体”。

如果我使用不同的国家,它会起作用。 如果我使用将国家/地区实例从一个对象复制到另一个对象,则它可以工作,因此两个子对象都引用该国家的同一个实例。

这两个国家的价值观相同。两者也具有相同的 hashCode。 这两个国家是平等的,但不是==,因为它们是不同的实例。

this question 上列出的提示对我没有帮助。

我正在使用 Hibernate 4.1.3 Final 和 Gson 2.2

java.lang.IllegalStateException: An entity copy was already assigned to a different entity.
    at org.hibernate.event.internal.EventCache.put(EventCache.java:184)
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:285)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
    at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:896)
    at org.hibernate.engine.spi.CascadingAction$6.cascade(CascadingAction.java:288)
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:380)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:165)
    at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:439)
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:308)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
    at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:896)
    at org.hibernate.engine.spi.CascadingAction$6.cascade(CascadingAction.java:288)
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:380)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
    at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:409)
    at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:350)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:326)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:165)
    at org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:439)
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:308)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)
    at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:904)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:888)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:892)
    at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:874)
    at play.db.jpa.GenericModel.merge(GenericModel.java:234)
    [...]

如何以一种通用的方式解决这个问题,我不需要知道对象类型以及哪些对象在逻辑上是相同的?

【问题讨论】:

  • @AndreiI 升级不是一个选项,因为我使用的框架(播放 1.2.x)需要特定版本,因为它们使用了补丁。我真的需要以通用方式解决它,因为我有不同实体的相同案例,是的,我需要将大对象从网络服务器传输到客户端的 mvc-javascript-framework (AngularJS) 并将它们存储回服务器。这些对象代表复杂的公司数据。
  • 你有没有想过,当两个ID相同但名字不同的国家出现时应该是什么行为?因为这个问题本身并不容易回答,所以我建议您不要接受界面中的所有内容,而是使用加载 ID 的 DTO。此外,您可能过于信任用户界面。只需考虑添加一个 ID 不存在的国家或一个不允许在该界面中使用的国家。这意味着您必须验证数据,而不是简单地将其合并到数据库中。
  • 此应用程序的用户是 100% 受信任的管理用户,没有以不寻常方式修改数据所需的知识,但是是的,需要验证。在我的情况下,他们不需要修改国家,但他们需要选择正确的国家。其他情况可能要经过编译才能实现。对于我的情况,获取该对象中的国家的 ID 可能就足够了,而休眠可以完成其余的工作,但前提是它们是相同的实例。
  • 当不同值存储在具有相同 ID 的对象中时的行为应该引发异常,因为在我的情况下这是无效数据,因此是一个错误的请求。
  • 我认为这样的行为完全超出了 JPA 或 Hibernate 的范围,这就是 Hibernate 会抛出该异常的原因:因为它不知道该做什么。我想说的是,您应该避免在实体图中出现相等的实例,要么干扰 GSON 的级别(它似乎也不支持这样的功能),要么就在合并 DB 中的实体图之前。

标签: hibernate jpa gson


【解决方案1】:

我认为这样的行为完全超出了 JPA 或 Hibernate 的范围,这就是 Hibernate 会抛出该异常的原因:因为它不知道该做什么。我想说的是,您应该避免在实体图中出现相等的实例,要么干扰 GSON 的级别(它似乎也不支持这样的功能),要么就在合并 DB 中的实体图之前。

【讨论】:

    【解决方案2】:

    这是我不使用合并的工作解决方案:

    我使用反射在绑定时迭代字段。我检查该字段是实体还是实体集合,然后如果 json 中提供了 id,则调用 findByID,如果不获取附加实体,则创建实体的新实例。 之后,我通过反射将 json 中存在的字段值复制到附加的实体中。 我递归地处理实体之间的关系。 之后,我有了一个完全合并和附加的实体版本,其中包含所有附加和合并的子对象。 我可以毫无问题地保存它

    重要提示:

    Andrei 我提出了很多可能发生的逻辑问题,并更改了 api 以获取完整的国家/地区,但只是他们的 ID 会更清楚。我更喜欢这个,并且将来会这样做。 因此安德烈我赢得了赏金。

    【讨论】:

      【解决方案3】:

      您是否尝试过使用clear 方法分离所有实体?我认为如果您分离所有实体,您将能够执行merge

      【讨论】:

      • 当从单个 json 一次反序列化所有对象时,它们都应该被分离,不是吗?
      • 在我的情况下有所帮助,但我正在通过 junit 测试工作
      【解决方案4】:

      有人在 Hibernate Jira 中打开了一张票,以便在发生此错误时添加更多信息

      HHH-7605

      在 4.2.x 版本中添加了更改,您可以查看 Hibernate 的 Git 上的提交日志以获取 EventCache 的更改和 EventCacheTest 以重现问题

      HHH-7605 Event cache descriptive log messages

      我希望这可以帮助您找到问题。

      【讨论】:

      • 谢谢,但升级休眠不是一个选项。我正在使用的框架(play 1.2.x)使用​​了一个补丁版本的hibernate,我不能简单地使用另一个hibernate版本。
      • 一位推土机映射器 Git 用户解决了这个问题,将 @Transactional 注释添加到他的测试类 github.com/DozerMapper/dozer/issues/109
      • 哦,不,我并不是建议您必须升级 Hibernate,但您可以查看测试代码以确定真正的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-15
      • 1970-01-01
      • 2016-08-24
      • 1970-01-01
      相关资源
      最近更新 更多