-->
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.  [ 10 posts ] 
Author Message
 Post subject: Two Bi-directional Associations between two classes
PostPosted: Thu Dec 06, 2007 10:56 am 
Newbie

Joined: Fri Sep 15, 2006 10:29 am
Posts: 14
Need help with Hibernate? Read this first:
http://www.hibernate.org/ForumMailingli ... AskForHelp

Hibernate version:
3.2.5.ga
Hibernate Annotations version:
3.3.0.GA
Hibernate Entity Manager version:
3.3.1.GA
Mapping documents:
Code:
@Entity
@Table(name = "LOCATION")
public class Location {
   @Id
   @Column(name = "ID")
   protected long identifier;

   @OneToMany(cascade = CascadeType.ALL, mappedBy = "childLocation")
   protected Set<LocationRelation> parentRelations;

   @OneToMany(cascade = CascadeType.ALL, mappedBy = "parentLocation")
   protected Set<LocationRelation> childRelations;

   public void addParentRelation(LocationRelation rel) {
      parentRelations.add(rel);
      rel.childLocation = this;
   }

   public void addChildRelation(LocationRelation rel) {
      childRelations.add(rel);
      rel.parentLocation = this;
   }
}

@Entity
@Table(name = "LOCATION_RELATION")
public class LocationRelation {
   @ManyToOne
   @JoinColumn(name = "CHILD_ID", nullable = false)
   protected Location childLocation;

   @ManyToOne
   @JoinColumn(name = "PARENT_ID", nullable = false)
   protected Location parentLocation;
}


Database Structure:
Code:
CREATE TABLE LOCATION (
    ID NUMBER(10) PRIMARY KEY
);

CREATE TABLE LOCATION_RELATION (
    CHILD_ID NUMBER(10) FOREIGN KEY REFERENCES LOCATION (ID),
    PARENT_ID NUMBER(10) FOREIGN KEY REFERENCES LOCATION (ID)
);

CREATE UNIQUE INDEX UQ_RELATION ON
    LOCATION_RELATION (CHILD_ID, PARENT_ID);


Code between sessionFactory.openSession() and session.close():
Code:
Location loc1;
Location loc2;
LocationRelation rel = new LocationRelation();
...
loc1.addChildRelation(rel);
loc2.addParentRelation(rel);
em.merge(loc1);


The problem is that Hibernate executes two inserts into the table LOCATION_RELATION, although there is only one instance of the class LocationRelation. Is it a bug, or am I doing something wrong?


Top
 Profile  
 
 Post subject: Re: Two Bi-directional Associations between two classes
PostPosted: Thu Dec 06, 2007 12:34 pm 
Expert
Expert

Joined: Wed Apr 11, 2007 11:39 am
Posts: 735
Location: Montreal, QC
dimitar wrote:
The problem is that Hibernate executes two inserts into the table LOCATION_RELATION, although there is only one instance of the class LocationRelation. Is it a bug, or am I doing something wrong?



To me the problem is the mapping itself. You have a many-to-many (or one-to-many) association from and to location class and location relation is only an association table and you don't necessarily need to map it as a separate class. Configure your association to work with the association table and hibernate should take care of the rest.


Top
 Profile  
 
 Post subject: Re: Two Bi-directional Associations between two classes
PostPosted: Thu Dec 06, 2007 12:45 pm 
Newbie

Joined: Fri Sep 15, 2006 10:29 am
Posts: 14
farzad wrote:
To me the problem is the mapping itself. You have a many-to-many (or one-to-many) association from and to location class and location relation is only an association table and you don't necessarily need to map it as a separate class. Configure your association to work with the association table and hibernate should take care of the rest.

I agree, that this is a many-to-many relation and association table can be used. However, I don't think that Hibernate behaves correctly in the case that I have described.


Top
 Profile  
 
 Post subject: Re: Two Bi-directional Associations between two classes
PostPosted: Thu Dec 06, 2007 12:49 pm 
Expert
Expert

Joined: Wed Apr 11, 2007 11:39 am
Posts: 735
Location: Montreal, QC
dimitar wrote:
farzad wrote:
To me the problem is the mapping itself. You have a many-to-many (or one-to-many) association from and to location class and location relation is only an association table and you don't necessarily need to map it as a separate class. Configure your association to work with the association table and hibernate should take care of the rest.

