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.  [ 8 posts ] 
Author Message
 Post subject: Need help with transactions, session, and detached objects
PostPosted: Tue Apr 10, 2007 1:29 pm 
Newbie

Joined: Tue Apr 03, 2007 6:48 pm
Posts: 4
I've spent two full days on this, reading the documentation, message boards and blogs. I still can't figure this out, so I'm begging for help, before I have to give up and resort to my old O/R mapper (LLBLGenPro).

The problem I'm having is with sessions and transactions. I just don't understand NHibernate's session/transaction paradigm and how to make
it work with our service facade.

We're using a distributed 2-tier architecture with a service facade. We have an application server with our service instance initialized (WCF). This app server can be console or web; we use the console version for debugging and local testing.

The client is a WinForms app. It has a serviceproxy that calls the real service through WCF bindings.

Now, my limited understanding of NHibernate's sessions is that in a Console app situation, it connects to the app thread. And in our situation where we have a central server handling data access that's not a good thing, as Ayende points out here:

http://www.ayende.com/Blog/archive/2007/02/03/iBatis-better-than-Hibernate-for-GUI-applications.aspx

The key part I drew from this was:

Quote:
Working with Hibernate or NHibernate in a desktop envrionment can be a little more challanging than on the web. The issue is that you need to make sure that your session doesn't live for the duration of the application. The reason is, of course, the session cache. Using a single session for the application would basically be a memory leak, since the objects would never be released.


Because we're using a service facade, we don't do any Lazy Loading of objects. We load everything on the request and shoot a fully baked object graph back to the client. When the client wants to save/update the object, it makes another request to the facade. At that point we expect the object to be saved, unless a Timestamp indicates the object has been changed by another user (optimistic concurrency for us, in this situation).

My reading of NHibernate suggests this sort of behavior is difficult to achieve primarily because of the way NHibernate handles sessions and
maintains object state with its session state. It is basically trying to do all sorts of work to maintain knowledge of object state, when really all we want it to do is act stupid and fetch objects when we ask for them and update objects when we ask for it (and what I'm reading so far says this is a "detached object" scenario. We could do this with regular ADO.Net (and maybe we should) but I like allowing an O/R mapper to map all
the fields so I don't have to iterate through a datareader (I especially
like how NHibernate uses reflection to fill fields; this works well for some
objects we have that provide a lot of readonly data to the client).

Anyway, I guess what I'm asking for is two things:

(A) Can we do what we want to do with NHibernate, or are we better off
using a different technology (we don't have any problems with LLBL and
it's transactions - we don't get tons of errors and exceptions just trying
to do regular fetch and update, like we do with NHibernate, but maybe that's because we don't pass their business objects up to the client).

(B) If so, what would typical syntax look like for this scenario? Because I've tried all sorts of combinations of Flush, Transactions, Disconnecting
and Reconnecting sessions, etc., and I can't get anything to work.

I'm dying for some clear-cut examples and advice here. Everything I've seen so far seems to be centered around asp.net web pages or non-distributed use. I can't find anything on how anyone would use NHibernate in a distributed scenario for something like a Smart Client where data is expected to be disconnected from the database frequently.

Thanks to anyone who responds - appreciate it.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Apr 10, 2007 3:33 pm 
Beginner
Beginner

Joined: Tue Jan 02, 2007 5:53 pm
Posts: 42
Location: Bergen, Norway
Hi!

Just to clarify, you are wondering how you should manage NHibernate sessions? If you have a service facade, then the easiest thing to do would be to delegate the session management to the facade(s), and here you could easily use Open Session In View or even keep the session alive for a usecase if you like. Your winform clients will never know anything about nhibernate.

Another thing, are your middleware statefull or stateless?

Here is a thread from the Spring.Net forum on session management:
http://forum.springframework.net/showthread.php?t=1519

_________________
Cheers,
Steinar.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Apr 10, 2007 4:09 pm 
Newbie

Joined: Tue Apr 03, 2007 6:48 pm
Posts: 4
steinard wrote:
Hi!

Just to clarify, you are wondering how you should manage NHibernate sessions?


More or less. What I really want to do is just get the darn thing to work. I want to be able to fetch or save data without running into errors, exceptions and problems. I don't understand why it's so hard with NHibernate and why it's so easy with other O/R mappers.

steinard wrote:
If you have a service facade, then the easiest thing to do would be to delegate the session management to the facade(s), and here you could easily use Open Session In View or even keep the session alive for a usecase if you like. Your winform clients will never know anything about nhibernate.

Another thing, are your middleware statefull or stateless?

Here is a thread from the Spring.Net forum on session management:
http://forum.springframework.net/showthread.php?t=1519


Stateless, and I suspect this is the big problem.

