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.  [ 6 posts ] 
Author Message
 Post subject: OneToOne with JoinTable -Exception during persist w/ cascade
PostPosted: Mon Feb 02, 2009 11:31 am 
Beginner
Beginner

Joined: Tue Sep 02, 2003 9:28 pm
Posts: 25
I'm trying to map a bi-directional lazy OneToOne relationship using a JoinTable to avoid having Hibernate eagerly load the related object [see http://www.hibernate.org/162.html, http://opensource.atlassian.com/projects/hibernate/browse/HHH-3653].

The use of the JoinTable does avoid the eager load of the related object, however an exception is generated during the persisting of the object graph when the OneToOne mapping with the JoinTable is mapped with cascade=Cascade.ALL. If the cascade is not specified, the persisting of the object graph works correctly.

Shouldn't the cascade work in this situation?



Hibernate version:

Hibernate Core 3.3.1.GA
Hibernate Annotations 3.4.0 GA
Hibernate EntityManager 3.4.0 GA



Schema:

Code:
create table PERSON (
    PERSON_ID NUMBER(21) PRIMARY KEY
);

create table STUDENT (
    STUDENT_ID NUMBER(21) PRIMARY KEY
);

create table PERSON_STUDENT (
    PERSON_ID NUMBER(21) NOT NULL REFERENCES PERSON(PERSON_ID),
    STUDENT_ID NUMBER(21) NOT NULL REFERENCES STUDENT(STUDENT_ID),
    PRIMARY KEY (PERSON_ID, STUDENT_ID)
);




Annotated Classes:
Code:
// Annotated Person Entity
@Entity
@Table(name="PERSON")
public class Person {

    @Id
    @GeneratedValue
    @Column(name="PERSON_ID")
    private Long id;
   
    @OneToOne(optional=true,
              fetch=FetchType.LAZY,
              cascade = CascadeType.ALL  // Removing the cascade will allow the persist to work
    )
    @JoinTable(name = "PERSON_STUDENT",
               joinColumns = @JoinColumn(name="PERSON_ID"),
               inverseJoinColumns = @JoinColumn(name="STUDENT_ID")
    )
    private Student student;

    public Long getId() {
        return id;
    }

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }
}


Code:
// Annotated Student Entity
@Entity
@Table(name="STUDENT")
public class Student {

    @Id
    @GeneratedValue
    @Column(name="STUDENT_ID")
    private Long id;

    @OneToOne(mappedBy="student",
              optional=false,
              fetch=FetchType.LAZY
    )
    private Person person;

    public Long getId() {
        return id;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }

}




Persist Test:
Code:
public class OneToOnePersistTest {

    public static void main(String[] args) throws Exception {
        EntityManagerFactory emf = null;
        EntityManager em = null;

        try {
            emf = Persistence.createEntityManagerFactory("onetoonetest");
            em = emf.createEntityManager();

            em.getTransaction().begin();

            Person person = new Person();
            Student student = new Student();

            person.setStudent(student);
            student.setPerson(person); 

            em.persist(person);
            //em.persist(student);  // need this if cascade is not specified
            em.getTransaction().commit();
        }
        catch (Exception e) {
            if (em != null && em.getTransaction().isActive())
                em.getTransaction().rollback();
            throw e;
        }
        finally {
            if (em != null) em.close();
            if (emf != null) emf.close();
        }
    }
}



Stack Trace of Exception:
Code:
Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.PropertyValueException: not-null property references a null or transient value: entities.Student.person
   at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:614)
   at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:226)
   at test.OneToOnePersistTest.main(OneToOnePersistTest.java:28)
   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 com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)
