-->
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: how to associate a dirty collection to a session w/o update?
PostPosted: Sun Feb 29, 2004 11:43 am 
Newbie

Joined: Sun Nov 30, 2003 7:24 am
Posts: 11
Can anyone please advise how to go about this one with Hibernate 2.1.2:

- I have a persistent object in memory that spans several sessions - it's manipulated over HTML UI through a webapp. I have a servlet filter that lock()s all in-memory objects at the beginning of each HTTP request. All works nicely while simple properties of the object are manipulated. However, as soon as a member collection of the object is manipulated, I get a

Code:
net.sf.hibernate.HibernateException: reassociated object has dirty collection
   at net.sf.hibernate.impl.OnLockVisitor.processCollection(OnLockVisitor.java:47)
   at net.sf.hibernate.impl.AbstractVisitor.processValue(AbstractVisitor.java:69)
   at net.sf.hibernate.impl.AbstractVisitor.processValues(AbstractVisitor.java:36)
   at net.sf.hibernate.impl.AbstractVisitor.process(AbstractVisitor.java:93)
   at net.sf.hibernate.impl.SessionImpl.reassociate(SessionImpl.java:1653)
   at net.sf.hibernate.impl.SessionImpl.lock(SessionImpl.java:1673)
   at net.sf.hibernate.engine.Cascades$2.cascade(Cascades.java:79)
   at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:436)
   at net.sf.hibernate.engine.Cascades.cascadeCollection(Cascades.java:526)
   at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:452)
   at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:503)
   at net.sf.hibernate.impl.SessionImpl.lock(SessionImpl.java:1677)


Now, I did read in the forum that lock() cannot be used on dirty objects and that I have to use update().

BUT:

I don't want to update yet. The user can move items in and out of the collection over several HTTP requests, and there's an explicit "Save" button on the UI, and that's the only time when I can write data back to the DB. What to do? Should I use update() instead of lock() and simply clear() the Session object at the end of the request, except when the "Save" button is pressed? Seems very counterintuitive, frankly. Any enlightement greatly appreciated.

(It's confusing that I can lock() an object whose fields were modified (so it's dirty too), but I can't lock() a dirty collection.)


Top
 Profile  
 
 Post subject:
PostPosted: Sun Feb 29, 2004 11:46 am 
Newbie

Joined: Sun Nov 30, 2003 7:24 am
Posts: 11
Another related question that popped up:

If I use update(obj) instead of lock(obj, LockMode.READ) when reassociating objects, will I still get early detection of optimistic locking failure? I'm using LockMode.READ so that I get explicit optimistic locking check at that point. Will update(obj) provide the same?


Top
 Profile  
 
 Post subject:
PostPosted: Sun Feb 29, 2004 2:16 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
Quote:
It's confusing that I can lock() an object whose fields were modified


Well, you can, but you shouldn't. lock() is for unmodified objects!

Quote:
Should I use update() instead of lock() and simply clear() the Session object at the end of the request


Well, you could use update() instead of lock() after calling setFlushMode(FlushMode.NEVER). Explicitly flush() the session on the last request of the application transaction.



Alternatively, you can use the session-per-application-transaction approach, with FlushMode.NEVER.


Top
 Profile  
 
 Post subject:
PostPosted: Sun Feb 29, 2004 3:25 pm 
Newbie

Joined: Sun Nov 30, 2003 7:24 am
Posts: 11
Why exactly do I need FlushMode.NEVER? In my understanding, autoflushing (the default) will generate INSERT/UPDATE statements before certain query executions, so I get consistent view of data from inside the transaction - I actually rely on this in several places in my app. Won't FlushMode.NEVER disable this? If I make sure transaction is rolled back at the end of the HTTP request if I don't intend updating anything, I should still be be OK, right?

However, when I finally have to complete the app-level transaction, Hibernate will probably generate UPDATE statements for otherwise unmodified objects as well, since I called update() for all of them, right?

In my application model, I can't distinguish between unmodified and modified objects. I tried using a kludge such as

Code:
try
{
  session.lock(obj, LockMode.READ);
}
catch(HibernateException e)
{
  session.update(obj);
}

to first try locking, and only fall back to update if it complains about dirty collections. However, this later led to Hibernate complaining about the collection being referenced from two places. I didn't heed the advice in docs saying that exceptions aren't recoverable :-)

Isn't there a way to find out if a non-collection object is dirty, without resorting to the above kludge? At least for lazy initialization proxy classes?

The session-per-app-transaction approach is unfortunate, as the user could just shut down the browser in middle of it and walk away, and then I have a dangling session until the HttpSession expires.

I'd really love to find some elegant solution for solving this impedance mismatch where I can

