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: Potential Pitfalls
PostPosted: Thu Aug 11, 2005 10:39 am 
Regular
Regular

Joined: Mon Jul 26, 2004 2:28 pm
Posts: 86
Location: Pensacola, Florida
Using the session-per-request design pattern would there be any problems using the code below to simply reassociate an object to the session for the purposes of lazy loading?

Code:
public class HibernateUtil
{
    // Standard static methods

    public static void refresh( Object o )
    {
        try
        {
            // See if refresh is even necessary.
            getSession( ).lock( o, LockMode.NONE );
        }
        catch( TransientObjectException toe )
        {
            // This is a transient object and does not need to be refreshed.
        }
        catch( StaleStateException sse )
        {
            // Version or timestamp has changed and the data is out of date.
            // Go to the database to load modified state.
            getSession( ).refresh( o );
        }
    }


What I am hoping to achieve is a standard, fail-safe method for reassociating an object with a session (if it needs to be). In a lot of cases I really don't know whether the object is transient (being added) or detached (being modified), so I would also like the method to gracefully return if the object is transient (no lazy loading needed--it's all in memory).

I understand that using refresh( ) probably isn't recommended as it could potentially load data modified by another process that the user may or may not intend to persist (it would happen transparently). Assuming that is acceptable, what other pitfalls might I run into?

Thanks,

- Jesse


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 11, 2005 4:15 pm 
Regular
Regular

Joined: Wed Jan 07, 2004 5:16 pm
Posts: 65
Location: CA, USA
If you use session.saveOrUpdate() this will persist a new object if it is transient, or reattach/update an object if it already exists or is detached.

Kevin Hooke


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 11, 2005 4:37 pm 
Regular
Regular

Joined: Mon Jul 26, 2004 2:28 pm
Posts: 86
Location: Pensacola, Florida
The problem is that I do not want to persist changes. The scenario is this:

Request 1: Object is loaded, detached, and used to render a view.

Request 2: Changes are applied to the object, but saveOrUpdate( ) is not called. The changes are in memory, giving the user the option of canceling without having to implement a manual rollback process.

Request 3: The user chooses to add something to the object, invoking the lazy loader for a collection on the object. I need to load the elements into the collection without persisting changes to the parent object. If the object is detached, I need to reassociate it (ostensibly using Session.lock()); if it is transient, I can just access the collection normally. At this point I have only done read operations from the database and changes are only in memory. The session is closed at the end of each request, so the objects are either transient or detached, so nothing is flushed/persisted automatically by Hibernate.

Request 4: User explicitly commits or cancels action; in-memory objects are saved or discared.

Session.lock( ) does what I want except for one scenario: when another process updates the same object between Request 1 and Request 3. In this case saveOrUpdate still throws an exception if the version is different, so I am no better off than using a lock( ), and the changes would persisted, so I would have to roll them back manually somehow. And I want the transient objects to stay transient until explicitly committed, so that's out as well.

The main question is whether or not to do the refresh( ). My thought is that since everything is in memory and onscreen I can refresh if there is a version mismatch and the user will either 1) be oblivious to the refresh and not effect any changes to the object; 2) notice the refresh and re-add his changes being cognizant of the changes made by the other process (or I could attempt to diff the objects and merge the changes).

The other concern is whether I'm catching all of the exceptions (or the correct exceptions) to handle transient and/or stale objects.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 11, 2005 5:05 pm 
Senior
Senior

Joined: Thu May 12, 2005 11:40 pm
Posts: 125
Location: Canada
Exceptions are for exceptional situations. If you find yourself using exceptions to handle control flow, you're doing something wrong.

In general, it pays to handle this kind of stuff pre-emptively rather than post-emptively by using eager fetching. There are a lot of other pitfalls you can encounter when trying to reattach, like cascading locks causing NonUniqueObjectExceptions.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 11, 2005 9:46 pm 
Regular
Regular

