-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 11 posts ] 
Author Message
 Post subject: Bidirectional parent-child mapping problem
PostPosted: Tue Jan 29, 2008 2:08 pm 
Newbie

Joined: Tue Jan 29, 2008 1:30 pm
Posts: 9
Hi all,
I am having trouble using Hibernate as JPA provider. I must be also missing something, altough I've read the docs many times.

I want to create a bidirectional parent-child association, where the parent is the only responsible for managing the association.

Since I'm using DTO's, I have to make the ejb <> dto conversion.

I have a method updateScenario() that receives a full-blown scenario, including the dependency elements collection. This collection can contain new elements (which do not have id), and old ones (which do have an id).
Because I have to use dtos, when I update my scenario I'm merging an unmanaged scenario instance that was converted from my DTO:

Code:
public void updateScenario(ScenarioEJB scenario) {   
        em.merge(scenario);
    }

but that doesn't work. Can you tell me what I am doing wrong?

Hibernate version:
3.2.5.GA, with Hibernate entity manager 3.3.1.GA
Mapping documents:
This is the parent mapping
Code:
@Entity
@Table(name = "OMP_SCENARIO")
public class ScenarioEJB implements java.io.Serializable {
...
    @OneToMany(cascade = { CascadeType.ALL }, mappedBy = "scenario")
    @JoinColumn(name = "SCENARIO")
    @Cascade(value = { org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
    public Set<ScenarioDependencyElementEJB> getDependencyElements() {
        return dependencyElements;
    }



This is the child mapping
Code:
@Entity
@Table(name = "OMP_SCENARIO_DEP_ELEMENT")
public class ScenarioDependencyElementEJB implements java.io.Serializable {
...
    @ManyToOne(cascade = {}, fetch = FetchType.LAZY)
    @JoinColumn(name = "SCENARIO", insertable = true, updatable = true, nullable = false)
    public ScenarioEJB getScenario() {
        return scenario;
    }


Full stack trace of any exception that occurs:
org.springframework.orm.hibernate3.HibernateSystemException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: ScenarioEJB.dependencyElements; nested exception is org.hibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: ScenarioEJB.dependencyElements
at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:659)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:95)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:306)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:62)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:212)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:146)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:631)
(...)
Name and version of the database you are using:
Oracle 10G
Code:


Top
 Profile  
 
 Post subject: Re: Bidirectional parent-child mapping problem
PostPosted: Tue Jan 29, 2008 3:21 pm 
Expert
Expert

Joined: Wed Apr 11, 2007 11:39 am
Posts: 735
Location: Montreal, QC
Umm, let me guess. I don't think DELETE_ORPHAN can do much in this case because the session object does not have the collection in memory so that it knows who is orphan now. You could possibly test this theory by adding a @org.hibernate.annotations.Entity and specifying selectBeforeUpdate. See if this makes any difference for you. If not, please give me the whole stack trace.


Farzad-



ADDED LATER: I did a test and it passed. Well, it means that the thing I said above has nothing to do with your problem. I am looking more into it.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 30, 2008 5:38 am 
Newbie

Joined: Tue Jan 29, 2008 1:30 pm
Posts: 9
I've been doing some tests, and I get it to work if I remove the DELETE_ORPHAN annotation, and merge the differences manually. That means whenever my EJB comes into my update() method, I must do a fetch for the original scenario object, and merge its dependencyElements collection by comparing each element and adding/removing as necessary.

What I would like is to do this automatically, but according to the lifecycle of an entity, I must always fetch the parent instance and handle this instance's collection so that hibernate can track the changes to the collection. Am I right, or can I do this transparently?


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 30, 2008 12:52 pm 
Expert
Expert

Joined: Wed Apr 11, 2007 11:39 am
Posts: 735
Location: Montreal, QC
Removing DELETE_ORPHAN will sure hide the problem but it is still there. I have readnig hibernate code for this problem and it appears you have a collection in your hibernate session that is not traversed by reachability and that is the one that is causing the problem. You sure should not have a problem by creating an object tree and merging it into a session. This tells me that may be something else is also happening in scope of the same session or you are using an stale session somehow. However, I know so little about your code at this point and I can infer much. You could set hibernate logging to debug or trace and show me what it says. You could also debug the code and go into hibernate code. That will definitely tell you what's going wrong.



Farzad-


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 30, 2008 3:27 pm 
Newbie

Joined: Tue Jan 29, 2008 1:30 pm
Posts: 9
I believe the problem is because I'm setting a collection on a scenario that's unmanaged, and I can only do that with managed scenarios.

Im saying this:

If I do the following(pseudo code):

Code:
Scenario s = new Scenario();
s.setId(1);

Dependency d1 = new Dependency();
d1.setId(23);
d1.setScenario(s);
d1.setName("xx");
Dependency d2 = new Depdency();
d2.setScenario(s);
d2.setName("yy");
s.setDependencies(d2);

(...)
em.merge(s);

What I want is to hibernate change d1's name to "xx" with a sql update, insert a new one for d2, and remove any other dependencies if they exist.
I guess this doesn't work because my scenario has an unmanaged collection.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 30, 2008 3:59 pm 
Expert
Expert

Joined: Wed Apr 11, 2007 11:39 am
Posts: 735
Location: Montreal, QC
Nah, that is not your problem. I do this test which is exactly what you do but it passes:

