Hibernate Books

All times are UTC - 5 hours [ DST ]



Post new topic Reply to topic  [ 15 posts ] 
Author Message
 Post subject: Repeating save after a failure
PostPosted: Wed May 19, 2004 3:59 am 
Newbie

Joined: Wed May 19, 2004 3:50 am
Posts: 11
Hi,

I need to be able to repeat a save/update of an object after a failure. Example scenario: the user has edited a lot of data on a GUI form (Swing) which is backed by a persistent object model. They can't afford to lose this data.

Then the user saves the data behind the form in a fresh session (this is how the application works - neither a transaction nor connection can be held open for the lifetime the form is open, so the save is in a separate session from the original load). All this works fine.

However, if there's a failure in the save (e.g. database constraint violation) Hibernate leaves the object model in an inconsistent state and the docs say to throw the instance & its related collections away - which is not an option in this case (we'll lose the user's changes). Attempting a re-save always fails as expected from the documentation.

It appears that I should be able to use saveOrUpdateCopy() to fix this by passing in the object that failed to save the first time and then use the new object returned for further changes - is this right?

If so, will this handle changes to collections on that object correctly? - e.g. if I've added a new instance to a collection on the object I'm trying to save, when I use saveOrUpdateCopy() the second time around, will the new instance be correctly detected and inserted or will it incorrectly contain an Id set during the first save attempt?

Thanks in advance,


Tom.


Top
 Profile  
 
 Post subject: Re: Repeating save after a failure
PostPosted: Thu May 20, 2004 9:15 am 
Newbie

Joined: Wed May 19, 2004 3:50 am
Posts: 11
Can anyone help?

Looks to me like Hibernate can't cope with this scenario.


Tom.


Top
 Profile  
 
 Post subject:
PostPosted: Thu May 20, 2004 10:05 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1812
Location: Austin, TX
I have not tried as this has not been an issue for me ever, so I cannot say for sure.

Have you tried it? Does not sound like it. Why don't you just try it and see what happens. If you get an error / issue, post it and we'll try to help.


Top
 Profile  
 
 Post subject:
PostPosted: Thu May 20, 2004 10:21 am 
Newbie

Joined: Wed May 19, 2004 3:50 am
Posts: 11
Yes I've tried it.

It appears to save the second time but I get the following:

et.sf.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) for uk.co.idbs.product.impl.ProductImpl instance with identifier: 8a94b2fcfa346b2200fa347c20a6001e
at net.sf.hibernate.impl.SessionImpl.doCopy(SessionImpl.java:3982)
at net.sf.hibernate.impl.SessionImpl.saveOrUpdateCopy(SessionImpl.java:3938)

Now there's a whole load of complex stuff going on in my application that could be causing a problem so I'm just trying to find out if this should work in principle before I expend lots of effort trying to fix it.

Maybe I've phrased the question unclearly, so here's another go:

I have a Swing client forms application that when the user edits the form edits a persistence object model (POJO object graph loaded by Hibernate) offline (outside transaction). When the user hits save we grab a new Session, start a transaction and save/insert the object they've edited (which may include insertions/deletions/edits to collections on that object and other objects in the graph). My requirement is to be able to save the orignal object again (after further edits if necessary) after the first save fails due to a database constraint or other problem.

