December 30, 2005 02:28
Programming, Java, Blog
I’m having trouble with Hibernate now. In particular, I’m having trouble with Spring and Hibernate’s idea of managing transactions. Hibernate doesn’t use the JTA for transaction management: instead, it uses a Session object that looks a lot like a UserTransaction, and is defined as that period when the objects that you get are actually connected to the database. So you call

session.beginTransaction();
Post post = getPost();  
// calling post.getComments() will get real comment objects from Hibernate here...
session.commit();

// You can’t call getComments() here because there’s no active session.
// You’ll die with a LazyInitializationException.
post.getComments();

You might think that Hibernate would open a new connection or session if it has to lazy load associations. This is a bad idea, because you can get a ton of open connections.

You might think that Hibernate would keep a session open on a thread, so you can do many operations in a single transaction. Well, it can. If you can get hold of a sessionFactory, you can call sessionFactory.getCurrentSession() and it will return you the ThreadLocal session back.

Except that I’m using a JTA transaction model in a J2EE web application. Oops.

EDIT: ARGH. I misread the error message. JTA works fine, but Hibernate needs a current JTA transaction and I didn't have that.

You might think that Hibernate would have a servlet filter that would open up a single transaction at the beginning of a page render and close it when the page finished rendering. And, well, Spring does. Hibernate is a persistence solution, so from what I can tell, they’ve essentially washed their hands of Spring. But Spring has OpenSessionInViewFilter.

However, I can’t get OpenSessionInView to work. Here’s my code:

public String execute() throws Exception {
  // this is a Post object extracted by another action and set on this one
  Post post = getPost();
  String parsedPost = parsePost(post);
  setParsedPost(parsedPost);
}

// Get various lazy loaded objects if they exist (we only want to pull them in if we are
// displaying posts)
public String parsePost(Post pPost) {
  ...
  Map media = pPost.getMedia();
  ...
  List comments = pPost.getComments();
}

and here’s what happens when I try it:

2005-12-29 23:38:27,515 DEBUG [com.tersesystems.blog.dao.hibernate.HibernatePostDAO] - <findPublishedPage: pPage = 0, pPageSize = 5>
2005-12-29 23:38:27,531 DEBUG [org.springframework.orm.hibernate3.SessionFactoryUtils] - <Opening Hibernate Session>
2005-12-29 23:38:27,625 DEBUG [org.springframework.orm.hibernate3.HibernateTemplate] - <Eagerly flushing Hibernate session>
2005-12-29 23:38:27,640 DEBUG [org.springframework.orm.hibernate3.SessionFactoryUtils] - <Closing Hibernate Session>
2005-12-29 23:38:27,640 DEBUG [com.tersesystems.blog.dao.hibernate.HibernatePostDAO] - <findPublishedPage: posts = [[...]>
2005-12-29 23:38:27,765 DEBUG [org.springframework.orm.hibernate3.SessionFactoryUtils] - <Opening Hibernate Session>
2005-12-29 23:38:27,765 DEBUG [org.springframework.orm.hibernate3.HibernateTemplate] - <Eagerly flushing Hibernate session>
2005-12-29 23:38:27,765 DEBUG [org.springframework.orm.hibernate3.SessionFactoryUtils] - <Closing Hibernate Session>
2005-12-29 23:38:27,765 ERROR [org.hibernate.LazyInitializationException] - <could not initialize proxy - the owning Session was closed>
org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed
        at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:53)
        at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:84)
        at org.hibernate.proxy.CGLIBLazyInitializer.intercept(CGLIBLazyInitializer.java:134)
        at com.tersesystems.blog.dao.hibernate.PostImpl$$EnhancerByCGLIB$$9066ad74.toString(<generated>)
        at java.lang.String.valueOf(String.java:2577)
        at java.lang.StringBuilder.append(StringBuilder.java:116)
        at com.tersesystems.blog.action.ParsePostAction.parse(ParsePostAction.java:108)
        at com.tersesystems.blog.action.ParsePostAction.execute(ParsePostAction.java:137)
        at com.opensymphony.xwork.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:283) 

What this seems to say is that OpenSessionInView will only keep a session open in an object that extends from HibernateDaoSupport, and uses a HibernateTemplate. I tried overriding getHibernateTemplate() so I could add better logging to it, but someone’s declared it final and private. Which is unfriendly.

Theoretically the way to do this is to use the HibernateCommentDAO and have it take in a postId, but that seems silly to me. I don’t want to have to define a transaction explicitly around every action. I just want to be able to call post.getComments() anywhere in the page that I’m rendering, which I should be able to do.

I'd write my own filter, but I don't want to mess with the existing Session support that exists. I started writing a filter behind the OpenSessionInView one and found out that I had two different instances of sessionFactory. I don't know how the heck I managed that one.

« Hibernate Question | Home | Webwork Actions in a Nutshell »

huh?? In a JTA environment just call SessionFactory.getCurrentSession() and you get a tx-bound session.

Doh. I misread the error message. JTA is fine, but there has to be a transaction beforehand. Okay, fixed. Sorry, was up late last night.

I think what I'm going to do is dismantle all the HibernateTemplate stuff and just have a straight transaction + session filter wrapping the request. Then I'll access it from the DAO and everything will be happy.

Okay, done. That was surprisingly easy. I had to change hibernate.transaction_manager_lookup to use OrionTransactionManager and Spring's TransactionManager to use JtaTransactionManager, but after doing that, the filters work perfectly.

name
url