- keep objects in memory between HTTP requests
- have one session per HTTP request
- have in-memory objects reassociated to the session in every request
- have only modified objects updated to database when the application level transaction (spanning several HTTP transactions/Hibernate sessions) finally completes
- have consistent in-transaction view of the database (as provided by autoflushing)

So, how do I do it? Is it possible at all?


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jul 21, 2004 6:47 pm 
Beginner
Beginner

Joined: Tue Dec 09, 2003 3:27 pm
Posts: 21
bump,

Gavin, any response to this as I'm in a very similar boat? I also can't keep a session open and was wanting to use lock() so I could load some lazy=true collections on demand. So needless to say I was a little disappointed to find out that everything was working ok until I modified a collection and then tried to lock the object to load another collection.

btw, where does it actually say "lock() is for unmodified objects"? :)


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jul 21, 2004 7:46 pm 
Expert
Expert

Joined: Sat Jan 17, 2004 2:57 pm
Posts: 329
Location: In the basement in my underwear
I also just ran into this today.

As for the "Update Workaround", in my case I have composite keys so I am using an Interceptor to mark my objects as 'saved'.

By setting the flush mode to NEVER and calling update works as in it no longer blows the exception regarding the dirty objects. However, my objects are not marked as saved which is less than optimal.

Any suggestions on how I might sidestep my interceptor from modifying my object in that manner?

Along the same vein, anyone else know of something internal that might get in a bad state? i.e. will some collection get unmarked as dirty at some point in it all and when I actually want to save my object I'll be unable to do so.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 22, 2004 12:44 pm 
Beginner
Beginner

Joined: Tue Dec 09, 2003 3:27 pm
Posts: 21
I just found out another reason that update with FlushMode.NEVER isn't going to work for me. I use oracle sequences for ids so when ever update is called it's going to consune sequence numbers. This is of course not desirable at all.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 22, 2004 6:12 pm 
Expert
Expert

Joined: Sat Jan 17, 2004 2:57 pm
Posts: 329
Location: In the basement in my underwear
So I guess I should add that in my case that even though we're not running in an EJB environment yet but it is a near future requirement to do so.

In this case we're using a Service facade layer that we're eventually going to replace with stateless session beans. So far, so good.

Our app is swing based and has a fairly large legacy data model (300+ tables) therefore the amount of POTENTIAL data to move across the wire is fairly large.

According to this following blurb from Hibernate in Action it states:
"Hence it


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 22, 2004 6:16 pm 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
Quote:
Now, everything is still fine as I click through my panels as I can lock the object through a service call and then init the collection I want and carry on, everything is happy.


And this is exactly how it works. Start a new database transaction explictely, lock/update your entity, load what you need, commit the transaction. Everything else would introduce you to very interesting problems. There is no magic solution, it's a design issue you will have to live with.

_________________
JAVA PERSISTENCE WITH HIBERNATE
http://jpwh.org
Get the book, training, and consulting for your Hibernate team.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 22, 2004 7:32 pm 
Expert
Expert

Joined: Sat Jan 17, 2004 2:57 pm
Posts: 329
Location: In the basement in my underwear
Yes, but that works all fine until I modify my object on the client side.

If I have collection A and lazy load it, perfect.
I can then load Collection B, again tickety boo.

My problem is that if I modify anything in collection A or B then I can't get at collection C.

To get around this it was suggested to use update(), however that causes reams of other problems as outlined by gschrader and with my case with the Interceptor.

Therein lies the dilemma. I'm not really looking for a magic solution just a solution :)

i.e. I'm not asking for Hibernate to detect the lazy init error and get me the data, I just want to be able to get the data as I want it, locking my object back to the session, etc.

Another alternative that would be nice (which might solve my other problem of trying to filter down a collection) would be the ability to query for my collection directly through some mechanism (Criteria, Query, HQL whatever) and then create a hibernate set and stick that on my parent object.

I was experimenting with this the other day and while I could create a hibernate set by passing in my Session and Set I couldn't generate a valid snapshot to assign to the Collection.

It would be nice to be able to say to an object here are 4 objects that you know about that you can modify and/or delete even though there might be 300 other records in the db that I don't care about right now.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 23, 2004 1:36 pm 
Beginner
Beginner

Joined: Tue Dec 09, 2003 3:27 pm
Posts: 21
Ok, to get around my sequence number problem, I've built the following sequence generator class. I think this will work, I'm not sure if FlushMode.NEVER will ever be used for something else so maybe someone can tell me if I'm on crack for doing it this way.

Code:
public class SequenceGenerator extends net.sf.hibernate.id.SequenceGenerator {
    private Type myType;
    public void configure(Type type, Properties params, Dialect dialect) throws MappingException {
        super.configure(type, params, dialect);
        this.myType = type;
    }