This seems a straightforward question about design (which is why I didn't post a trace the first time) - can I do this with Hibernate, and if so how?


Tom.


Top
 Profile  
 
 Post subject:
PostPosted: Thu May 20, 2004 3:15 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1812
Location: Austin, TX
Ah, because the ids are already assigned.

Not sure if there's a good way around this. Let me give it some thought.


Top
 Profile  
 
 Post subject:
PostPosted: Thu May 27, 2004 7:56 am 
Newbie

Joined: Wed May 19, 2004 3:50 am
Posts: 11
Any further thoughts?

It would be brilliant if Hibernate could remove any generated Ids if the transaction rolls back.


Tom.


Top
 Profile  
 
 Post subject: A possible fix
PostPosted: Mon Jun 07, 2004 8:52 pm 
Newbie

Joined: Mon Mar 08, 2004 1:41 pm
Posts: 5
I too have the same problem. I was looking around docs for some hints when I saw this, so have made the assumption that if steve is stumped (at least temporarily :-) that the fix must be more than a special method call, etc. Hope I'm correct!

Here is my best idea of what is going on.

The error will occur when you attempt to save a new object in a transaction, and that transaction fails for whatever reason. Hibernate keeps score of what passes through, so although the object is still transient (the flush failed), the object will now have an ID.

So, when you go to fix the error with the object, and attempt to re-save it, hibernate will fail because it sees the ID, assumes the object has been persisted, and shoots into its update routines. The update routines, however, expect the object to be in the database, and throw the error tomsedge2 reports when they do not find it.

So my attempted workarounds:
My first thought is to work with what the hibernate API gives me. I have a DAO layer that abstracts all this, so I record what is going on, and if something fails, I simply remove the ID on the object.

Hibernate keeps track of that too, however, and throws an error complaining about the id of my object changing to Null when I attempt to commit my transaction.

So I dove into hibernate, and this is what I have so far, which is working for my small test case. (test case is: start txn, save object I know will fail due to db constraints, rollback txn, fix errors in object, start txn, save object, commit txn)

You will find this function in net.sf.hibernate.impl.SessionImpl

Code:

   private Serializable doSave(
      final Object object,
      final Serializable id,
      final ClassPersister persister,
      final boolean useIdentityColumn,
      final Cascades.CascadingAction cascadeAction,
      final Object anything)
   throws HibernateException {

      if ( log.isTraceEnabled() ) log.trace( "saving " + MessageHelper.infoString(persister, id) );

      final Key key;
      if (useIdentityColumn) {
         // if the id is generated by the database, we assign the key later
         key = null;
      }
      else {
         key = new Key(id, persister);
         Object old = getEntity(key);
         if (old!= null) {
            EntityEntry e = getEntry(old);
            if (e.status==DELETED) {
               forceFlush(e);
            }
            else {
               throw new NonUniqueObjectException( id, persister.getMappedClass() );
            }
         }
         persister.setIdentifier(object, id);
      }

      // Sub-insertions should occur before containing insertion so
      // Try to do the callback now
      if ( persister.implementsLifecycle() ) {
         log.debug("calling onSave()");
         if ( ( (Lifecycle) object ).onSave(this) ) {
            log.debug("insertion vetoed by onSave()");
            return id;
         }
      }
               
//              ################# Begin Fix ##########################
                /* A hack to attempt & fix the save & fail / save again error.
                 * The general idea is to remove every trace of the object being inserted if
                 * something goes wrong.
                 *
                 * Possible problems with this fix are:
                 * 1. I am not a hibernate expert, this could break a great many things
                 * 2. I believe this code relies on the user of the hibernate API to rollback
                 *      (which isn't much to ask, it is required according to the docs)
                 *
                 * You *must* re-call save, or saveOrUpdate on the object in the new transaction.
                 */               
                try {
                    return doSave(object, key, persister, false, useIdentityColumn, cascadeAction, anything);
                } catch (HibernateException t) {
                    this.removeEntity(key);
                    this.removeEntry(object);
                    persister.setIdentifier(object, null);
                    throw t;
                }
//              ################# End Fix ##########################

   }


I do not expect this to be the be-all-to-end-all solution, but at least I can shed a little more light on the subject and some possible fixes.

Really, my "gut" reaction is that something other than ID's should be used to determine if an object is transient or persistent. It seems that hibernate implements methods to keep track of what happens to a specific object (hence it knowing that I changed the ID), perhaps those same methods could be used to determine if an object is transient or not.

That is all I have for the moment. I have never used Jira, etc., so I will look into that tonight when I get home and see if I can start something on that end as well.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jun 07, 2004 9:00 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
This fix is not, in general, sufficient.

The Hibernate docs are quite clear that all Hibernate exceptions are by nature unrecoverable. The reason for this is performance, rollback of the session state is prohibitively expensive.

You need to approach the problem differently.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jun 07, 2004 9:04 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
With respect to the original post, it is likely that we will change saveOrUpdateCopy() to save new instances by value rather than by reference in Hibernate3. We cannot make this change in 2.1 for back-compatibility reasons.

(You can, of course, copy the object graph yourself.)


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jun 08, 2004 3:06 pm 
Newbie

Joined: Mon Mar 08, 2004 1:41 pm
Posts: 5
This is my final fix until something better comes up in hibernate:

I use my DAO layer to record what objects are attached to each session. The layer also records what objects are new (i.e. unsaved in the database).

When a rollback occurs, the DAO layer sets the ID of all new objects to null.

I tried this before, and it didn't work (my fix above was the result of trying to get hibernate to release its hold on my objects after I close the session & set their ID's to null so that a *new* session could then save the objects). I discovered my problem was that I was setting the ID's to null while the session was still open.