I agree, that this is a many-to-many relation and association table can be used. However, I don't think that Hibernate behaves correctly in the case that I have described.



You might be able to come to an answer by debugging the example but my rough guess is that it is trying to save the same object in two paths and if at some point the relation object is copied by value then it will not notice it already has an id and does not need to be inserted. In any events, doing it like this will require you to do some manual work. Hibernate gives best results when it is treated properly. The amount of manual work you need to do increases as you get farther from standard.

Farzad-


Top
 Profile  
 
 Post subject:
PostPosted: Mon Dec 10, 2007 6:17 am 
Newbie

Joined: Fri Sep 15, 2006 10:29 am
Posts: 14
Well, I think it is not that uncommon situation to have some additional information for a many-to-many association. Consider the following case:
Code:
@Entity
@Table(name = "LOCATION_RELATION")
public class LocationRelation {
   @ManyToOne
   @JoinColumn(name = "CHILD_ID", nullable = false)
   protected Location childLocation;

   @ManyToOne
   @JoinColumn(name = "PARENT_ID", nullable = false)
   protected Location parentLocation;

   @Column(name = "TYPE", nullable = false)
   protected String type;
}

In this case, a third column is needed, and I am not sure if Hibernate can handle such "complex" many-to-many associations. Therefore, one should use two one-to-many associations and an additional entity, as I've described.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Dec 10, 2007 2:49 pm 
Expert
Expert

Joined: Wed Apr 11, 2007 11:39 am
Posts: 735
Location: Montreal, QC
dimitar wrote:
In this case, a third column is needed, and I am not sure if Hibernate can handle such "complex" many-to-many associations. Therefore, one should use two one-to-many associations and an additional entity, as I've described.


I do agree with your point. However I did a little test with your code and it appeared to be working for me. So, I post the files here so that you decide what is different:

Location:
Code:
package test.model.data.jpa;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "LOCATION")
public class Location {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID")

    protected long identifier;

    @OneToMany(cascade= CascadeType.ALL, mappedBy = "parentLocation")
    protected Set<LocationRelation> parentRelations = new HashSet<LocationRelation>();

    @OneToMany(cascade= CascadeType.ALL, mappedBy = "childLocation")
    protected Set<LocationRelation> childRelations = new HashSet<LocationRelation>() ;

    public void addParentRelation(LocationRelation rel) {
        parentRelations.add(rel);
        rel.childLocation = this;
    }

    public void addChildRelation(LocationRelation rel) {
        childRelations.add(rel);
        rel.parentLocation = this;
    }
}


LocationRelation:
Code:
package test.model.data.jpa;

import javax.persistence.*;

@Entity
@Table(name = "LOCATION_RELATION")
public class LocationRelation {
    @Id
    @Column(name="ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected long id;
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "CHILD_ID", nullable = false)
    protected Location childLocation;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "PARENT_ID", nullable = false)
    protected Location parentLocation;
}


And the test code:
Code:
import test.model.data.jpa.Location;
import test.model.data.jpa.LocationRelation;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

/**
* @author Farzad Kohantorabi
* @created Dec 10, 2007
*/
public class Driver {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("manager1");
        EntityManager em = emf.createEntityManager();

        Location l1 = new Location();
        Location l2 = new Location();

        LocationRelation lr = new LocationRelation();

        l1.addChildRelation(lr);
        l2.addParentRelation(lr);

        em.getTransaction().begin();
        em.merge(l1);
        em.flush();
        em.getTransaction().commit();
        em.close();
        emf.close();
    }
}


and persitence.xml:
Code:
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
             version="1.0">


    <persistence-unit name="manager1" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <class>test.model.data.jpa.Location</class>
        <class>test.model.data.jpa.LocationRelation</class>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect"/>
            <property name="hibernate.connection.driver_class" value="net.sourceforge.jtds.jdbc.Driver"/>
            <property name="hibernate.connection.username" value="sa"/>
            <property name="hibernate.connection.password" value=""/>
            <property name="hibernate.connection.url"
                      value=""/>
            <property name="hibernate.max_fetch_depth" value="3"/>
            <property name="hibernate.show_sql" value="true"/>
        </properties>
    </persistence-unit>