I don't want my O/R mapper managing any state. I just want to fetch data from the database and have it mapped to my business objects so I can return those to the calling application. Then when save call is made, I want to persist that object (if dirty) to the database. Concurrency will be handled by us (developers) because in some cases we want optimistic concurrency and in other cases we need pessimistic concurrency. We make no assumptions about the state of any "session" when our client accesses the service facade.

I really don't want a "session". I want a transaction - a unit of work. Make a call to the service and get some data. Make another call - inside a transaction - to save some data, and if the transaction fails then rollback and report to the calling application that the operation failed. And these operations could be *days* apart - the client might be disconnected for a long period of time with the business object cached locally. So I can't make any assumptions about a "session state" when my client connects.

I hope some of this is making sense.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Apr 10, 2007 4:45 pm 
Beginner
Beginner

Joined: Tue Jan 02, 2007 5:53 pm
Posts: 42
Location: Bergen, Norway
Hi!

Everything you say makes sense, I retrieve my domain objects and pass them to the client and then send them back for either save or update. If you are running stateless you are having the same scenario as I have (I also mainly develop winforms at the moment). A transaction also use a session under the hood so you cannot avoid managing the sessions somehow. It would be nice if you posted some information about how you manage sessions, how do you acquire an ISession object at the moment, and also some of your exceptions, because I have no clue about what your problem really is.

If you use OSIV (Open Session In View pattern) then you close your session when the facade responds back to the client and thus have no session state either.

Code:
/*
This is a very simple example just to demonstrate that you must have
some sort of construct for maintaining the session on the server side.
Ultimately you could close your sessions after each trip to the DB, but this
isn't very efficient and you may easily run into LazyLoadExceptions as
you've closed the owning session.
*/
class ServiceFacade {
...
  public IList SomeServiceMethod(SessionToken token, Type type)
  {
    if(Authenticator.Authenticate(token))
    {
      // Hey, you are still authenticated!

      // Creation of sessions should be taken care by some other class.
      // Also, you should not pass your session around like I do in GetAll
      // below, but have some construct to provide it to your DAL. This is
      // just to give you a basic picture...
      ISession session = CreateNewSession();
      IList list = dataAccessObject.GetAll(session, type);

      foreach(object o : list)
      {
         // do some important stuff on you retrieved objects that may
         // involve another collection so that it is important that the owning
         // session is still open to avoid LazyLoadException.
      }

      // If you want to keep the session open for more then one request -
      // response (to utilize NHibernate caches) then store it in some
      // static hashtable, having some key to map it to the correct client.
      // But to be completely stateless, simply close it before the facades/
      // service responds back (OSIV).
      session.close;
      return list; // Responding to client...
    }
    else throw new NotAuthenticatedException("blablabla");
  }
...
}


And remember, NHibernate will not manage the session(s) for you, there are too many design choices and optimizations that can be made to let an orm blindly manage them. In the end I decided to use Spring.Net's NHibernate integration project to take most of these infrastructural concerns out of my code, but I'm still responsible for the life cycle of sessions.

_________________
Cheers,
Steinar.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 11, 2007 10:50 am 
Newbie

Joined: Tue Apr 03, 2007 6:48 pm
Posts: 4
steinard,

Thanks for the post! I was going to respond this morning, and when I hit "quote" I saw a whole lot more to your post that isn't showing up for me in FireFox.

So now I have your code sample to look at :)

I can see one difference right away: I was creating sessions in each dataMapper class, instead of one session at the service layer. So that obviously was causing problems because in two service calls I'd be opening two sessions, which explains why when I tried to fetch and then save an object, the save wasn't working and complaining about a something being modified in another session.

So I'll change that this morning and see if that affects things.

-Chris


Top
 Profile  
 
 Post subject: More Information on the Problem
PostPosted: Wed Apr 11, 2007 11:57 am 
Newbie

Joined: Tue Apr 03, 2007 6:48 pm
Posts: 4
Here's where I'm at right now: I can't save anything. I get this error:

"StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)"

Here's basically what my code looks like:

Code:
public void SaveSomething(SomeObject obj)
{
    ITransaction transaction = null;
    try
    {
        transaction = _session.BeginTransaction();
           
        //_session.SaveOrUpdate(timecard);
        //_session.Update(timecard);
        //_session.Save(timecard);
        _session.SaveOrUpdateCopy(timecard);
        //_session.Update(timecard);
        //_session.Save(timecard);
         
        if (transaction != null)
            transaction.Commit();
   
        }
        catch (Exception e)
        {

             if (transaction != null)
                 transaction.Rollback();

                throw;
            }
        }
}


As seen by the commented out lines, I've tried just about every combination of Saving. The object in question is something that is fetched in another method call prior to this save. So all of the data already exists in the database. I'm just trying to update the data.


This is where I run into problems. I don't understand how NHibernate works with its sessions. It seems like as soon as you try and update an object's data in the database NHibernate throws up, because it expects the object to already be associated with the session. And since it's not, it doesn't work.

