Hibernate sessions that are initiated from the web layer are covered by the Spring OpenEntityManagerInViewInterceptor. But what about asynchronous executions, or jobs triggered by a scheduler like Quartz?
Instead of dealing with TransactionSynchronizationManager or worse, hibernate in your Quartz code, it would be nice to have an elegant transparant solution like the OpenEntityManagerInViewInterceptor.
With a little work (or actually almost no work) this is possible. Instead of binding to the web layer, we can create an aop advisor to intercept method calls to a task class. Put that task class in the jobdata of your quartz job and call it. The OpenEntityManagerInViewMethodInterceptor (code below) will transparently do the work for you.
<bean id="reindexTask" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<bean class="nl.avisi.SomeTask"/>
</property>
<property name="interceptorNames">
<list>
<value>OpenEntityManagerInViewMethodInterceptor</value>
</list>
</property>
</bean>
public class OpenEntityManagerInViewMethodInterceptor extends EntityManagerFactoryAccessor implements Advisor {
public void pre() throws DataAccessException {
logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewInterceptor");
try {
EntityManager em = createEntityManager();
TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), new EntityManagerHolder(em));
}
catch (PersistenceException ex) {
throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
}
}
public void post() throws DataAccessException {
EntityManagerHolder emHolder = (EntityManagerHolder)
TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewInterceptor");
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}
@Override
public Advice getAdvice() {
return new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if (invocation.getMethod().isAnnotationPresent(Transactional.class)) {
pre();
}
Object answer = invocation.proceed();
if (invocation.getMethod().isAnnotationPresent(Transactional.class)) {
post();
}
return answer;
}
};
}
@Override
public boolean isPerInstance() {
return false;
}
}
The check for an existing annotation can be left out, it is a quick way to select a single method to be intercepted. I annotate that method with:
@Transactional(readOnly=true)
Additionally you can also check for existing bindings and use these when they are already present.