-->
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.  [ 7 posts ] 
Author Message
 Post subject: Incorrect order of persistence for dependent entities
PostPosted: Sun Apr 30, 2006 4:07 am 
Newbie

Joined: Fri Mar 31, 2006 5:18 am
Posts: 9
Hi, I have a problem whereby two entities that have a bidirectional relationship are persisted in the wrong order.

In some cases I discover this by a foreign key being broken, in others (such as the following example) by the primary key generator generating a null primary key. It is the hibernate specific 'foreign' key generator.

I have done some thorough testing and have a worked example of this in action that I will paste at the end of the message. Basically though, changing 'optional=false' to 'optional=true' in the first entitys relationship to the second fixes it. That said, in my domain model both ends of this bi-directional relationship are definately not optional, so I have a problem setting that to true.

I could stop using the 'foreign' key generator and stop having the inverse relationship marked as '@PrimaryKeyJoinColumn', but I feel there are efficiencies to be had by having identical primary keys, and would like to continue.

I am using the following versions of hibernate software:
hibernate-3.2.0.cr1
hibernate-annotations-3.1.0.Beta10
hibernate-entitymanager-3.1.0.Beta8
hibernatetools-3.1.0.beta5

Any help or advice would be appreciated.
Many thanks,
Michael.



The following are my test entities with a unit test:

Code:
package com.washery.test;

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

@Entity()
public class First
{
    @Id()
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
   
    @OneToOne(fetch = FetchType.LAZY, optional = false, mappedBy="first", cascade=CascadeType.ALL)
    private Second second;
   
   
    public First()
    {
       
    }
   
   
    public Long getId()
    {
        return id;
    }
    public void setId(Long id)
    {
        this.id = id;
    }
   
    public Second getSecond()
    {
        return second;
    }
    public void setSecond(Second second)
    {
        this.second = second;
    }
}


Code:
package com.washery.test;

import javax.persistence.*;

@Entity()
public class Second
{
    @Id()
    @GeneratedValue(generator = "system-foreign")
    @org.hibernate.annotations.GenericGenerator(name = "system-foreign", strategy = "foreign", parameters = {
        @org.hibernate.annotations.Parameter(name = "property", value = "first")
    })
    private Long id;
   
    @OneToOne(fetch = FetchType.LAZY, optional = false)
    @PrimaryKeyJoinColumn()
    private First first;
   
   
    public Second()
    {
       
    }
    public Second(First first)
    {
        this.first = first;
        first.setSecond(this);
    }
   
   
    public Long getId()
    {
        return id;
    }
    public void setId(Long id)
    {
        this.id = id;
    }
   
    public First getFirst()
    {
        return first;
    }
    public void setFirst(First first)
    {
        this.first = first;
    }
}


Code:
package com.washery.test;

import junit.framework.*;
import javax.persistence.*;
import java.util.*;

public class FirstTest extends TestCase
{
    public FirstTest(String testName)
    {
        super(testName);
    }
   
    protected EntityManagerFactory emf;
    protected EntityManager         em;

    public static Test suite()
    {
        TestSuite suite = new TestSuite(FirstTest.class);
       
        return suite;
    }
   
    public void setUp() throws Exception
    {

        emf = Persistence.createEntityManagerFactory("dating_test", new HashMap());
        em = emf.createEntityManager();
        em.getTransaction().begin();
    }
   
    public void tearDown() throws Exception
    {
        if (em.getTransaction().isActive()) {
            em.getTransaction().rollback();
        }
        em.close();
        emf.close();
    }

    public void testPersist() throws Exception
    {
        First first = new First();
        Second second = new Second(first);
       
        em.persist(first);
        em.getTransaction().commit();
    }
}


Here is the generated ddl:

Code:
    create table First (
        id bigint not null auto_increment,
        primary key (id)
    ) type=InnoDB;

    create table Second (
        id bigint not null,
        primary key (id)
    ) type=InnoDB;

    alter table Second
        add index FK93603094B29B2AFD (id),
        add constraint FK93603094B29B2AFD
        foreign key (id)
        references First (id);


Here is the exception:

Code:
Testcase: testPersist(com.washery.test.FirstTest):   Caused an ERROR
org.hibernate.id.IdentifierGenerationException: null id generated for:class com.washery.test.Second
javax.persistence.PersistenceException: org.hibernate.id.IdentifierGenerationException: null id generated for:class com.washery.test.Second
        at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:560)
        at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:192)
        at com.washery.test.FirstTest.testPersist(FirstTest.java:58)
