-->
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.  [ 9 posts ] 
Author Message
 Post subject: Problem with saving after StaleObjectStateException.
PostPosted: Tue May 11, 2004 11:18 am 
Newbie

Joined: Tue May 11, 2004 10:47 am
Posts: 16
I'm having trouble trying to save objects after having received a StaleObjectStateException. Here's the background:

I have three classes: Class A which contains a Set (one-to-many, cascade=all) of class B which contains an instance (many-to-one, cascade=all) of class C.

Class A has a version property for recognizing stale objects.

I have several message-driven beans running in parallel, all modifying the same instance of A, but each modifying a separate instance of B and associating with it a new instance of C.

So, in a MDB I have something like this:

Code:
void saveObjects(C c) {
    //  NOTE:  'c' is a transient object which I can't duplicate.
   boolean success = false;
   while (!success) {
       success = true;
       session = // get new Hibernate session
       session.beginTransaction();
       try {
            A a = // retrieve the instance of a from the database
            a.setSomeFieldValuesHere...

            B b = // get correct instance of B from 'a'
            b.setC(c);  // associated 'b' with new transient object 'c'.

           session.saveOrUpdate(a);
           session.commit();
       } catch (StaleObjectException e) {
           success = false;
           session.rollback();
       } finally {
           session.close();
       }

   }  // end of while loop
} // end of saveObjects


Ok, so if too many MDB's are running in parallel, I get StaleObjectStateException's, as expected, and try to repeat the saving task. My problem is that if the save fails the first time, it seems that the ID values of object 'c' are set to new values instead of the 0's used to indicate an unsaved object, so when trying to save it a second time I think 'c' is treated as an already persisted object instead of a new object, and I receive an error like:

Caused by: COM.ibm.db2.jdbc.DB2Exception: [IBM][CLI Driver][DB2/SUN] SQL0530N The insert or update value of the FOREIGN KEY "DB2.B.FKABCDEFG" is not equal to any value of the parent key of the parent table. SQLSTATE=23503

Other than manually resetting the ID of 'c' to 0, and iterating through all its contained cascaded objects and setting their id's back to 0 - which is not trivial in my case - is there any way to get around this problem?

Thanks for any help....

Luke
Code:


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 11, 2004 2:14 pm 
Newbie

Joined: Wed Jan 07, 2004 11:34 am
Posts: 11
Location: London, UK
This is in chapter 20 of the manual:

Quote:
Don't treat exceptions as recoverable.
This is more of a necessary paractice than a "best" practice. When an exception occurs, roll back the Transaction and close the Session. If you don't, Hibernate can't guarantee that in-memory state accurately represents persistent state. As a special case of this, do not use Session.load() to determine if an instance with the given identifier exists on the database; use find() instead.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 11, 2004 2:38 pm 
Newbie

Joined: Tue May 11, 2004 10:47 am
Posts: 16
Thanks for your reply.

Quote:
Don't treat exceptions as recoverable.
This is more of a necessary paractice than a "best" practice. When an exception occurs, roll back the Transaction and close the Session. If you don't, Hibernate can't guarantee that in-memory state accurately represents persistent state. As a special case of this, do not use Session.load() to determine if an instance with the given identifier exists on the database; use find() instead.


I am not treating the exception as recoverable, and I am calling session.rollback() in the catch block. If there is an exception, I rollback and close the session and then open a new one with a new transaction - the problem, however, is that the session that I have rolled back has still left its imprint on my transient object 'c' by updating its ID field, and the ID fields of its contained objects. Thus, the next time I try to save it (in a new session) I get an error because the ID values are non-zero but don't correspond to an actual database entry. Maybe the only solution is to reset the ID's manually, but if there's another way that would be great...

Luke


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 11, 2004 3:39 pm 
Newbie

Joined: Wed Jan 07, 2004 11:34 am
Posts: 11
Location: London, UK
I think this is what I would expect - the extract from the manual is saying exactly this -

Quote:
If you don't, Hibernate can't guarantee that in-memory state accurately represents persistent state


Your object 'C' is not really a transient object - it is a persistent object that hasn't yet been persisted.

Have you thought of newing your 'C' inside this method, using a true transient object to pass in your parameters. If you need a copy of 'C' after you have updated the database, return it from the method.

In general have a bunch of MDBs running like this always runs into these kind of problems. You need to synchronize in some way to avoid this problem. One way of doing this is to hold the lock on the select. This can have downsides - if you're not careful you can run into increased deadlocks and reduce concurrency.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 11, 2004 4:07 pm 
Newbie

Joined: Tue May 11, 2004 10:47 am
Posts: 16
Thanks again for your response...


Quote:
Your object 'C' is not really a transient object - it is a persistent object that hasn't yet been persisted.

Ok... maybe I was using the wrong terminology. You're right that it's an object that has yet to be saved in the database.

Quote:
If you don't [rollback the transaction and close the session], Hibernate can't guarantee that in-memory state accurately represents persistent state

This doesn't really apply because I do rollback the transaction and close the session, but the in-memory state of 'c' is still incorrect, in that 'c' has a non-zero ID which implies that it is contained in the database.

Quote:
Have you thought of newing your 'C' inside this method, using a true transient object to pass in your parameters. If you need a copy of 'C' after you have updated the database, return it from the method.


Unfortunately I'm working with the constraint that I'm supposed to persist the instance (not a copy/clone) that is passed to my method. I can't change the caller to use a transient object as you have suggested... When you say "...newing your 'C'..." I assume you mean create a new instance of 'C', which unfortunately isn't an option for me.

Anyways, maybe I'm being too demanding and just have to change the ID's manually. Still if there's an easier way... anyone?... anyone?... Bueller?...

Luke


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 11, 2004 4:56 pm 
Newbie

Joined: Wed Jan 07, 2004 11:34 am
Posts: 11
Location: London, UK
Fry....Fry....Fry...


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 11, 2004 6:46 pm 
Newbie

Joined: Tue May 11, 2004 10:47 am
Posts: 16
Quote:
In general have a bunch of MDBs running like this always runs into these kind of problems. You need to synchronize in some way to avoid this problem. One way of doing this is to hold the lock on the select. This can have downsides - if you're not careful you can run into increased deadlocks and reduce concurrency.


Does anyone know if there's a standard way of doing accomplishing this? I was of the impression that locking objects is pretty much frowned upon because as the application grows it becomes difficult to avoid deadlocks.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 11, 2004 6:56 pm 
Newbie

Joined: Wed Jan 07, 2004 11:34 am
Posts: 11
Location: London, UK
Sorry - it's me again.

A few ways that we have solved this -

First is to break all the rules and implement a lock map in the container that effectively stops the same data being updated at the same time by two MDBs. You need to ensure that you are using row level locking and not page level or you won't get around the deadlocks. Not a solution for clustered servers.

The second is to only put on MDB in your pool. Surpisingly, the performance isn't normally that bad. If you are using a server like Websphere that implements MDB message aggregation - this can help you out with the performance (where a single transaction processes many messages).

The third is to use a filter/router MDB in front of your business MDBs that routes out the messages on to a number of queues, keyed such that you ensure that the same row will be processed by one repeatably specific queue. You then use only one MDB per queue. Hence if you want 10 concurrent updates you split your data across 10 queues each with one MDB listening (I hope this makes sense).

We've got all of these variants in systems we run.

- Nick


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 11, 2004 8:12 pm 
Newbie

Joined: Tue May 11, 2004 10:47 am
Posts: 16
Thanks for all your help...

Luke


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