One of the most populare articles in this blog so far ist the one about, well: Hibernate Sessions in Two Tier Rich Client Applications. Although the original article is writen in german I keep refering to this article, even in english communities. Therefore I decided to break my habit of writing articles in german only and provide a english version which you are reading right now.
This is NOT a direct translation so there might be some differnces in scope, focus and detail.
Everybody working with Java and Databases is probably aware of Hibernate. It is a great solution for mapping a database schema to a object model. It is used in hundreds of applications. So on my current project we decided to use it as well. But big surprise. While Support in the user forum of hibernate is pretty good most of the time we hit one kind of a problem which had no apropriate solution available: How to handle Sessions in a Two Tier Hibernate Application (Swing or SWT typically).
How could such a essential problem be unsolved? Well Rich Client Applications are not hip anymore. Everybody goes for web applications and in that context the session handling is well understood and there is a easy straight forward solution that works in most cases: On each request open a session, do all your work including constructing the result (e.g. JSP) , close the session. But there is no equivalent for a request in a swing application. And since Rich Client Applications are kind of old fashioned nobody bothers to find (and write about) a clean solution for this problem.
We tried a lot of things, starting with the antipattern of a single session per application. This doesn't work at all for any kind of serious app for the following reasons:
- No transaction control between different parts of the application. When you flush/commit a session, everything gets flushed. So imagine a application with two open edit frames. The user hits save in one frame and surprise, her half done changes in the other frame get saved as well. Not good.
- Memory Leak. The Hibernate Session keeps track of every entity ever loaded by that session. So if you don't close the session from time to time the session will grow until the application blows up or the whole database is contained in the session. If nothing else this will cause performance issues.
Of course if you write a really small simple application this might work, but not for us. As said before we tried a lot of things to solve our problem in the assumption that our requirements are so common that it can't be hard to find the solution. We were wron. So if the solution to a problem isn't easy to find, what should be your first step? Wrong (probably)! Your first step should be to exactly identify the problem. In this case this meant: What are the requirements we had? What are the relevant features/limitations of Hibernate? The requirements were:
- We want Lazy Loading: Lazy Loading is a real powerfull feature whe working with a rich domain model (as we did) especially in a Swing Application. You just display what every attribute you can reach by any kind of object navigation, hibernate will ensure the data is there. Awesome. This was one of the main selling points for Hibernate vs. plain JDBC. So it wasn't realy an option to loose it.
- Different views of the same entity should show the same state. So if you edit an Object the same Object shown in some kind of list-view should show the updated state immediatly.
- It should be obvious for the user what is going to be saved when she hits the save button.
- The whole mechanism must be fairly easy to use, since the team was growing and not everybody was a hibernate expert. Also lazy loading problems are sometimes hard to debug and fix.
The relevant properties of the Hibernate session are
- For lazy loading to work an entity must be attached to a open session.
- A entity wich contains at least one collection (which are well above 50% of our class) can only be attached to one session at a time.
- A session keeps a reference to any entity it loads, until the entity get evicted or the session gets closed.
- A Hibernate session guarantees to return the same instance everytime a specific entity is requested.
If you look at it in this compact form it is rather obvious that you can't have your cake and eat it too. The automatic refresh in all views of an object would be easily implemented, when there is only one session. But we simply couldn't do this. So the decision was made that the automatic refresh is (obviously) not as important as clean transactional control plus lazy loading. So we will have more then one session. But which part of the application is using which session? The Hibernate site uses the term 'Unit-of-Work'. Each unit of work should be contained in its own session. But this is basically back to step one. A request in a webapp ist a unit of work, but where is a unit of work in a rich client app? Imagine this example: The user fires up the application and opens a search dialog resulting in a list of objects. Clearly we are accessing the database, so we just startet a unit of work. Now she double clicks an item in the list. The entity gets opened in a editor, she edits it and hits save. Work commited, transaction closed, unit of work ended. Wow, thats easy, isn't it? No it isn't: She clicks on a different item, edits it, saves it. So now we have opened one session and closed two sessions. Not good.
The answer is actually fairly easy once found: Use one session per frame/internal frame/dialog. Modal dialogs use the session of the frame the got starteted from. Background tasks get their own session. The critical point is the transfer of objects from one frame to another. It is tempting to just pass the object but then you have a object from the wrong session in the frame. Instead pass just the Id (the primary key) and use that to load the object in the new session. This approach solved most of our problems nicely.
Most problems? Yes and no. We have some things that we don't like to much. I think I know how to solve them but I haven't implemented them yet. So I can't really promis anything.
- Instant refresh in other (readonly) views. When editing a object the changed state is not represented in other views of the same object. This could be fixed by a hibernate interceptor or event listener. It would listen for update events, then check if this entity is contained in any other session, check if that session is read only and if so trigger a refresh of that object. We haven't implemented that simple due to time constraints.
- Saving in background. Most of the stuff we do is simple object editing, but on fairly complex object graphs. So saving an object to DB may take some time. Due to the tight integration of Frames and session we can't easily delegate this work to a background thread. The same is true of lazy loading. In long lists of complex objects scrolling to new cells for the first time might cause some lag, because lazy loading happens in the Event Handling Thread. The first part should be fairly easy to fix:Open a new Session, find the dirty Objects, merge the dirty Objects in the new Session, do a flush of the new session in a background thread. One just has to make sure the background thread uses its own instances, not the same instances as the original frame/session. The lazy loading in background is more difficult to solve. For almost everything in the GUI we use a JGoodies PresentationModel and the associated ValueModels, so the swing objects don't access directly any attributes of our Hibernate entities. These ValueModel could be used to implement a seperation of two differnt threads: The Swing Event Handling Thread on one side and a seperate thread for all the model work, including lazy loading. But this would mean the whole application is split in two threads, instead of the normal aproach of having one main thread and a couple of worker threads for special work. I think it should be feasibly and actually enforces a very strict and clean architecture, and result in a highly responsive gui, but it would also be a lot of (debugging) work and as everybody knows: Concurrency is hard. So we decided that we don't need it in this application.
If you are going to implement this kind of session handling I strongly suggest to use a director/mediator pattern for all your frames. While always a good idea, it becomes important in this case because it gives you a well defined spot to do your session handling. And of course you should be aware of the alternatives:
- Go with a single long living session. Only feasable with smallish applications
- Kick Lazy Loading and close your session after retrieving your objects. This loses many of the interesting benefits hibernate offers.
In any case I how this write up shed some light on the issues and helped to make a informed decission.