Caused by: org.hibernate.id.IdentifierGenerationException: null id generated for:class com.washery.test.Second
        at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:102)
        at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:131)
        at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:87)
        at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:608)
        at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:600)
        at org.hibernate.engine.CascadingAction$8.cascade(CascadingAction.java:202)
        at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:213)
        at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:157)
        at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:108)
        at org.hibernate.engine.Cascade.cascade(Cascade.java:248)
        at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:412)
        at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:261)
        at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:180)
        at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:108)
        at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:131)
        at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:87)
        at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:38)
        at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:617)
        at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:591)
        at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:595)
        at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:186)
        ... 16 more




[/code]


Top
 Profile  
 
 Post subject:
PostPosted: Mon Nov 19, 2007 9:38 am 
Newbie

Joined: Sat Oct 30, 2004 3:48 pm
Posts: 14
I have the same exact problem with

hibernate-3.2.5.jar
hibernate-annotations-3.3.0.jar
hibernate-commons-annotations.jar
hibernate-entitymanager-3.3.1.jar
hibernate-search-3.0.0.jar
hibernate-validator-3.0.0.jar

Only way to fix is by making the parent entity have an optional child entity.

Is this just incorrect usage or a long standing bug?

The only difference in my situation to this one is i'm not using an auto generated key on the parent entity.

I'm using a org.hibernate.id.enhanced.SequenceStyleGenerator

So, hibernate will try to do the insert but RI will be violated


Top
 Profile  
 
 Post subject:
PostPosted: Mon Nov 19, 2007 10:11 am 
Newbie

Joined: Sat Oct 30, 2004 3:48 pm
Posts: 14
Forgot to mention,

The reason I don't want to map it with optional = false, is with this setting it is not possible to lazy load the child.

And mapping it without a shared primary key should not be required


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jan 19, 2008 8:54 pm 
Newbie

Joined: Sat Oct 30, 2004 3:48 pm
Posts: 14
dwsmith75 wrote:
Forgot to mention,

The reason I don't want to map it with optional = false, is with this setting it is not possible to lazy load the child.

And mapping it without a shared primary key should not be required


This should have stated:

The reason i don't want to map the Parent.child with optional = false, is this will not allow me to lazy load the child.


I've been trying to get this to work all day, could someone please provide an example of a OneToOne with:
    JPA annotations
    Shared primary key
    A foreign key from child table to the parent table
    The ability to lazy load the child from the Parent


From all my experimentation i believe this is truly a bug. Should a raise a JIRA issue?


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jan 19, 2008 9:48 pm 
Newbie

Joined: Sat Oct 30, 2004 3:48 pm
Posts: 14
Forgot I should post an example of what i'm talking about

Code:
@Entity
@Table(name="TEST_FIRST")
public class First implements Serializable {

   @Id
   @GeneratedValue(generator = "idGenerator")
   @GenericGenerator( name = "idGenerator",
       strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
       parameters = {
           @Parameter(name = "force_table_use", value = "true")
       }
   )   
   private Long id;

   @OneToOne(cascade = CascadeType.ALL, optional = false , fetch = FetchType.LAZY, mappedBy="first")
   @PrimaryKeyJoinColumn
   private Second second;
....
}


Code:
@Entity
@Table(name="TEST_SECOND")
public class Second implements Serializable {
   

   @Id
   @GeneratedValue(generator = "secondFKeyGenerator")
   @GenericGenerator(name = "secondFKeyGenerator",
         strategy = "foreign",
         parameters = @Parameter(name = "property", value = "first")
         )
   private Long id;

   @OneToOne(fetch = FetchType.LAZY, optional = false)
   @PrimaryKeyJoinColumn
   private First first;
...
}