What's the missing piece here? Because I'm not getting it. [/code]


Top
 Profile  
 
 Post subject: Re: More Information on the Problem
PostPosted: Wed Apr 11, 2007 2:57 pm 
Newbie

Joined: Wed Mar 21, 2007 4:31 pm
Posts: 6
ChrisHolmes wrote:
This is where I run into problems. I don't understand how NHibernate works with its sessions. It seems like as soon as you try and update an object's data in the database NHibernate throws up, because it expects the object to already be associated with the session. And since it's not, it doesn't work.

What's the missing piece here? Because I'm not getting it. [/code]


A "StaleStateException" is a concurrency violation basically. Hibernate is telling you that the "old" state of the object doesn't match what is in the database any more (or more specifically that the number of expected rows wasn't updated).

If you have an existing object and want to add it to a new session, you can use the Lock() method:

Code:
...

                User1 objUser1 = list[0]; // Give us the first one
               
                sess.Close();

                sess = f.OpenSession();

                using (ITransaction trans = sess.BeginTransaction())
                {
                      try
                        {
                          sess.Lock(objUser1, LockMode.None);

                          // Make changes
                          ...

                          // Save object
                          sess.Flush();

                          trans.Commit();
                        }
                        catch (Exception ex)
                        {
                            LogMessage(ex);
                        }
                    }
                }



Note that Hibernate will cache the values of the object at the time you Lock() it into the session, so then it can do dirty field detection based on those values.

The problem may be that it offers too many options. For example, we want to persist the changed fields only, but in a detached evironment using database timestamps, that isn't possible. You probably need to submit the entire record.

You can also get NHibernate to submit all the fields if it thinks any one of them has changed (dirty fields only versus all fields is a mapping property).


Top
 Profile  
 
 Post subject:
PostPosted: Wed Apr 11, 2007 6:31 pm 
Beginner
Beginner

Joined: Tue Jan 02, 2007 5:53 pm
Posts: 42
Location: Bergen, Norway
Hi!

Are you sure you closed the previous session? Have any other clients (threads hitting the middleware) modified any of the detached objects so they now have a newer version number/timestamp? I cannot see from your code listing how you aquired your session or that you closed it. Could you post your complete method and the ServiceFacade calling your dao?

If this is the code then I would also like to see something like this:
Code:
// In DAO class:
public void SaveSomething(SomeObject obj, ISession session)
{
    ITransaction transaction = null;
    Exception ex = null;
    try
    {
        //transaction = _session.BeginTransaction();
        // Why is your session a member variable?? If so, make sure
        // it will be set to null when finishing this method. Do the easiest
        // first and pass in the session as an arg.

        transaction = session.BeginTransaction();
           
        session.SaveOrUpdate(obj);
        transaction.Commit();
    }
    catch (StaleStateException sse)
    {
         transaction.Rollback();
         
         // By explicitly catching this exception you could
         // reattach your detached object here and try again!
            try {
                 obj = (SomeObject) session.Merge(obj);
                 ....
                 
            } catch(Exception e) { .... }
        }
    }
    catch (Exception e)
    {
         transaction.Rollback();

         ex = e;
        }
    }
    finally
    {
        // if session is a member variable then you can remove the
        // reference to it here
        // session = null;
        if(null != ex) throw ex;
    }
}


Writing such boilerplate code is really bad if you do it this way because it will completly litter the service facade, the full services by passing args and also the DAO classes. If you have the time then you should seriously consider reading about Spring.Nets NHibernate integration which will totally remove this code from your methods, this means that you write this code once in some class and then there is only one class to maintain:

Code:
// In ServiceFacade:
// Here the session scope attribute using AOP will do
// all session management for you. You can see the implementation
// of the session scope here:
// http://forum.springframework.net/showthread.php?p=6285#post6285
[SessionScope]
public void SaveSomething(SomeObject obj)
{
    someService.save(obj);
}


And in your serverside service class you could let AOP take care of your transactions aswell:
Code:
[Transaction]
public void Save(Object obj)
{
    dao.Save(obj);
}


And in the DAO:
Code:
public void Save(Object obj)
{
    // Here hibernate template will get access to your session
    // based on your thred
    HibernateTemplate.Save(obj);
}


You should also post your mapping for the failing domain object as your exception complains about incorrect unsaved-value mapping.

Anyway, this is as rb1 says most probably a concurrency problem (or you have set the unsaved-value = -1 while in your obj it is 0) and you must resolve this somehow by reattaching your detached object(s) (if someone has in the meantime modified the loaded objects). You can read the javadocs for Session.merge, Session.replicate and Session.lock for various ways of doing this. You can play with the various approaches in your unit tests!

Hope this helps.

_________________
Cheers,
Steinar.


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