</persistence>


One main difference I can tell you is the primary key for the relation table but I assume you did something for that anyways. Well, hibernate perfectly stored the relation in the database and inserted two locations.

Hibernate: insert into LOCATION default values
Hibernate: insert into LOCATION default values
Hibernate: insert into LOCATION_RELATION (CHILD_ID, PARENT_ID) values (?, ?)

I didn't observe any extra insert. However, I was getting unexpected result when the identity of Location class was not set to AUTO which means it somehow confused hibernate which object is which. I do believe your problem should be something close to this. Let me know how this works for you.


Farzad-


Top
 Profile  
 
 Post subject:
PostPosted: Tue Dec 11, 2007 7:24 am 
Newbie

Joined: Fri Sep 15, 2006 10:29 am
Posts: 14
Thanks for your effort, Farzad. I was preparing a similar test case. I agree, that your code is working. However, consider the following procedure:
Code:
import test.model.data.jpa.Location;
import test.model.data.jpa.LocationRelation;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

/**
* @author Farzad Kohantorabi
* @created Dec 10, 2007
*/
public class Driver {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("manager1");
        EntityManager em = emf.createEntityManager();

        Location l1 = new Location();
        Location l2 = new Location();

        em.getTransaction().begin();
        l1 = em.merge(l1);
        l2 = em.merge(l2);
        em.flush();
        em.getTransaction().commit();

        LocationRelation lr = new LocationRelation();

        l1.addChildRelation(lr);
        l2.addParentRelation(lr);

        em.getTransaction().begin();
        em.merge(l1);
        em.flush();
        em.getTransaction().commit();

        em.close();
        emf.close();
    }
}

This code throws the following exception:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: test.model.data.jpa.LocationRelation.

However, if you change the following:
l2.addParentRelation(lr);
with
lr.setChildLocation(l2);
it all goes fine. Where is the problem? It seems that I miss something crucial when doing the cascading.

Thanks for your help!


Top
 Profile  
 
 Post subject:
PostPosted: Tue Dec 11, 2007 12:44 pm 
Expert
Expert

Joined: Wed Apr 11, 2007 11:39 am
Posts: 735
Location: Montreal, QC
dimitar wrote:
This code throws the following exception:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: test.model.data.jpa.LocationRelation.

However, if you change the following:
l2.addParentRelation(lr);
with
lr.setChildLocation(l2);
it all goes fine. Where is the problem? It seems that I miss something crucial when doing the cascading.

Thanks for your help!


This has got to do something with a session's memory. Since the following code works fine:

Code:
public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("manager1");
        EntityManager em = emf.createEntityManager();

        Location l1 = new Location();
        Location l2 = new Location();

        em.getTransaction().begin();
        l1 = em.merge(l1);
        l2 = em.merge(l2);
        em.flush();
        em.getTransaction().commit();
        em.close(); // ADDED <<<<<<<<<

        LocationRelation lr = new LocationRelation();

        l1.addChildRelation(lr);
        l2.addParentRelation(lr);

        em = emf.createEntityManager(); // ADDED <<<<<<<
        em.getTransaction().begin();
        em.merge(l1);
        em.flush();
        em.getTransaction().commit();

        em.close();
        emf.close();
    }


I will probably debug hibernate code to see why this is happening but as far as I know you are better off not using hibernate session after it is committed. I don't remember where I saw this recommendation. There also might be some tricks on how to use extended sessions.

Farzad-


Top
 Profile  
 
 Post subject:
PostPosted: Tue Dec 11, 2007 5:58 pm 
Newbie

Joined: Fri Sep 15, 2006 10:29 am
Posts: 14
Thanks Farzad, you are very helpful. I will investigate further this issue, too.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Dec 13, 2007 10:57 am 
Newbie

Joined: Fri Sep 15, 2006 10:29 am
Posts: 14
Hi,

I tried my example with Oracle TopLink Essentials. It works. Bye-bye Hibernate? :(


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 10 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.