2010-07-08 9 views
7

J'ai un parent-enfant de base à plusieurs comme dans le chapitre 21 du livre de références d'Hibernate.
La cascade est uniquement d'enfant à parent (persistez en cascade uniquement parce que je ne veux pas supprimer le parent si je supprime un enfant).
Quand j'ajouter un enfant au parent et je sauve l'enfant, j'ai TransientObjectException ...Hibernate - Relation un-à-plusieurs et cascade OrphanRemoval

@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); 
} 

Mais le saveOrUpdate, je cette exception:

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) 

Je vraiment don ne comprends pas car sauver l'Enfant devrait sauver le Parent via la cascade ... Des idées?

MISE À JOUR 1
Le problème semble être lié à "orphanRemoval" parce que si je commente sur le parent:

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

ça marche!
Il sauve l'enfant, puis le parent.
Mais j'ai vraiment besoin que l'orphelin soit supprimé via la cascade lorsque je retire un enfant de son parent.

MISE À JOUR 2
J'ai créé un problème JIRA:
http://opensource.atlassian.com/projects/hibernate/browse/HHH-5364

MISE À JOUR 3
Il semble être fixé :-)
http://opensource.atlassian.com/projects/hibernate/browse/HHH-2269

+0

Bienvenue sur Stack Overflow! Utilisez le bouton avec des zéros et ceux pour formater correctement votre code dans le futur (je l'ai formaté pour vous). –

+0

Merci ... nous l'avons fait en même temps ;-) –

+0

Que se passe-t-il si vous enregistrez le parent avant d'enregistrer l'enfant? – Kendrick

Répondre

0

Fondamentalement, vous violez une contrainte. La ligne de la base de données correspondant au parent n'existe pas. Il n'existe donc aucune relation de clé étrangère que l'enfant peut utiliser pour faire référence au parent. Ajoutez un appel à saveOrUpdate sur le parent avant de faire pour l'enfant.

(edit) J'ai manqué votre commentaire sur la cascade avant le reformatage. Mon souvenir est que la cascade ne fonctionne pas en amont de cette façon; vous devez toujours enregistrer le parent en premier.

+0

OK mais le saveOrUpdate est exécuté en une seule transaction, donc enregistrez d'abord l'enfant, puis la cascade devrait créer le parent (et mettre à jour la clé étrangère enfant) et enfin vider DB et seulement à ce moment les contraintes sont validées. . non ? –

+0

avez-vous essayé d'ajouter 'inverse = true' au mappage parent? Je ne pense pas que ça marcherait mais Hibernate me surprend. – Mikeb

+0

Je n'utilise pas de configuration XML mais des annotations. @OneToMany (mappedBy = "parent") fait la même chose. –

Questions connexes