A conversation consists of one or multiple dialogs. Think about a dialog to edit a product which let you go through a couple of JSP pages.
In the former examples we always have closed the session directly after a commit or rollback of the transaction. Most of the time Hibernate has done this for us automatically as we have configured a ThreadLocal session context.
<property name="current_session_context_class">thread</property>
But is this really the best approach or do we have alternative options. We could keep the session open all the time, but how to deal with the transaction in that case. The user might expect that either all or none of the changes to the product is saved. We might hold the transaction open all the time or not save at all.
In chapter Lazy Initialization Lazy initialization we have seen the problem of lazy initialization. We named the option to extend the lifetime of a session from the beginning of the request until the JSP was rendered. This allows to fetch data during the rendering of the JSP. This approach is called Open-Session-in-View. This is another option as well.
Let’s have a look at the different approaches available.
For every request we create a new and clean session inside of the application logic. It is closed at the end of the application logic. The advantage is that it is unlikely to get the NonUniqueObject exception, we talked about in chapter 1.6 Working with Objects.
We might get a LazyInitialization exception during rendering. This happens if we have not initialized collections, we want to print out during the rendering phase. There is an advantage of the exception as well: we see immediately what we need to initialize and can do this efficiently.
Another advantage is a very clean application structure. It is simple to handle exceptions from our application logic and redirect the user to an error page.
A consequence of this approach is that the objects change their state to detached between each request/dialog and they need to be reattached using session.update, session.merge or session.lock.
Let’s have a look at sample code.
Dialog 1
We enter a dialog to edit a turtle.
Session session = factory.getCurrentSession(); session.beginTransaction(); Turtle turtle = (Turtle) session.get(Turtle.class, "4711"); session.getTransaction().commit();
Dialog 2
The user has selected a name for the turtle and clicks on the save button.
session = factory.getCurrentSession(); session.beginTransaction(); turtle.setName("Alexander"); session.update(turtle); session.getTransaction().commit();
Dialog 3
On the next screen the user can input the size of the turtle. He inputs the size and presses the save button again.
session = factory.getCurrentSession(); session.beginTransaction(); turtle.setSize(TurtleSize.MEDIUM); session.update(turtle); session.getTransaction().commit();
This approach is simple but we will write the changes to the database in every step. If you want a acid transaction saving either all or none of the changes to turtle, we would have store the turtle in the HTTP session for example and save it only in the last step.
The pattern Open-Session-in-View extends the lifetime of the session. You can find an example in the project OpenSessionInView. In this project a Servletfilter opens a session and begins a transaction. After this the application logic is called, then the JSP is called to render a view. At the end the transaction is committed and the session is closed.
This is a pretty old approach to do this and will talk about a better approach later one. But let’s have a lock at the advantages and disadvantages. Basically it is the opposite of the last approach. We probably don’t get the LazyInitializationException any more but it might happen that data is inefficiently fetched during the rendering of the view.
Another problem we will face is exception handling. The commit of the transaction happens in the servlet filter. With the commit Hibernate flushes all open SQL statements. If we get any exception there, the view is already rendered and we can’t even show an error page. The page is already partly rendered and the browser will not accept a redirect at this point.
At a minimum measure you should call session.flush in the application logic to enforce that open statements are inserted. This will probably reduce the number of exceptions but still a commit might fail.
The best approach in my opinion is to commit the transaction inside of the application logic and to open a second transaction for rendering. You will need some coding and might use the ManagedSessionContext class of Hibernate in that case.
Concerning the conversation, we will have the same code as in the last example. Between requests, all entities will be in detached state and need to be reattached, if we want to change them.
A session with a long lifetime is called Extended Session. The session is opened when a conversation starts and closed when it ends.
Between the requests we need to store the session somewhere. One option is for example the HTTP Session of a web application.
In every dialog happens a transaction, but we tell Hibernate not to flush any SQL statements. Normally a commit would flush all open statements. If we set the FlushMode of the session to manual, then we take over the responsibility and can do the flushing at the end. Let’s have a look at the code.
Dialog 1
We show a dialog to create a new turtle. The first thing we do after we have called \ openSession is to change the FlushMode to manual. It is not shown in the code but we will save the Hibernate session in the HTTP session.
Session session = factory.openSession(); session.setFlushMode(FlushMode.MANUAL); session.beginTransaction(); Turtle turtle = new Turtle(); session.save(turtle); session.getTransaction().commit();
Dialog 2
The user selects a beautiful name for the turtle and press save. We fetch the Hibernate session from the HTTP session and continue to work with it.
// Hibernate Session is stored in HTTP-Session session.beginTransaction(); turtle.setName("Alexander"); session.getTransaction().commit();
Dialog 3
Finally the user measures the size of the turtle and inputs it in the last dialog. The Hibernate session is fetched from the HTTP session. At the end session.flush is called to send all open statements to the database. As we haven’t configured automatic closing for the session in the hibernate.cfg.xml, we have to call \ session.close.
// Hibernate Session is stored in HTTP-Session session.beginTransaction(); turtle.setSize(TurtleSize.MEDIUM); session.flush(); session.getTransaction().commit(); session.close();
The extended session approach has an attractive advantage: the session is never closed and the entities in the session stay in persistent state. You just have to change them and call session.flush at the end. There is no need to call session.update and you may prevent exceptions like the NonUniqueObject exception. There is a trade off as well. The session might grow as it contains all objects you have loaded with queries. In addition you are storing this large Hibernate session in the HTTP session. The former approaches close the session and release all unused entities to be garbage collected. The size of the session can be managed. Session.evict \ removes entities from the session.
It is up to you to decide what you want to do.
Pitfalls
Sometimes the manual flush doesn’t work. Hibernate just ignores what you want and flushes.
The reason is the chosen id generator. If you use sequence as generator, you will see a select statement for the id when calling session.save. Even in flush mode manual Hibernate figures out the id. There are id generators which requires an insert statement, ie: identity and select. If we use them we will get an immediate insert statements in the first dialog.
Open Session in View
We might combine the extended session with open session in view. In this case we need to set a kind of marker in the application logic to tell a servlet filter that it can close the session.