Caused by: org.hibernate.PropertyValueException: not-null property references a null or transient value: entities.Student.person
   at org.hibernate.engine.Nullability.checkNullability(Nullability.java:95)
   at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:313)
   at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
   at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:144)
   at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:49)
   at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:154)
   at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:110)
   at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:636)
   at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:628)
   at org.hibernate.engine.EJB3CascadingAction$1.cascade(EJB3CascadingAction.java:28)
   at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:291)
   at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:239)
   at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192)
   at org.hibernate.engine.Cascade.cascade(Cascade.java:153)
   at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:454)
   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:144)
   at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:49)
   at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:154)
   at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:110)
   at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
   at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:645)
   at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:619)
   at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:623)
   at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:220)
   ... 6 more



Generated SQL:
Code:
Hibernate:
    select
        hibernate_sequence.nextval
    from
        dual
Hibernate:
    select
        hibernate_sequence.nextval
    from
        dual



Database:

Oracle 10g


Top
 Profile  
 
 Post subject: related JIRA issue?
PostPosted: Tue Feb 03, 2009 10:50 pm 
Beginner
Beginner

Joined: Tue Sep 02, 2003 9:28 pm
Posts: 25
I've found a JIRA issue that sounds similar: http://opensource.atlassian.com/projects/hibernate/browse/HHH-3544.

Maybe the problems are one in the same or at least related?

-Jay


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 3:47 am 
Expert
Expert

Joined: Thu Jan 08, 2009 6:16 am
Posts: 661
Location: Germany
To avoid eager loading on person's side you don't need a JoinTable on OneToOne. You could also use a JoinColumn (not the primary key-column).

On students side there should be no eager loading, as you have mapped the association as not optional.

_________________
-----------------
Need advanced help? http://www.viada.eu


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 11:01 am 
Beginner
Beginner

Joined: Tue Sep 02, 2003 9:28 pm
Posts: 25
Since this bidirectional relationship is optional from Person->Student, normally I would use a FK mapping and model a FK in the Student table that references the PK of the Person table. This causes Hibernate to eagerly load the Student object when the Person object is retrieved - since it has to query the Student table to see if one exists for this particular Person object, it goes ahead and retrieves the entire Student object. The use of the JoinTable eliminates the eager load in this case, but the persist with cascade throws an exception.

You wrote that the JoinTable is not needed to avoid eager loading on the person's side. Could you be more specific? I'd be interested in learning how to solve it this way.

Thanks for your help.
-Jay


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 11:19 am 
Expert
Expert

Joined: Thu Jan 08, 2009 6:16 am
Posts: 661
Location: Germany
To be more specific: If you use a JoinColumn instead of a PrimaryKeyJoinColumn, hibernate knows that the referenced Entity does exist or not, if the JoinColumn is null. A primary key is never null, so when a primary key is used as JoinColumn, it is still possible that the referenced entity does not exists, even when the PK is not null (which it never is). So hibernate must do the select on the referenced table to see if it exists.

In my example I have the following mapping, which works fine and does no eager loading:
Code:
    @OneToOne(fetch=FetchType.LAZY, optional=true)
    @JoinColumn(name = "toOne_id"))


See also thistopic, for some more info about one-to-one.
Rating is appreciated. ;-)

_________________
-----------------
Need advanced help? http://www.viada.eu


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 04, 2009 1:42 pm 
Beginner
Beginner

Joined: Tue Sep 02, 2003 9:28 pm
Posts: 25
If I understand you correctly, you're suggesting I put the FK for Student in the Person table. That way Hibernate will be able to use the fact of whether the FK is null or not to determine if it can/should build a proxy. However, in my case, I'm trying to model the FK the other way around (FK for Person in the Student table so it will never be null since this relationship is not optional). This is based on some discussions/requirements with the project's DBA and other concerns with the schema (what I've shown here is a very simplified view).

However, even if I move the FK to the Person side of the relationship as you suggest, then I get the same problem on the Student side since the FK is now in the other table - even if I map the relationship with optional=false. When I retrieve a Student, Hibernate still queries the associated Person table and ends up eager loading the Person object.

Trying to work around this issue of the eager loading of the OneToOnes is what led me to the use of the JoinTable. This mapping allows for true lazy loading of the OneToOne related objects from both directions. I'm just battling the exception that I get when persisting with cascade.

-Jay


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