-->
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.  [ 21 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: Unexpected 'detached entity passed to persist' error
PostPosted: Thu Jan 25, 2007 5:00 pm 
Beginner
Beginner

Joined: Thu Jan 25, 2007 3:36 pm
Posts: 25
Hibernate version: 3.2.0.GA
Database: MySQL 5.0.27


Hello

I have an entity (let's call it Person) with a One-To-One relationship to another entity (Address) which has cascade-all specified.

Both entities have a Primary Key which is an int.

The Person entity also has accountSystem and accountNumber attributes. The accountNumber attribute is randomly generated and assigned at the time of persistence. The bones of this process are as follows:

Code:
public void save( Person person ) {
    EntityManager     mgr;
    EntityTransaction txn;
    boolean           retryRequired;

    //Initialise:-
    em = .....
    em.setFlushMode( FlushModeType.COMMIT );
    txn = em.getTransaction();
    txn.begin();
    retryRequired = false;

    try {
        if( person.getId() == 0 ) {
            //New person - assign an account number and insert:-
            person.setAccountNumber( getRandomAccountNumber() );
            em.persist( person );
            ... do some other stuff ...
        } else {
            //Update existing person:-
            em.merge( person );
        }

        txn.commit();
    } catch( Exception x ) {
        txn.rollback();

        //Retry if the error was due to an account-number collision:-
        if( exceptionCausedByEntityExistsException(x) ) {
            retryRequired = true;
        }
    } finally {
        em.close();
    }


    //Retry if the error was due to an account-number collision:-
    if( retryRequired ) {
        warning( "Insert of person failed due to duplicate account number - retrying..." );
        save( person );
    }
}



The Person entity is declared as follows in orm.xml - note there's a unique constraint on the accountNumber and accountSystem attributes:-
Code:
<entity class="x.y.z.Person" access="FIELD">
    <table name="Person">
        <unique-constraint>
            <column-name>accountNumber</column-name>
            <column-name>accountSystem</column-name>
        </unique-constraint>
    </table>
    <attributes>
        <id name="id">
            <generated-value strategy="AUTO" />
        </id>

        <basic name="username" optional="false" />
        [snip]
        <basic name="accountSystem" optional="false" />
        <basic name="accountNumber" optional="false" />

        <one-to-one name="address" target-entity="x.y.z.Address" fetch="LAZY" optional="false">
            <cascade>
                <cascade-all />
            </cascade>
        </one-to-one>
    </attributes>
</entity>


There's nothing out of the ordinary in Address's declaration.

When I execute the program in order to create a bunch of new Persons, it all works great. I call save(new Person(...)) a number of times and each is saved along with their address details (due to cascade-all) and their unique random account numbers.

However, in order to test my exception handling, I temporarily modified my getRandomAccountNumber() method in order to always return 123. I ran the program again and the first Person is inserted successfully as before. The second Person's insert then fails, I get the warning message informing me that the a collision occured and a retry is about to take place. All as expected so far... except I then get a PersistenceException:

Code:
org.hibernate.PersistentObjectException: detached entity passed to persist: x.y.z.Address


I don't understand why the Address has become detached during the retry. Is this expected given the code above? If so, why is it attached the first time through? I thought by performing a rollback I'd cleaned up properly and subsequent passes through the save() method would exhibit the same behaviour as the first.

Many thanks in advance for any advice anyone is able to offer.
Darren


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jan 25, 2007 5:15 pm 
Beginner
Beginner

Joined: Thu Jan 25, 2007 3:36 pm
Posts: 25
Sorry to reply to my own post - just thought of something I should've added to my original description...

Just to clarify, the PersistenceObjectException is thrown from the em.persist(), which confirms to my mind that on the retry, the person is still recognised as a new entity and an insertion attempted. So, as I'm performing a persist() on a new entity, I expected the fact that the Address is also new (and detached) would be handled for me and that I wouldn't have to do anything to have it attached to the persistence context.

Stack trace looks like this:

Code:
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: x.y.z.Address
        at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:647)
        at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:218)
        at x.y.z.RepositoryManager.save(....)
        ... 12 more
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: x.y.z.Address
        at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:79)
        at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:609)
        at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:601)
        at org.hibernate.engine.CascadingAction$8.cascade(CascadingAction.java:295)
        at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)
        at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:216)
        at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
        at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
        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:618)
        at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:592)
        at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:596)
        at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:212)