Joined: Mon Jul 26, 2004 2:28 pm
Posts: 86
Location: Pensacola, Florida
Is there an API call for detecting whether an object is transient or not? The obvious solution is something like if( object.getId( ) < 0 ) { // unsaved }, but that's sort of a hack given that the unsaved-value is defined outside of the code in the mapping XML. I read in another post that there is a method called isUnsaved( ) (or something similar) on SessionImplementor, but casting Session to SessionImplementor is equally bad, if not worse.

I guess the best way would be to *know* whether the object is transient or detached by design. However, it is very tempting to combine add and edit functions since they are 99% the same code (same views, same backing beans, same validations, same model, same DAO method calls). And again, I suppose I could save the new instance right off the bat so that it is a detached instance rather than transient, but that is committing data that I don't want committed.

I think what I'll do for now is develop an interface for my model classes that exposes an isTransient( ) method and create an abstract base class that provides a default implementation based on a negative ID field. Then I'll change my HibernateUtil method like so:

Code:
public class HibernateUtil
{
    public void lock( ModelObject o )
    {
        // Check for transience here so that we don't
        // have to address the same concern in every
        // calling method
        if( ! o.isTransient( ) )
        {
            getSession( ).lock( o, LockMode.NONE );
            // Allow StaleObjectException to be thrown
            // and handled on a case-by-case basis.
        }
    }
}


I didn't know about the cascading locks and NonUniqueObjectException, but I think that my odds of getting that are probably low considering I'm closing the session on each request and dealing mostly with detached objects. Still, good to know.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Aug 12, 2005 8:54 am 
Senior
Senior

Joined: Thu May 12, 2005 11:40 pm
Posts: 125
Location: Canada
I have something similar:

Code:
public interface Identifiable
{
   Long getId();
   boolean isTransient();
}


However, that will not help you to determine whether an object is detached or attached. Transience is partially orthogonal to the idea of detachment, in that a transient object is definitely detached, but a non-transient object is not necessarily attached.

If you wanted to check whether a Session knows about a given instance, you could use contains(), but that is not a valid guard against NonUniqueObjectExceptions when locking:

Code:
if (!session.contains(instance))
    session.lock(instance, LockMode.NONE);


This is because the contains only checks for the instance itself, and the lock operation could cascade to a number of associated instances, some of which may have been loaded earlier during the session's life.

What is the reason you cannot use eager fetching when the data is first retrieved? I built a system around attach/detach, but in the end it caused me so much grief that I refactored to use eager loading exclusively.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Aug 15, 2005 10:04 am 
Regular
Regular

Joined: Mon Jul 26, 2004 2:28 pm
Posts: 86
Location: Pensacola, Florida
My object tree is something like this:

Code:
Class A
{
    private String _name;
    private Set _bs;
}

Class B
{
    private String _name;
    private Set _cs;
}

Class C
{
    private String _name;
}


I want Screen1 to show a list of As. When an A is selected, Screen2 shows the properties for A and a list of Bs. When a B is selected, Screen3 shows the properties for B and a list of Cs (and so on). I want to eagerly fetch the collection at the point the object is selected (there can be many As, Bs, and Cs, and I don't want to load them all at once). I think this is reasonable...

Anyway, in Screen1 you can also *add* a B to the list, which creates a new transient instance of B that is used to populate Screen2. Screen2 allows you to create a new C, and so on. So depending on whether you're adding a B or editing a B, the transition logic from Screen1 to Screen2 will have to detect whether B is transient or not. If not, it needs to be re-attached and loaded.

Since I'm closing the session at the end of every request, there is virtually no chance that any of the objects will already be in the current session, so I think I'm safe from the NonUniqueObjectException. And if all else fails, I suppose I could skip the interceptor (HibernateFilter) pattern and just open and close a new session inside every method (since I'm re-attaching anyway). The big gotcha is the transience check, and the interface/abstract base class should handle that (albeit not elegantly).


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.