In the previous post regarding Hibernate, I've shown how the OpenSessionInView pattern can be implemented.
In short, the idea was that the transactional statements (CRUD operations with the database) happen in between the following lines:
These lines are executed by a servlet filter, making thus sure that the Hibernate session is kept open until the view is rendered, and thus allowing us to use managed entities, as opposed to detached.
However, there is still something about which we haven't talked yet: what happens in the real case, with several simultaneous requests to the servlet ? We know that servlets are multithreaded objects, i.e. multiple threads may call the servlet's methods at the same time. We also know that servlets are not thread safe, so we have to pay attention to instance or class variables, which can be shared by several threads.
In the case of the OpenSessionPerView pattern, note that sf.getCurrentSession().beginTransaction() may be called by several threads. This means that the result of sf.getCurrentSession(), which is the current Session object, is shared among threads, which is dangerous since a Session is not a thread safe object. This asks for trouble, since the instance of Session is used to create queries and perform CRUD operations with the database.
So how can we ensure a thread-safe behavior of the OpenSessionPerView pattern ? ThreadLocal pattern to the rescue.
In a few words, what we need is a Session object per thread, which can be easily implemented using a ThreadLocal variable, as in the code below:
public class HibernateUtil {
private static final SessionFactory sessionFactory;
private static final ThreadLocal threadSession =
new ThreadLocal();
static {
// initialize SessionFactory
sessionFactory = new Configuration().configure().
buildSessionFactory();
}
public static Session getSession() {
Session s = (Session) threadSession.get();
// open a new Session, if this thread has none yet
try {
if (s == null) {
s = sessionFactory.openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
public static void closeSession() {
try {
Session s = (Session) threadSession.get();
threadSession.set(null);
if (s != null && s.isOpen()) s.close();
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
}
}
You get the picture. We would call HibernateUtil.getSession().getTransaction() in our servlet filter, instead of sf.getCurrentSession().beginTransaction(). Now each thread running through the servlet (and the filter) gets its own session object, so it performs the CRUD operations to the database without interfering with the other threads.
When you enter the Hibernate world, you hear a lot about sessions, transactions, session-per-request, etc., to the point you start mixing the concepts. Let's face it:
Fact 1: Hibernate is big
Fact 2: Hibernate is easy if you understand the fundamentals; everything else builds on top of those
I'll try to explain these fundamental concepts and then I'll cover the famous OpenSessionInView pattern with an example.
So, what's a session ? If I tell you "a session is a unit of work bla bla bla", you'll say "I've read that in about 1,200,000 hits returned by Google and yet I don't really understand the concept". Let me put it this way: from the developer's perspective (what can I do, I usually incarnate this perspective), a session is actually an API that abstracts the notion of persistence service. Thus, whenever you want to do something with the objects (representing the O in the ORM), like synchronize them with the DB, persist them to the DB, etc., you need a session instance.
However, as we all know, each operation to the DB - be it a read or a write - needs a transaction. Thus, because you use the session in order to perform DB operations, it means whenever we're using the session we need a transaction (with a noteworthy exception, but we'll cover that later). If you're trying to run an operation on the session without being inside a transaction, you'll get a runtime exception of the type org.hibernate.TransactionException: Transaction not successfully started.
In other words, it's quite logical that the scope of a session (i.e. from the moment you create/open it to the moment you close it) and the scope of a transaction coincide. Without further ado, this is the session-per-request pattern.
Now let's take a painfully real example. Suppose I have two tables in my DB: a Patients table and a PatientPhones table (basically each patient from the first table has a number of telephone numbers recorded in the second table). They are linked by PATIENT_ID, a foreign key in PatientPhones table and primary key in the Patients table.
The Hibernate file that describes these tables is:
Notice the lazy="true" attribute above ? This means that actually the phone numbers will be retrieved from the DB lazily, i.e. when we request them. For example, suppose I do this:
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
// note that in this query we use the name of the class, Patient2,
// rather than the name of the table (Patients)
ArrayList patients =
new ArrayList(session.createQuery("from Patient2").list());
session.getTransaction().commit();
HibernateUtil.getSessionFactory().close();
Then, after the session has been closed, I do this:
for (Patient2 p : patients) {
System.out.println("Patient " + p.getName() + " " + p.getId());
}
This will work, because the patients list has been eagerly populated with the DB data inside the transaction above.
But if I try:
for (Patient2 p : patients) {
if ((p.getPhoneNumbers() != null) && (p.getPhoneNumbers().size() > 0)) {
System.out.print(" * phones :");
Iterator iter = p.getPhoneNumbers().iterator();
while (iter.hasNext()) {
System.out.print(" " + iter.next());
}
System.out.println();
}
}
This fails with the error LazyInitializationException (or something like this) because in the xml file above I asked the phone numbers to be lazily retrieved, i.e. when I do p.getPhoneNumbers(). However, at this moment I am outside the session, so I'm working with the detached object p. In order to be able to retrieve the phones, I have 2 choices:
1. either do lazy = "false" in the xml above
2. or reattach each object p to which I'm asking getPhoneNumbers(), like this:
This situation is extremely relevant, because in a typical web application you'd do exactly this:
whenever you show the results of a DB query (via Hibernate) in your view (a jsp page for example), your objects are detached (the session is closed, the transaction committed), so you can get the exception LazyInitializationException.
The solution is the Open Session in View pattern, which simply states that the session should remain open and the transaction uncommitted until the results have been processed in the view. This can be easily done with a filter, e.g.:
public class HibernateSessionRequestFilter implements Filter {
private SessionFactory sf;
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
sf.getCurrentSession().beginTransaction();
// Call the next filter (continue request processing)
chain.doFilter(request, response);
// Commit and cleanup
sf.getCurrentSession().getTransaction().commit();
} catch (StaleObjectStateException staleEx) {
throw staleEx;
} catch (Throwable ex) {
// Rollback only
try {
if (sf.getCurrentSession().getTransaction().isActive()) {
sf.getCurrentSession().getTransaction().rollback();
}
} catch (Throwable rbEx) {
}
// Let others handle it... maybe another interceptor for exceptions?
throw new ServletException(ex);
}
}
public void init(FilterConfig filterConfig) throws ServletException {
sf = HibernateSession.getSessionFactory();
}
(NOTE: this pattern only works when the jsp and Hibernate code run on the same JVM, i.e. when the presentation and business logic tiers run on the same machine)
This works just as described above: in the view I don't need to worry about the session; even if lazy = "true", I know that the calls patient.phoneMobile and patient.phoneFix will retrieve the data, because everything happens between the two important statements in the filter: