8

我正在处理我的数据库中一个相当复杂的对象图。我正在使用 XStream 序列化和反序列化这个工作正常的对象图。当我导入数据库中存在的对象的对象图时,它最初是瞬态的,因为没有 ID 并且 hibernate 对此一无所知。然后,我有业务逻辑,通过确定新临时导入的对象映射到现有持久对象中的哪些对象,在我的对象图的某些部分上设置 ID。然后我使用 Hibernate 的 merge() 和 saveOrUpdate()。

一些伪代码可以让您更好地了解我在做什么:

ComplexObject transObj = xstream.import("object.xml");
ComplexObject persistObj = someService.getObjByName(transObj.getName());
for (OtherObject o : c.getObjects()) {
    if (persistObj.getObjects().contains(o.getName())) {
        o.setId(persistObj.getObjectByName(o.getName()).getId())
    }
    ... set a bunch of other IDs deeper in the object graph ...
}

transObj = session.merge(transObj);
session.saveOrUpdate(transObj);

现在这不起作用,因为我收到以下错误:

   org.springframework.dao.InvalidDataAccessApiUsageException: deleted object would be re-saved by cascade (remove deleted object from associations): [com.......SomeObject#353296]; nested exception is org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [com.......SomeObject#353296]

似乎休眠合并并不意味着将瞬态对象与持久对象相关联。

有什么方法可以实现我想要做的事情,而不必在会话中获取持久对象并对其进行修改,而不是修改瞬态对象,并尝试保存并覆盖现有的持久对象?

4

2 回答 2

8

似乎休眠合并并不意味着将瞬态对象与持久对象相关联

合并是 JPA 标准 - 将“新的和分离的实体实例”合并到“(持久性上下文)托管实体实例”。将跨标记为 Cascade.MERGE 或 Cascade.ALL 的 FK 关系级联。

JPA的角度来看 - 不,合并并不是为了将您的 Xstream 流式瞬态对象与持久对象相关联。JPA 打算让合并与正常的 JPA 生命周期一起工作 - 创建新对象、持久化它们、获取/查找它们、分离它们、修改它们(包括添加新对象)、合并它们、选择性地修改它们,然后持久化/保存它们. 这是经过深思熟虑的设计,因此 JPA 是流线型和高性能的 - 每个单独的对象都持久保存到数据库中,不需要在检索对象状态之前确定是否/要插入/更新什么。JPA 持久性上下文对象状态已经包含足够的细节来确定这一点。

你的问题是你有新的实体实例,你想表现得好像它们已经被分离了——这不是 JPA 的方式。

SaveOrUpdate 是一种休眠的专有操作——如果一个实体有一个身份,它就会被更新,但如果它没有实体,那么它就会被插入。

休眠的角度来看 - 是的,合并后的 saveOrUpdate 理论上可以用于将(Xstream 流式传输的)瞬态对象与持久对象相关联,但在与 JPA 操作结合使用时可能会有限制。saveOrUpdate 确实在每个对象持久存储到数据库之前进行检索以确定是否/要插入/更新的内容 - 它很聪明,但它不是 JPA 并且它不是最高性能的。即,您应该能够通过一些谨慎和正确的配置来让它工作 - 并在发生冲突时使用休眠操作而不是 JPA 操作。

我收到以下错误:

org.hibernate.ObjectDeletedException:已删除的对象将被级联重新保存(从关联中删除已删除的对象):[com.......SomeObject#353296]

我认为这可能是由两个因素造成的:

  • 在您(Xstream 创建的)对象图中的某处,如果您使用 JPA 进行(级联)检索,则缺少一个子实体实例:这会删除对象
  • 在您(Xstream 创建的)对象图中的其他地方,存在相同的子实体:这使对象被重新保存

    如何调试: (a) 从 Xstream 创建对象图并完整打印出来 - 每个实体,每个字段;(b) 通过 JPA 加载相同的对象图,从顶部实体级联检索并完整打印出来 - 每个实体,每个字段 (c) 比较两者 - (a) 中缺少的东西 (b) )??

有什么方法可以实现我想要做的事情,而不必在会话中获取持久对象并对其进行修改,而不是修改瞬态对象,并尝试保存并覆盖现有的持久对象?

通过刚刚建议的调试/修复 - 希望如此。

或者我的蛮力建议(愚蠢地)绕过这个错误(虽然会降低性能一点):在对象图中填充 ID 后,调用 EntityManager.clear(),然后继续执行 merge() 和 saveOrUpdate() . 但是对数据库结果进行单元测试 - 以确保您在填充 Xstream 图表时没有忽略一些重要的事情。

对于测试 / 作为最后的手段,请尝试不使用 merge() 的 saveOrUpdate(),但您可能会被迫 clear() 实体管理器以避免 Hibernate 异常,例如 NonUniqueObjectException。

如果您了解更多/有更多信息,请告诉我 B^)

于 2013-03-18T05:09:54.590 回答
1
    Hibernate merge was not meant for associating transient objects 
to persistent ones.

merge() 应该非常适合您,因为您实际上是在处理分离的对象。由于您在调用合并之前在“transObj”中设置 id,因此休眠会将它们视为分离的(不是瞬态的)。

我认为您的代码的问题在于它正在触发休眠。

在您的代码中,您正在从数据库中加载“persistObj”。现在,hibernate 在会话中持有这个“persistObj”。然后,您在“transObj”中设置“persistObj”中的一些 id,然后调用 merge。'persistObj' 和 'transObj' 中的一些子对象具有相同的 id,hibernate 会感到困惑。

ComplexObject transObj = xstream.import("object.xml");
ComplexObject persistObj = someService.getObjByName(transObj.getName());
for (OtherObject o : c.getObjects()) {
    if (persistObj.getObjects().contains(o.getName())) {
        o.setId(persistObj.getObjectByName(o.getName()).getId())
     }
    ... set a bunch of other IDs deeper in the object graph ...
}
transObj = session.merge(transObj);
session.saveOrUpdate(transObj);

在加载“persistObj”后尝试调用 session.clear(),以便休眠删除 persistObj 并只考虑您分离的对象。

ComplexObject transObj = xstream.import("object.xml");
ComplexObject persistObj = someService.getObjByName(transObj.getName());
// Clear the session, so that hibernate removes 'persistObj' from it's cache
session.clear();
for (OtherObject o : c.getObjects()) {
    if (persistObj.getObjects().contains(o.getName())) {
        o.setId(persistObj.getObjectByName(o.getName()).getId())
     }
    ... set a bunch of other IDs deeper in the object graph ...
}
transObj = session.merge(transObj);
session.saveOrUpdate(transObj);
于 2013-03-18T18:06:50.177 回答