[01/19 20:43:58] DEBUG [org.hibernate.tool.hbm2ddl.SchemaExport] - <create table TEST_FIRST (id bigint not null, primary key (id))>
[01/19 20:43:58] DEBUG [org.hibernate.tool.hbm2ddl.SchemaExport] - <create table TEST_SECOND (id bigint not null, primary key (id))>
[01/19 20:43:58] DEBUG [org.hibernate.tool.hbm2ddl.SchemaExport] - <alter table TEST_SECOND add constraint FKDE710C1188F241B foreign key (id) references TEST_FIRST>
...
...
persist================================================
[01/19 20:45:50] TRACE [org.hibernate.event.def.AbstractSaveEventListener] - <transient instance of: eg.test.First>
[01/19 20:45:50] TRACE [org.hibernate.event.def.DefaultPersistEventListener] - <saving transient instance>
[01/19 20:45:50] DEBUG [org.hibernate.jdbc.AbstractBatcher] - <opening JDBC connection>
[01/19 20:45:50] TRACE [org.hibernate.connection.DriverManagerConnectionProvider] - <total checked-out connections: 1>
[01/19 20:45:50] DEBUG [org.hibernate.connection.DriverManagerConnectionProvider] - <opening new JDBC connection>
[01/19 20:45:50] DEBUG [org.hibernate.connection.DriverManagerConnectionProvider] - <created connection to: jdbc:hsqldb:., Isolation Level: 2>
[01/19 20:45:50] DEBUG [org.hibernate.SQL] - <select next_val id_val from hibernate_sequence>
[01/19 20:45:50] DEBUG [org.hibernate.SQL] - <update hibernate_sequence set next_val= ? where next_val=?>
[01/19 20:45:50] DEBUG [org.hibernate.jdbc.AbstractBatcher] - <closing JDBC connection (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)>
[01/19 20:45:50] TRACE [org.hibernate.connection.DriverManagerConnectionProvider] - <returning connection to pool, pool size: 1>
[01/19 20:45:50] DEBUG [org.hibernate.event.def.AbstractSaveEventListener] - <generated identifier: 1, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator>
[01/19 20:45:50] TRACE [org.hibernate.event.def.AbstractSaveEventListener] - <saving [eg.test.First#1]>
[01/19 20:45:50] TRACE [org.hibernate.engine.Cascade] - <processing cascade ACTION_PERSIST_SKIPLAZY for: eg.test.First>
[01/19 20:45:50] TRACE [org.hibernate.engine.CascadingAction] - <cascading to persist: eg.test.Second>
[01/19 20:45:50] TRACE [org.hibernate.event.def.AbstractSaveEventListener] - <transient instance of: eg.test.Second>
[01/19 20:45:50] TRACE [org.hibernate.event.def.DefaultPersistEventListener] - <saving transient instance>
[01/19 20:45:50] DEBUG [org.hibernate.event.def.AbstractSaveEventListener] - <generated identifier: 1, using strategy: org.hibernate.id.ForeignGenerator>
[01/19 20:45:50] TRACE [org.hibernate.event.def.AbstractSaveEventListener] - <saving [eg.test.Second#1]>
[01/19 20:45:50] TRACE [org.hibernate.engine.Cascade] - <done processing cascade ACTION_PERSIST_SKIPLAZY for: eg.test.First>
[01/19 20:45:50] TRACE [org.hibernate.engine.Cascade] - <processing cascade ACTION_PERSIST_SKIPLAZY for: eg.test.First>
[01/19 20:45:50] TRACE [org.hibernate.engine.Cascade] - <done processing cascade ACTION_PERSIST_SKIPLAZY for: eg.test.First>
commit================================================
[01/19 20:45:50] DEBUG [org.hibernate.transaction.JDBCTransaction] - <commit>
[01/19 20:45:50] TRACE [org.hibernate.impl.SessionImpl] - <automatically flushing session>
[01/19 20:45:50] TRACE [org.hibernate.event.def.AbstractFlushingEventListener] - <flushing session>
[01/19 20:45:50] DEBUG [org.hibernate.event.def.AbstractFlushingEventListener] - <processing flush-time cascades>
[01/19 20:45:50] TRACE [org.hibernate.engine.Cascade] - <processing cascade ACTION_PERSIST_ON_FLUSH for: eg.test.Second>
[01/19 20:45:50] TRACE [org.hibernate.engine.Cascade] - <done processing cascade ACTION_PERSIST_ON_FLUSH for: eg.test.Second>
[01/19 20:45:50] TRACE [org.hibernate.engine.Cascade] - <processing cascade ACTION_PERSIST_ON_FLUSH for: eg.test.First>
[01/19 20:45:50] TRACE [org.hibernate.engine.CascadingAction] - <cascading to persistOnFlush: eg.test.Second>
[01/19 20:45:50] TRACE [org.hibernate.event.def.AbstractSaveEventListener] - <persistent instance of: eg.test.Second>
[01/19 20:45:50] TRACE [org.hibernate.event.def.DefaultPersistEventListener] - <ignoring persistent instance>
[01/19 20:45:50] TRACE [org.hibernate.engine.Cascade] - <processing cascade ACTION_PERSIST_ON_FLUSH for: eg.test.Second>
[01/19 20:45:50] TRACE [org.hibernate.engine.Cascade] - <done processing cascade ACTION_PERSIST_ON_FLUSH for: eg.test.Second>
[01/19 20:45:50] TRACE [org.hibernate.engine.Cascade] - <processing cascade ACTION_PERSIST_ON_FLUSH for: eg.test.Second>
[01/19 20:45:50] TRACE [org.hibernate.engine.Cascade] - <done processing cascade ACTION_PERSIST_ON_FLUSH for: eg.test.Second>
[01/19 20:45:50] TRACE [org.hibernate.engine.Cascade] - <done processing cascade ACTION_PERSIST_ON_FLUSH for: eg.test.First>
[01/19 20:45:50] DEBUG [org.hibernate.event.def.AbstractFlushingEventListener] - <dirty checking collections>
[01/19 20:45:50] TRACE [org.hibernate.event.def.AbstractFlushingEventListener] - <Flushing entities and processing referenced collections>
[01/19 20:45:50] TRACE [org.hibernate.event.def.AbstractFlushingEventListener] - <Processing unreferenced collections>
[01/19 20:45:50] TRACE [org.hibernate.event.def.AbstractFlushingEventListener] - <Scheduling collection removes/(re)creates/updates>
[01/19 20:45:50] DEBUG [org.hibernate.event.def.AbstractFlushingEventListener] - <Flushed: 2 insertions, 0 updates, 0 deletions to 2 objects>
[01/19 20:45:50] DEBUG [org.hibernate.event.def.AbstractFlushingEventListener] - <Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections>
[01/19 20:45:50] DEBUG [org.hibernate.pretty.Printer] - <listing entities:>
[01/19 20:45:50] DEBUG [org.hibernate.pretty.Printer] - <eg.test.Second{first=eg.test.First#1, id=1}>
[01/19 20:45:50] DEBUG [org.hibernate.pretty.Printer] - <eg.test.First{second=eg.test.Second#1, id=1}>
[01/19 20:45:50] TRACE [org.hibernate.event.def.AbstractFlushingEventListener] - <executing flush>
[01/19 20:45:50] TRACE [org.hibernate.jdbc.ConnectionManager] - <registering flush begin>
[01/19 20:45:50] TRACE [org.hibernate.persister.entity.AbstractEntityPersister] - <Inserting entity: [eg.test.Second#1]>
[01/19 20:45:50] DEBUG [org.hibernate.jdbc.AbstractBatcher] - <about to open PreparedStatement (open PreparedStatements: 0, globally: 0)>
[01/19 20:45:50] DEBUG [org.hibernate.SQL] - <insert into TEST_SECOND (id) values (?)>
[01/19 20:45:50] TRACE [org.hibernate.jdbc.AbstractBatcher] - <preparing statement>
[01/19 20:45:50] TRACE [org.hibernate.persister.entity.AbstractEntityPersister] - <Dehydrating entity: [eg.test.Second#1]>
[01/19 20:45:50] TRACE [org.hibernate.type.LongType] - <binding '1' to parameter: 1>
[01/19 20:45:50] TRACE [org.hibernate.persister.entity.AbstractEntityPersister] - <Inserting entity: [eg.test.First#1]>
[01/19 20:45:50] DEBUG [org.hibernate.jdbc.AbstractBatcher] - <Executing batch size: 1>
[01/19 20:45:50] DEBUG [org.hibernate.jdbc.AbstractBatcher] - <about to close PreparedStatement (open PreparedStatements: 1, globally: 1)>
[01/19 20:45:50] TRACE [org.hibernate.jdbc.AbstractBatcher] - <closing statement>
[01/19 20:45:50] DEBUG [org.hibernate.util.JDBCExceptionReporter] - <Could not execute JDBC batch update [insert into TEST_SECOND (id) values (?)]>
java.sql.BatchUpdateException: failed batch


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 12, 2008 6:32 pm 
Newbie

Joined: Fri Jul 25, 2008 5:22 am
Posts: 12
So this is the solution? Make the relationship optional? DBAs and data modelers are expected to break their relational rules to satisfy the tool?

I see three solutions:

1.) Do what the original poster suggests which is break the relational rule to satisfy the limitations of the tool.
2.) Generate keys for both which would require checking in code for synchronization
3.) Don't use Hibernate

If using solution one and the user wishes to have optional, then normalization rules are broken (down to 0 normal form, AKA unnormalized). Solution two requires coding to do the checking.

A one-to-one optional relationship is a pretty common relationship to have to model. Am I to believe that Hibernate is this broken?

_________________
“The Edge... there is no honest way to explain it because the only people who really know where it is are the ones who have gone over.” - Hunter S. Thompson


Top
 Profile  
 
 Post subject:
PostPosted: Sun Nov 23, 2008 7:36 am 
Beginner
Beginner

Joined: Thu Jun 21, 2007 1:47 pm
Posts: 46
It's actually the lazy one-to-one that gets you in trouble. If the target object is optional, hibernate has to check whether it exists so it can set the field to null or not null. This is because hibernate allows you to access fields direclty instead of using the getter (if it intercepted the getter, like OpenJPA, it could check for null at that time).

To have an optional inverse one-to-one you have to put a OneToMany on the inverse side and use isEmpty() to check for null, and iterator().next() to get the value. It's a pain, but it's also a trade-off that was made a while back in hibernate.


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