2

If I have a bidirectional Relationship in my DataModel it is the responsibility of my application to keep the references up to date in the java code.

What is the best way to do this?

For example a bidir. 1:N relation between A and B.

@Entity
class A {

@ManyToOne
private B b;

}


@Entity
class B {

@OneToMany(mappedBy="b")
private Collection<A> as; 

}

If I say B.addA(b) this does not let the variable b in A point to the Reference i added. And if i call A.setB(b) this does not add a reference of b to the collection in B.

One possible way would be to call setB AND addA in my application code.

The other posibility would be to write the setA(..) method like this:

public setB(B b) {
    this.b = b;
    if(!b.contains(this) {
    b.add(this);
    }
}



public addA(A a) {
    if(!as.conatains(a)) {
      as.add(a);
    }
    a.setB(this);
    }

but this sometimes throws some exceptions like:

org.hibernate.LazyInitializationException: illegal access to loading collection

i guess because the framework calls at somepoint this setMethod and want to load the "this" reference...?!? can somebody explain me why this happens? And what is the way to go to guarantee that i have clean bidrectional relationships in my java code?

thx

UPDATE: here is the original code:

@Entity
class Cluster{

private Grid grid

//someother fields

@ManyToOne
    public Grid getGrid() {
        return grid;
    }

    public void setGrid(Grid grid) {
        this.grid = grid;
        if(!grid.getClusters().contains(this)) { //HERE AN EXCEPTION IS THROWN
            grid.addCluster(this);
        }
    }

}

@Entity
class Grid {

    private Collection<Cluster> clusters = new ArrayList<Cluster>();

    //some other fields

    @OneToMany(mappedBy = "grid", cascade = CascadeType.PERSIST, orphanRemoval = true)
    public Collection<Cluster> getClusters() {
        return clusters;
    }

    public void setClusters(Collection<Cluster> clusters) {
        this.clusters = clusters;
    }

    public void addCluster(Cluster c) {
    this.clusters.add(c);
    c.setGrid(this);
}

}

In one of my queries i get the exception which says that something inside the setGrid Method ist wrong... If i remove the lines everything is fine.. but then i do not have my bidirection... :/

The stacktrace:

Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.PropertyAccessException: Exception occurred inside setter of dst1.model.Cluster.grid
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1214)
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1147)
    at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:255)
    at dst1.Main.dst02b(Main.java:828)
    at dst1.Main.main(Main.java:38)
Caused by: org.hibernate.PropertyAccessException: Exception occurred inside setter of dst1.model.Cluster.grid
    at org.hibernate.property.BasicPropertyAccessor$BasicSetter.set(BasicPropertyAccessor.java:89)
    at org.hibernate.tuple.entity.AbstractEntityTuplizer.setPropertyValues(AbstractEntityTuplizer.java:583)
    at org.hibernate.tuple.entity.PojoEntityTuplizer.setPropertyValues(PojoEntityTuplizer.java:229)
    at org.hibernate.persister.entity.AbstractEntityPersister.setPropertyValues(AbstractEntityPersister.java:3822)
    at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:152)
    at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:982)
    at org.hibernate.loader.Loader.doQuery(Loader.java:857)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)
    at org.hibernate.loader.Loader.loadEntity(Loader.java:2037)
    at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:86)
    at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:76)
    at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3268)
    at org.hibernate.event.def.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:496)
    at org.hibernate.event.def.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:477)
    at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:227)
    at org.hibernate.event.def.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:285)
    at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:152)
    at org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:1090)
    at org.hibernate.impl.SessionImpl.internalLoad(SessionImpl.java:1038)
    at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:630)
    at org.hibernate.type.EntityType.resolve(EntityType.java:438)
    at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:139)
    at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:982)
    at org.hibernate.loader.Loader.doQuery(Loader.java:857)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)
    at org.hibernate.loader.Loader.doList(Loader.java:2533)
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2276)
    at org.hibernate.loader.Loader.list(Loader.java:2271)
    at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:452)
    at org.hibernate.hql.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:363)
    at org.hibernate.engine.query.HQLQueryPlan.performList(HQLQueryPlan.java:196)
    at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1268)
    at org.hibernate.impl.QueryImpl.list(QueryImpl.java:102)
    at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:246)
    ... 2 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.hibernate.property.BasicPropertyAccessor$BasicSetter.set(BasicPropertyAccessor.java:66)
    ... 35 more
Caused by: org.hibernate.PropertyAccessException: Exception occurred inside setter of dst1.model.Cluster.grid
    at org.hibernate.property.BasicPropertyAccessor$BasicSetter.set(BasicPropertyAccessor.java:89)
    at org.hibernate.tuple.entity.AbstractEntityTuplizer.setPropertyValues(AbstractEntityTuplizer.java:583)
    at org.hibernate.tuple.entity.PojoEntityTuplizer.setPropertyValues(PojoEntityTuplizer.java:229)
    at org.hibernate.persister.entity.AbstractEntityPersister.setPropertyValues(AbstractEntityPersister.java:3822)
    at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:152)
    at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:982)
    at org.hibernate.loader.Loader.doQuery(Loader.java:857)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)
    at org.hibernate.loader.Loader.loadCollection(Loader.java:2166)
    at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:62)
    at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:627)
    at org.hibernate.event.def.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:83)
    at org.hibernate.impl.SessionImpl.initializeCollection(SessionImpl.java:1863)
    at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:369)
    at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:111)
    at org.hibernate.collection.AbstractPersistentCollection.readElementExistence(AbstractPersistentCollection.java:167)
    at org.hibernate.collection.PersistentBag.contains(PersistentBag.java:262)
    at dst1.model.Cluster.setGrid(Cluster.java:114)
    ... 40 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.hibernate.property.BasicPropertyAccessor$BasicSetter.set(BasicPropertyAccessor.java:66)
    ... 57 more
