Hibernate - 一对多关系和孤儿删除级联

9

我有一个基本的一对多的父子关系,就像Hibernate参考书的第21章中所述。
级联只从子到父(仅持久化级联,因为如果删除子项,我不想删除父项)。
当我将一个子项添加到父项并保存子项时,我遇到了TransientObjectException...

@Entity
public class Parent implements Serializable {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  @OneToMany(mappedBy = "parent", orphanRemoval = true)
  private List<Child> childs;

  public List<Child> getChilds() {
    return childs;
  }

  public void setChilds(List<Child> childs) {
    this.childs = childs;
  }

  public void addChild(Child child) {
    if (childs == null) childs = new ArrayList<Child>();
    if (childs.add(child)) child.setParent(this);
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }
}

@Entity
public class Child implements Serializable {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  @ManyToOne(optional = false)
  @Cascade( { PERSIST, MERGE, REFRESH, SAVE_UPDATE, REPLICATE, LOCK, DETACH })
  private Parent parent;

  public Parent getParent() {
    return parent;
  }

  public void setParent(Parent parent) {
    this.parent = parent;
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }
}


@Test
public void test() {
  Parent parent = new Parent();
  Child child = new Child();
  parent.addChild(child);
  genericDao.saveOrUpdate(child);
}

但是在执行saveOrUpdate时,我遇到了这个异常:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: Child
  at org.hibernate.engine.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:244)
  at org.hibernate.collection.AbstractPersistentCollection.getOrphans(AbstractPersistentCollection.java:911)
  at org.hibernate.collection.PersistentBag.getOrphans(PersistentBag.java:143)
  at org.hibernate.engine.CollectionEntry.getOrphans(CollectionEntry.java:373)
  at org.hibernate.engine.Cascade.deleteOrphans(Cascade.java:471)
  at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:455)
  at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:362)
  at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:338)
  at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
  at org.hibernate.engine.Cascade.cascade(Cascade.java:161)
  at org.hibernate.event.def.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:476)
  at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:354)
  at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
  at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)
  at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
  at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
  at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:117)
  at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
  at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:677)
  at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:669)
  at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:252)
  at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:392)
  at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:335)
  at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
  at org.hibernate.engine.Cascade.cascade(Cascade.java:161)
  at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:451)
  at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:288)
  at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
  at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)
  at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
  at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
  at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:117)
  at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
  at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:677)
  at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:669)
  at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:665)

我真的不明白为什么通过级联保存子项应该能够保存父项...有什么想法吗?
更新1 问题似乎与"orphanRemoval"有关,因为如果我在父项上注释它:
@OneToMany(mappedBy = "parent" /*, orphanRemoval = true */)
private List<Child> childs;

它有效!
它保留了子项,然后是父项。
但是当我从其父项中删除一个子项时,我真的需要通过级联删除孤儿。

更新2
我已经创建了一个JIRA问题:
http://opensource.atlassian.com/projects/hibernate/browse/HHH-5364

更新3
看起来已经修复了:-)
http://opensource.atlassian.com/projects/hibernate/browse/HHH-2269


欢迎来到 Stack Overflow!今后使用由“0”和“1”组成的按钮来正确格式化您的代码(我已经为您格式化了它)。 - Petar Minchev
谢谢...我们同时完成了它;-) - Cedric Thiebault
如果您在保存子项之前保存父项会发生什么? - Kendrick
它可以工作,但我无法在我的应用程序中这样做。这些子项在其他实体中使用并通过级联保存...这就是为什么当保存子项时,我也想保存它们的父项。 - Cedric Thiebault
2个回答

2

0

基本上,您正在违反限制条件。对应于父级的数据库行不存在,因此子项无法使用外键关系引用父项。在处理子项之前,在父项上添加saveOrUpdate的调用。

(编辑) 在重新格式化前我错过了您的关于级联的评论。我的记忆是级联不会向上游方式工作; 您仍需要先保存父项。


好的,但是saveOrUpdate在单个事务中执行,所以它首先保存子项,然后级联应该创建父项(并更新子项的外键),最后刷新到数据库,只有在此时才验证约束...对吗? - Cedric Thiebault
你尝试在父映射中添加“inverse=true”了吗?我认为这不会起作用,但Hibernate总是能给我带来惊喜。 - Mikeb
我没有使用XML配置,而是使用注解。 @OneToMany(mappedBy = "parent") 也可以实现相同的功能。 - Cedric Thiebault
似乎我的问题来自于orphanRemoval...我刚刚更新了我的帖子。 - Cedric Thiebault
@Cedric:看起来是Hibernate的一个bug。 - axtavt
我已经打开了一个JIRA问题:http://opensource.atlassian.com/projects/hibernate/browse/HHH-5364 - Cedric Thiebault

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接