So one possible fix besides copying everything is to close the session first, and then set all of the ID's of unsaved objects to null.

But you will have to keep track of what objects are new yourself through flags, or through a map.

Another problem your going to run into is cascades.

I will use either the hibernate interceptor framework or AspectJ to intercept some hibernate calls so that I can still make use of cascades (really a must in my program).

I think the best fix would be fore hibernate to delete IDs on a failed flush, since the session must be discarded the IDs must be re-generated as well.

I have created an issue in JIRA and have posted more information there on some possible fixes on the hibernate side.
http://opensource.atlassian.com/project ... se/HB-1014

I will attempt also to see if I can come up with any more concrete ideas than what I have posed to fix this in hibernate.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jun 11, 2004 6:41 am 
Newbie

Joined: Mon May 03, 2004 9:29 am
Posts: 2
strobhen wrote:
This is my final fix until something better comes up in hibernate:
...

I tried this before, and it didn't work (my fix above was the result of trying to get hibernate to release its hold on my objects after I close the session & set their ID's to null so that a *new* session could then save the objects). I discovered my problem was that I was setting the ID's to null while the session was still open.
...


Another posibility is to invoke session.evict(yourFailedObject) before set id to null. After invoke evict the session must doesn't know anything about your object and you can set id to null without getting an exception. Then you can use save() with the same session. It must work - i use this approach in my versioning-interceptor. In onFlushDirty method i open new session, load second object with the same id from the database (with old original state), then invoke evict on this second object, set it's id to null and save this second object (with original state) in my helper session again. So i make a copy of original state of changed object and then (after leaving onFlushDirty method) all changes will be saved to.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jun 11, 2004 12:12 pm 
Newbie

Joined: Mon Mar 08, 2004 1:41 pm
Posts: 5
Yes, i found that evict works as well to make hibernate "forget" a transient object.

What I have ended up doing is modifying hibernate though.

I have created a set in SessionImpl that keeps track of new objects in the sesson. New objects are added to that set as they pass through doSave() (the one that does the real work). When afterTransactionCompletion is called, if the txn failed, it uses that Set to iterate through the objects, find the persister, and set the id to null.

Works beautifully thus far.

Since this isn't a bug according to Gavin, I can't put it on Jira or anything (though I still cannot find any info in the docs that tell us to throw out persistent objects if a session has an exception. AND it would be nice if that were reiterated in the section "handling exceptions' but oh well, I'm too busy to fight this one out).

So the code is available at:
http://www.digitalnothing.com/thomas/SessionImpl.java
if anyone is interested.

This is for hibernate 2.1.4 btw.

Good luck to all!


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jun 15, 2004 11:30 am 
Newbie

Joined: Wed May 19, 2004 3:50 am
Posts: 11
I've just returned from vacation which is why I haven't participated earlier.

I've voiced my support for Hibernate removing the ID assignments on error and given the reasons why in the JIRA entry mentioned above.

There are valid application design scenarios where simply throwing away the object model is just not an option. If Hibernate forces me to do this to recover from this simple end case then I can't use the object model to store user information which kind of defeats the purpose of a persistence layer!

Now I have no argument about rolling everything back - that's is indeed the application's responsibility - except for the one bit that the application cannot know about: the autogenerated IDs just created by Hibernate. That is for Hibernate to undo following the error IMHO.

Thanks for the pointers on fixing this - I will try them out.


Tom.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Sep 25, 2004 12:12 pm 
Newbie

Joined: Sat Sep 25, 2004 12:03 pm
Posts: 5
strobhen,
Thanks for submitting your code, i'll use it. But i found a little error in it, you clean the newEntities list in the finally block, but this occurs when iterating thru the list, so if you have more than one items in the list, your code will fail with a ConcurentModificationException, is this correct?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 13, 2005 6:47 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
Apparently Hibernate 3 removes the IDs.

Here is a relevant thread (to which I am trying to unify many other threads on this topic):

Canonical "Can't Recover From Version Exception" Thread


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 15 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.