Caused by: org.hibernate.LazyInitializationException: illegal access to loading collection
    at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:366)
    at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:111)
    at org.hibernate.collection.AbstractPersistentCollection.readElementExistence(AbstractPersistentCollection.java:167)
    at org.hibernate.collection.PersistentBag.contains(PersistentBag.java:262)
    at dst1.model.Cluster.setGrid(Cluster.java:114)
    ... 62 more
Moonlit
  • 5,171
  • 14
  • 57
  • 95
  • haven't you got any feedback yet? – Alonso Dominguez Apr 02 '12 at 18:26
  • Hi and thank you very much for your help but unfortunately it did not solve the problem. Whenever i want to get the collection of another entity out from a set/add method of an related entity i get this LazyInitializationException :/ I will try to take apart my solution and see if I can get more details why this is happening :( thank you for your help – Moonlit Apr 02 '12 at 20:46
  • maybe you should ask at Hibernate forum, they will know better the internals of the implementation. – Alonso Dominguez Apr 02 '12 at 21:10

2 Answers2

1

This is an idea.

I use two layers, "persistence model layer" and "domain model layer".

Classes of "persistence model layer" have some JPA annotations, but do not have any application rules.
Classes of "domain model layer" do not have any JPA annotations.

JPA/Hibernate knows classes of "persistence model layer", but do not know classes of "domain model layer".

Classes in "persistence model layer" are very simple for JPA/Hibernate.
So, problems like this question, will be unlikely to occur.

Classes in "domain model layer", in this case, have a responsibility to keep bidirectional relationship between A and B.(A#setB, B#addA)
There is no need to worry about ORM impact.

There are exsample code.
"persistence model layer" contains A and B.
"domain model layer" contains MA and MB.
An instance of MA has an instance of A, and MA delegate its state to A.

/** persistence model layer */
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
public class A {
    private Long id;
    private B b;
    public A(){
    }
    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    @ManyToOne
    public B getB() {
        return b;
    }
    public void setB(B b) {
        this.b = b;
    }
}
import java.util.ArrayList;
import java.util.Collection;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
@Entity
public class B {
    private Long id;
    private Collection<A> as = new ArrayList<A>();
    public B(){
    }
    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    @OneToMany(cascade=CascadeType.ALL, mappedBy="b", fetch=FetchType.LAZY)
    public Collection<A> getAs() {
        return as;
    }
    public void setAs(Collection<A> as) {
        this.as = as;
    }
}
/** domain model layer */
public class MA {
    private A entity;
    public MA(A a){
        this.entity = a;
    }
    public A getEntity(){
        return this.entity;
    }
    public MB getB(){
        return new MB(entity.getB());
    }
    public void setB(MB mb){
        if (mb != null && this.entity.getB() != mb.getEntity()){
                this.entity.setB(mb.getEntity());
                mb.addA(this);
        }
        return;
    }
}
import java.util.ArrayList;
import java.util.List;
public class MB {
    private B entity;
    public MB(B b){
        this.entity = b;
    }
    public B getEntity(){
        return this.entity;
    }
    public void addA(MA ma){
        if (ma != null && ! this.getEntity().getAs().contains(ma.getEntity())){
            this.entity.getAs().add(ma.getEntity());
            ma.setB(this);
        }
        return;
    }
    public List<MA> getAs(){
        List<MA> resultList = new ArrayList<MA>();
        for(A a : entity.getAs()){
            resultList.add(new MA(a));
        }
        return resultList;
    }
}

It is better to implement equals/hashCode method.
I hope you will be a hint.

murayama
  • 11
  • 2
1

Hibernate and other JPA-based ORMs use to load collections that define relationships just when needed (lazy loading). I understand that Hibernate is throwing that exception when you try to modify a collection that wasn't loaded yet or that is an intermediate state.

Hibernate uses proxies to handle the entities and it understands that you want to use a collection when invoking the get method for that specific collection.

I would implement your setGrid method really different but first your entities need to implement the methods equals and hashCode. Other modifications would be:

Change your collections of clusters to be a set. A set doesn't contains duplicate instances and thus you won't need to do that contains check before adding any element to the collection:

Set<Cluster> clusters = new HashSet<Cluster>();

Then modify your setGrid method so it calls the add method of the collection itself, instead of the one you had declared:

setGrid(Grid grid) {
   Grid oldGrid = this.grid;
   this.grid = grid;
   if (oldGrid != null) {
       oldGrid.getClusters().remove(this);
   }
   if (grid != null) {
       grid.getClusters().add(this);
   }
}

And finally, change a bit the implementation of your addCluster method in the Grid class:

public void addCluster(Cluster c) {
    //this.clusters.add(c); -- no needed anymore
    c.setGrid(this);
}

Hope this helps

Alonso Dominguez
  • 7,750
  • 1
  • 27
  • 37
  • Thank you for your reply. I made a mistake .. in the method setB it should be if(!b.getAs().contains(this)) ...so i already use the get method. ANd this line shows up in the stacktrace of the exception. – Moonlit Apr 01 '12 at 12:39
  • can you please update your question with that information?, you should find a link that will allow you to edit it. (that stacktrace will be really useful if you post it) – Alonso Dominguez Apr 01 '12 at 12:45
  • It doesn't have to be lazy - you can specify the default loading mode. See javax.persistence.FetchType. – Mike Baranczak Apr 01 '12 at 12:54
  • that's right Mike, and his model is just compound of those two entities using EAGER loading will solve the problem... but, from my point of view, the decision of a lazy or an eager collection must be always analysed in each specific case as having all the relationships in your model declared as EAGER can kill the performance with large data sets. – Alonso Dominguez Apr 01 '12 at 13:36