Code:
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("manager1");
        EntityManager em = emf.createEntityManager();

        em.getTransaction().begin();

        WidgetContainer container = new WidgetContainer();

        container.setId(1l);

        container.getWidgets().add(new Widget(7l));
        container.getWidgets().add(new Widget(8l));

        em.merge(container);

        em.getTransaction().commit();
        em.close();

        emf.close();


It also detects what Widgets used to be in the Container and it removes them from database. Merge will cause a select from database if the object is not in the sessions first level cache. You could verify this by looking at the hibernate log. If you don't see a select then you might have a problem with equals and hashcode methods. Basically hibernate is not able to find the relation or you could be playing with references and that explains why there is a zombie collection in the session's first level cache.


Farzad-


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 30, 2008 5:17 pm 
Newbie

Joined: Tue Jan 29, 2008 1:30 pm
Posts: 9
I don't know if by calling
Code:
new Widget(8l)
you're passing the id of the widget (71) or it's just a property, but in my example the equivalent would be passing a widget without an id, besides the first one with the 71 in the constructor, and hibernate updating the first, persisting the second and deleting any other.

By the way, in your example do you use the "mappedBy" attribute in the @JoinColumn for WidgetContainer? and do you use the DELETE_ORPHAN hibernate annotation?

I'll check it out later and see what I'm doing wrong, anyway thanks for the quick reply!


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 30, 2008 5:22 pm 
Expert
Expert

Joined: Wed Apr 11, 2007 11:39 am
Posts: 735
Location: Montreal, QC
miguelping wrote:
I don't know if by calling
Code:
new Widget(8l)
you're passing the id of the widget (71) or it's just a property, but in my example the equivalent would be passing a widget without an id, besides the first one with the 71 in the constructor, and hibernate updating the first, persisting the second and deleting any other.

By the way, in your example do you use the "mappedBy" attribute in the @JoinColumn for WidgetContainer? and do you use the DELETE_ORPHAN hibernate annotation?

I'll check it out later and see what I'm doing wrong, anyway thanks for the quick reply!



It is indeed the id. I forgot to mention that. I was just too lazy to do it over and over and yes I do use DELETE_ORPHAN. I modified one of my examples to match your case. I also did a couple of other cases that would cause a WidgetContainer to be loaded into the session's memory and within the scope of the same session I created a WidgetContainer object with the same primary key and a modified list of Widgets, all new objects, and it worked and hibernate removed the Widgets that were not longer in the list.


Farzad-


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jan 31, 2008 7:17 am 
Newbie

Joined: Tue Jan 29, 2008 1:30 pm
Posts: 9
Hi again, can you show your mapping for Widget and WidgetContainer?
Are you using the mappedBy property (equivalent to inverse=true)?

I've been reading the "Java persistence with hibernate" but I'm confused, because I've found on some forums that if you do not specify the "mappedBy" attribute, the parent becomes responsible for the relation. In the book it seems the other way.

Thanks again


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jan 31, 2008 11:14 am 
Expert
Expert

Joined: Wed Apr 11, 2007 11:39 am
Posts: 735
Location: Montreal, QC
There you go. I modified my example a little more so that it very much looks like your mapping and I still get the same results:

Code:
package test.model.data.jpa;

import javax.persistence.*;


@Entity
@Table(name = "widget")
public class Widget {
    @Id
    @GeneratedValue
    @Column(name = "id")
    private Long id;

    @ManyToOne(cascade = {}, fetch = FetchType.LAZY)
    @JoinColumn(name = "ContainerID", insertable = true, updatable = true, nullable = false)
    private WidgetContainer container;

    public Widget() {
    }

    public Widget(Long id) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }

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

    public WidgetContainer getContainer() {
        return container;
    }

    public void setContainer(WidgetContainer container) {
        this.container = container;
    }
}



Code:
package test.model.data.jpa;

import org.hibernate.annotations.*;

import javax.persistence.*;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.CascadeType;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.HashSet;

@Entity
@Table(name = "WidgetContainer")
public class WidgetContainer {
    private Long id;
    private Set<Widget> widgets = new HashSet<Widget>();

    @OneToMany(cascade = { CascadeType.ALL }, mappedBy = "container")
    @JoinColumn(name = "ContainerID")
    @Cascade(value = { org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
    public Set<Widget> getWidgets() {
        return widgets;
    }

    public void setWidgets(Set<Widget> widgets) {
        this.widgets = widgets;
    }

    @Id
    @GeneratedValue
    @Column(name = "id")
    public Long getId() {
        return id;
    }

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

    public void addWidget(Widget widget)
    {
        getWidgets().add(widget);
        widget.setContainer(this);
    }
}



Umm, I don't know why you are concerned with that since I don't believe your problem is there. You should more be focused how you obtain an EntityManager and if it is shared with other areas of your code. You may also want to get a new entity manager in that part of your code and merge the changes with that.


Farzad-


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jan 31, 2008 12:03 pm 
Newbie

Joined: Tue Jan 29, 2008 1:30 pm
Posts: 9
Thanks, I solved it. I found out the hard way how simple it is.

I removed the "mappedBy" from parent annotations, added the "DELETE_ORPHAN" hibernate annotation, and most important, I only add/remove childs from managed instances. Among other things I was doing
Code:
parent.setChilds(someMixedChildsSet);

instead of doing
Code:
parent.getChilds().clear();
parent.getChilds().addAll(someMixedChildsSet);


The someMixedChildsSet can have childs with or without id.

I also had another problem, I had a parent-child-grandchild relation whose grandchilds were not properly mapped.

I understand that is unusual, but this way I can manage childs only from the parent entity.

Thanks again![/i]


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 11 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.