    public Serializable generate(SessionImplementor session, Object obj) throws SQLException, HibernateException {
        if (session.getFlushMode() != FlushMode.NEVER) {
            return super.generate(session, obj);
        } else {
            Class clazz = myType.getReturnedClass();
            if ( clazz==Long.class ) {
                return new Long(-1);
            }
            else if ( clazz==Integer.class ) {
                return new Integer(-1);
            }
            else if ( clazz==Short.class ) {
                return new Short((short) -1);
            }
            else {
                throw new IdentifierGenerationException("this id generator generates long, integer, short");
            }
        }
    }
}



I realize that this will assign this bogus key to my objects however in my case I'm actually discarding the object.

Here's the convoluted process I take to lazy load a collection:

1. build a copy of the object (with serialization)
2. reassociate the copied object to a session using update() and FlushMode.NEVER
3. lazy load a collection by calling size()
4. copy the new lazy loaded collection onto the original object
5. discard the copied object

The other reason to do this on a cloned object is so that the other collections maintain their state in regards to keeping track of whether it needs to insert/delete items in the collection.

As I said this is very colvoluted, please let me know if anyone has a better way.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jul 28, 2004 5:41 pm 
Regular
Regular

Joined: Mon Sep 29, 2003 9:39 am
Posts: 67
Vampboy, did you find a solution yet?
I'm having similar issues, and the work-arounds really mangle my application design.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jul 28, 2004 7:14 pm 
Expert
Expert

Joined: Sat Jan 17, 2004 2:57 pm
Posts: 329
Location: In the basement in my underwear
Well, we have a solution that seems to be working right now (at least for now) that also handles many to one proxies.

Essentially, working with a given root object and using some recursion we look at what the user wants to lazy load.

So imagine the following if you will.

Root Object with a 1-M collection of As, on each A there is a M-1 proxied B and then off the B a 1-M collection of Cs (need a scorecard yet?)

A user might say for a given root object give me A.B.C.

So there pass the instance of the RootObject and a string "A.B.C"

The first thing the method does is opens up a session, clones the object (due to problems with interceptor) and then calls session.update(rootObject) - we were using LockMode at first but that wasn't working for dirty objects.

Once the clone of the rootObject is locked into the session we can then start looking at the properties that the user requested.

If we are just asking for the collection of As (or even if it is a proxy) it is pretty simple. We simply call the following method



Code:
private void initialize(Object objectToInitialize, Session session) throws Exception {
        //if the object is already initialized then don't bother with it
        if (!Hibernate.isInitialized(objectToInitialize)) {
            //need to set the session on the object so that it can be initialized
            if (objectToInitialize instanceof PersistentCollection) {
                PersistentCollection persistentCollection = (PersistentCollection) objectToInitialize;
                persistentCollection.setCurrentSession((SessionImplementor) session);
            } else if (objectToInitialize instanceof HibernateProxy) {
                HibernateProxyHelper.getLazyInitializer((HibernateProxy) objectToInitialize).setSession((SessionImplementor) session);
            }
            Hibernate.initialize(objectToInitialize);
        }
    }


This allows us to set the object to be tied to the current session, we can then call initialize on it so that it gets loaded up.

The last step would be to set the initialized hibernate object back onto the original root object (not the cloned one).

The root object then goes back to the client and we can perform this logic multiple times. (Took us a while to work out the kinks)

Now, the A.B.C scenario is a bit more complex but by using recursion we can reuse all of the logic from the simple A case.

Essentially, the method looks and sees there is more than one property being requested in a chain (by the A.B.C notation).

It then takes the A and initializes it, then takes B.C and passes it back to the method itself recursively for each element in the collection. Where each A in the collection is the new root object and then B.C are the properties to load for A.

Same thing happens when it sees the B (i.e. passes B recursively as the rootObject and C as the property and initializes B).

It goes all the way down to C, calls the initialize on C, sets it on B.

Once A.B.C is initialized the whole works is set back on the original rootObject that the Client passed in and everyone goes home happy.

Now, if you can follow all of that I didn't obfusicate well enough ;)


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jul 28, 2004 10:41 pm 
Regular
Regular

Joined: Mon Sep 29, 2003 9:39 am
Posts: 67
I suppose it is obfuscated enough for my taste...thanks!


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jul 28, 2004 11:50 pm 
Expert
Expert

Joined: Sat Jan 17, 2004 2:57 pm
Posts: 329
Location: In the basement in my underwear
puffybsd wrote:
I suppose it is obfuscated enough for my taste...thanks!


LOL, does that mean you followed it? If so, great. If not, if you have any questions, just ask.


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.