Wicket: A slightly better “Open Session in View”
November 25, 2012 1 Comment
We recently participated at the The Plat_forms contest (more on that in a few days). Coding a full application in less than 30 hours is quite the task and there’s no room for wasting time.
Sadly, we wasted time. A lot. On the persistence layer.
Wasted may be too hard of a word but we spent too much time building the actual business services needed to populate the view layer.
You might wonder why it took so much time.
Well, because each use case needs a different service method which provides an entity and all it’s relations initialized as deep as the view layer will use it.
Now you might give me a confused look and ask “Why the hell did you bother building those and didn’t use Open Session in View?” and I would have answered with tirade on why I consider it an anti pattern.
But sometimes you have to rethink opinions you held dear for a long time.
Sometimes verbose is better
Before I continue to dive into Open Session in View (OSiV) a little deeper, let’s take a look why I still prefer the verbose approach. Building a relational persistence layer using an ORM can be a tricky thing.
There’s a lot of things that can go wrong. Especially if JOINs are involved. Any access to a collection or a referenced entity can cause havoc on your application performance.
By avoiding Open Session in View this problem is easily avoided as the developer has to think about every single JOIN as he will have to build the queries to resolve them.
There will also be integration tests covering individual queries and the possibility to do load tests based on those.
The moment you start using OSiV all these advantages disappear.
Sometimes terse is better
A contest (or building a prototype) has different rules. The only thing that counts is “getting it done”.
That’s where the verbose approach starts to fail as it demands a lot of code being written and tested.
Enter Open Session in View
Open Session in View
Using OSiV the database session is opened and closed through a Servlet Filter.
As the session stays open in the view layer you are free to navigate the entity-tree as you please.
This convenience comes at a price and, asides the problems I already mentioned, there are two key disadvantages:
N+1 Select problem
The most dreaded problem is the N+1-Select-Problem. Accessing collections without appropriate annotations will cause the ORM to do a Select for each entry in the collection.
So you will get 1 initial select plus N (=size of collection) subsequent selects. Using todays query-statistics-tools (every good ORM has a set of these) they are easy to find if they occur.
They are easily introduced and hard to find during development. With a good project setup they should be discovered during load testing.
If you are actually doing load tests …
Exception in the filter
ORMs do a lot of things when a database session is closed. Doing a lot of things also means that a lot of things can go wrong.
With OSiV the session is closed after the web request has been processed by the web framework. There is no way you can react on these exceptions in a meaningful way without putting significant logic into the ServletFilter. Not really a good idea.
Wicket
There is no way solving N+1 in a generic way. But the exception problem is easily solved. Well, if Wicket is your view layer. Wicket provides hooks to every step of request processing through
IRequestCycleListener. The following OpenSessionInRequestCycleListener shows how to build a Hibernate based OSiV for Wicket.
import org.apache.wicket.MetaDataKey;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.cycle.AbstractRequestCycleListener;
import org.apache.wicket.request.cycle.RequestCycle;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.hibernate4.SessionFactoryUtils;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.orm.jpa.EntityManagerHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import javax.persistence.PersistenceUnit;
@Configurable
public final class OpenEntityManagerInRequestCycleListener extends
AbstractRequestCycleListener {
private static final Logger LOG = LoggerFactory.getLogger(OpenEntityManagerInRequestCycleListener.class);
@SuppressWarnings("serial")
static final MetaDataKey<Boolean> PARTICIPATE = new MetaDataKey<Boolean>() {};
@PersistenceUnit
private EntityManagerFactory emf;
@Override
public void onBeginRequest(RequestCycle cycle) {
cycle.setMetaData(PARTICIPATE, TransactionSynchronizationManager.hasResource(emf));
if(!cycle.getMetaData(PARTICIPATE)) {
try {
LOG.debug("OPENING NEW ENTITY MANAGER FOR THIS REQUEST.");
EntityManager em = emf.createEntityManager();
TransactionSynchronizationManager.bindResource(emf, new EntityManagerHolder(em));
}
catch (PersistenceException ex) {
throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
}
}
}
@Override
public void onRequestHandlerExecuted(RequestCycle cycle, IRequestHandler handler) {
if (!cycle.getMetaData(PARTICIPATE)) {
try {
LOG.debug("CLOSING ENTITY MANAGER FOR THIS REQUEST.");
EntityManagerHolder emHolder = (EntityManagerHolder)
TransactionSynchronizationManager.unbindResource(emf);
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}
catch (WhateverExceptionYouAreInterstedIn e) {
//DOSTUFF
}
}
}
@Override
public IRequestHandler onException(RequestCycle cycle, Exception ex) {
return super.onException(cycle, ex); //To change body of overridden methods use File | Settings | File Templates.
}
protected Session openSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
try {
Session session = SessionFactoryUtils.openSession(sessionFactory);
session.setFlushMode(FlushMode.MANUAL);
return session;
}
catch (HibernateException ex) {
throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex);
}
}
}
And the same using JPA2:
import org.apache.wicket.MetaDataKey;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.cycle.AbstractRequestCycleListener;
import org.apache.wicket.request.cycle.RequestCycle;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.hibernate4.SessionFactoryUtils;
import org.springframework.orm.hibernate4.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@Configurable
final class OpenSessionInRequestCycleListener extends
AbstractRequestCycleListener {
@SuppressWarnings("serial")
static final MetaDataKey<Boolean> PARTICIPATE = new MetaDataKey<Boolean>() {};
@Autowired
private SessionFactory sessionFactory;
@Override
public void onBeginRequest(RequestCycle cycle) {
cycle.setMetaData(PARTICIPATE, TransactionSynchronizationManager.hasResource(sessionFactory));
if(!cycle.getMetaData(PARTICIPATE)) {
Session session = openSession(sessionFactory);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}
}
@Override
public void onRequestHandlerExecuted(RequestCycle cycle, IRequestHandler handler) {
if (!cycle.getMetaData(PARTICIPATE)) {
try {
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
SessionFactoryUtils.closeSession(sessionHolder.getSession());
}
catch (WhateverExceptionYouAreInterstedIn e) {
//DOSTUFF
}
}
}
protected Session openSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
try {
Session session = SessionFactoryUtils.openSession(sessionFactory);
session.setFlushMode(FlushMode.MANUAL);
return session;
}
catch (HibernateException ex) {
throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex);
}
}
}
Both use the same mechanism. Overwrite onRequestHandlerExecuted and go wild. Throw a RestartResponseException or recover gracefully, it’s up to you.
End
There’s only one question left: Would I use it?
Well, to be honest: It depends.
If I ever had to do something like Plat_forms again or a project prototype I would definitely go for it.
For a mission critical application I still prefer to go the save route and know each JOIN by its first name.