Darren


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 31, 2007 7:53 am 
Beginner
Beginner

Joined: Thu Jan 25, 2007 3:36 pm
Posts: 25
I'm still trying to get to the bottom of this and have just noticed the following in my debugger:-

When my save(Person) method is called for the second person (the person that will cause a duplicate key exception), my entities have the following properties:
person.getId() = 0
person.getAddress().getId() = 0

No surprise there. Now entityManager.persist(person) is called, which throws an exception, the transaction is rolled back, the entity manager is closed and an exception thrown back to the caller of this method.

The caller detects that the exception was due to a duplicate key, calls person.setAccountNumber(someOtherValue) and calls save(Person) again (the re-invocation of the save() method was shown above in my example code as part of the save()'s logic, i.e. it called itself recursively, but I've changed this to keep things simple).

When I inspect the entities now (from the top of my save(Person) method), I see the following:
person.getId() = 0
person.getAddress().getId() = 16

So although I performed a rollback() (and this is confirmed when I look at the database), the Address entity which is new and which previously had ID=0 now has ID=16. Is this what's causing my problem? Is there something I should have done to reset this in addition to calling the txn.rollback()?

Many thanks to anyone who can shed some light.
Darren


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 31, 2007 9:15 am 
Beginner
Beginner

Joined: Thu Jan 25, 2007 3:36 pm
Posts: 25
OK - I've created a standalone example that replicates the problem.

Then I changed it so that when the second person's insertion fails but immediately before re-calling the save() method, I call person.getAddress.setId(0) and everything now works.

Is this expected behaviour? If I rollback an insertion and then want to retry it would I be expected to manually reset all related entitys' primary keys first?

Thanks
Darren


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 31, 2007 6:21 pm 
Regular
Regular

Joined: Mon Nov 14, 2005 7:33 pm
Posts: 73
Try using em.getReference() before calling persist().


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 01, 2007 2:51 pm 
Newbie

Joined: Wed May 03, 2006 5:09 pm
Posts: 17
The problem is that hibernate can't deal with you calling persist on a non-persisted entity (i.e. one that you've created which doesn't have a valid PK) which has, in one of it's relationships or somewhere within the chain of relationships, an entity that has already been persisted.

So if Class A references Class B and A is new but A.B is persisted, then you have to call the merge() method for it to work correctly. This could also be a deeper chain, something like A.B.C.D where D is persisted but A,B, and C are new. I'm currently trying to figure out if there is a way around this but no luck thus far.

D


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 01, 2007 2:57 pm 
Regular
Regular

Joined: Mon Nov 14, 2005 7:33 pm
Posts: 73
UTDrew wrote:
The problem is that hibernate can't deal with you calling persist on a non-persisted entity (i.e. one that you've created which doesn't have a valid PK) which has, in one of it's relationships or somewhere within the chain of relationships, an entity that has already been persisted.

So if Class A references Class B and A is new but A.B is persisted, then you have to call the merge() method for it to work correctly. This could also be a deeper chain, something like A.B.C.D where D is persisted but A,B, and C are new. I'm currently trying to figure out if there is a way around this but no luck thus far.

D


I have found this to be true of both Hibernate and Toplink (in Glassfish) in the context of EJB 3.0. Calling persist() really only works if the entity (and all related entities, if they are associated) is new and none have a populated primary key value.

I haven't done so; but you may want to check the EJB 3.0 spec to be sure that's not the expected, specified behavior.

You could always resort to extended persistence contexts if this stuff becomes too much of a pain for you.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 02, 2007 11:23 am 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
This is the expected behavior.Note that merge actually insert or update an object acordingly. This will not help you in this case.

What you need to do is to add hibernate.use_identifier_rollback = true in your persistence.xml file

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Tue Mar 20, 2007 1:20 pm 
Beginner
Beginner

Joined: Thu Jan 25, 2007 3:36 pm
Posts: 25
Thanks all for your comments.

Emmanuel - what you suggest:
Quote:
What you need to do is to add hibernate.use_identifier_rollback = true in your persistence.xml file

sounds ideal. I've added this, but it doesn't seem to make any difference. I.e. my persistence.xml now looks like this:

Code:
<persistence xmlns="..." xmlns:xsi="..." xsi:schemaLocation="..." version="1.0">
   <persistence-unit name="lgp-core" transaction-type="RESOURCE_LOCAL">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <jar-file>lib/lgp.client.jar</jar-file>

      <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
         <property name="hibernate.hbm2ddl.auto" value="update" />
         <property name="hibernate.show_sql"    value="true" />

         <property name="hibernate.use_identifier_rollback" value="true" />

         <property name="hibernate.connection.driver_class"   value="com.mysql.jdbc.Driver" />
         <property name="hibernate.connection.url"         value="jdbc:mysql://localhost/lgpcore" />
         <property name="hibernate.connection.username"      value="[snip]" />
         <property name="hibernate.connection.password"      value="[snip]" />
      </properties>
   </persistence-unit>
</persistence>


and when I start my project I see the following:

Code:
...
2007-03-20 16:46:01.548 INFO    org.hibernate.cfg.SettingsFactory       Deleted entity synthetic identifier rollback: enabled
...
2007-03-20 16:46:01.606 FINE    org.hibernate.impl.SessionFactoryImpl   instantiating session factory with properties: {...  hibernate.use_identifier_rollback=true...}
...



However, when I've run the test code again, the IDs of the child objects are still non-zero, i.e. person.getAddress().getId() does not return 0.

Have I missed something?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 22, 2007 12:57 am 
Newbie

Joined: Thu Mar 22, 2007 12:50 am
Posts: 1
I'm stumped by this as well. I've got the exact same scenario. I added hibernate.use_identifier_rollback = true to my persistence.xml to no avail.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 22, 2007 10:35 am 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
Not sure I made myself clear, using identifier rollback should be able to do something like that

Code:
Entity e = new Entity();
...
assertNull( e.getId() );

session.getTransaction().begin();
session.persist( e );
session.flush(); //be sure to create the id
assertNotNull( e.getId() );
session.getTransaction().rollback(); //important to remove the id

assertNull( e.getId() );

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 22, 2007 3:12 pm 
Beginner
Beginner

Joined: Thu Jan 25, 2007 3:36 pm
Posts: 25
Yeah, that's not far off what I've got. There are some minor differences, e.g. my primary key is an int rather than an object, so assertNull() etc. wouldn't work in my case, but other than that it's fairly close:


Code:
01        EntityManager     em;
02        EntityTransaction txn;

03        em = .....
04        em.setFlushMode( FlushModeType.COMMIT );
05        txn = em.getTransaction();
06        txn.begin();

07        try {
08            log( "person.getId()=" + person.getId() );
09            log( "person.getAddress().getId()=" + person.getAddress().getId() );

10            em.persist( person );

11            txn.commit();
12        } catch( Exception x ) {
13            txn.rollback();
14            log( "person.getId()=" + person.getId() );
15            log( "person.getAddress().getId()=" + person.getAddress().getId() );
16        } finally {
17            em.close();
18        }



Lines 8 and 9 both print zeros as neither the Person entity nor it's Address entity exist yet. Line 10 throws an exception as the Person.accountNumber contains a value that breaks a unique-constraint (deliberate for testing purposes).

Line 13 executes without a problem. Line 14 then prints person.getId()=0 as expected, but line 15 prints person.getAddress().getId()=2, despite the fact that the database rollback was successful and my identifier-rollback is enabled.

I'll put together a standalone example tomorrow that uses object-based IDs rather than ints, just in case that's causing the problem. If anyone has any comments in the meantime they would be much appreciated.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 22, 2007 3:37 pm 
Beginner
Beginner

Joined: Thu Jan 25, 2007 3:36 pm
Posts: 25
OK - that was a bit quicker to do than I expected - I changed all PKs from int to Integer, but still getting the same problem.

Lines 8 and 9 print id=null.

Line 14 prints id=null, but line 15 prints id=4 (i.e. not null) :-(


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 22, 2007 4:11 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
Can you attach your test case to JIRA so that the issue is not lost and solved more easily

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Fri Mar 23, 2007 7:49 am 
Beginner
Beginner

Joined: Thu Jan 25, 2007 3:36 pm
Posts: 25
Done - HHH-2516.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 21 posts ]  Go to page 1, 2  Next

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.