Quartz is a great tool by which you could set up cron jobs inside your Java application. Spring makes even easier, but Spring doesn’t provide one function I want to achieve — dynamically managing Quartz jobs such as changing cron expression at runtime.
It was really easy to set up a Quartz job via Spring xml configuration based on the documentation (http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/scheduling.html#scheduling-quartz.). But once the application starts up, configuration cannot be changed or in another word the change will not be accepted until a server bounce, which is an issue in the production environment. Also my Quartz jobs require my service layer and need to update database. Since they run separately from sessions controlled by OSIV and interceptor, my old friend ‘no session or session was close’ error was back.
So here are two problems I want to solve.
1. dynamically update (and create) Quartz jobs.
2. no session or session was close while trying to get a hibernate collection (lazy initialization problem outside the view)
I searched this problem for four hours and experienced many suggestions until I came across with this one @ http://tech.javayogi.com/spring-quartz-problem.html.
Its solution was to created an abstract Quartz job class that creates and closes a hibernate transaction. Simply inherit from the class, instead of directly from the Quartz Job class will solve that transcation problem.
import org.springframework.scheduling.quartz.*;
import org.springframework.orm.hibernate3.*;
import org.springframework.transaction.support.*;
import org.quartz.*;
import org.hibernate.*;
import org.hibernate.context.*;
import org.apache.log4j.*;
/**
* Hibernate/Spring can be a pain, because hibernate sessions are not propagated. For JSP pages there is an
* interceptor taking care of it. Quartz tasks are outside of the scope of the view interceptor. So, this
* class wraps a transaction around it and makes sure that hibernate POJOs can load lazily.
*
* Make sure that the spring reference to the hibernateSessionFactory is satisfied in the spring mapping.
*
* @author Thomas Fischer
*/
public abstract class TransactionalQuartzTask extends QuartzJobBean {
private static final Logger log = Logger.getLogger(TransactionalQuartzTask.class);
// spring injected reference
private SessionFactory hibernateSessionFactory;
/**
* Most of this method is copied from the HibernateInterceptor.
*/
protected final void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
Session session = SessionFactoryUtils.getSession(hibernateSessionFactory, true);
boolean existingTransaction = SessionFactoryUtils.isSessionTransactional(session, getHibernateSessionFactory());
if (existingTransaction) {
log.debug("Found thread-bound Session for TransactionalQuartzTask");
}
else {
TransactionSynchronizationManager.bindResource(getHibernateSessionFactory(), new SessionHolder(session));
}
try {
executeTransactional(ctx);
}
catch (HibernateException ex) {
throw ex;
}
finally {
if (existingTransaction) {
log.debug("Not closing pre-bound Hibernate Session after TransactionalQuartzTask");
}
else {
TransactionSynchronizationManager.unbindResource(getHibernateSessionFactory());
SessionFactoryUtils.releaseSession(session, getHibernateSessionFactory());
}
}
}
/**
* Implementing classes, implement this method.
*/
protected abstract void executeTransactional(JobExecutionContext ctx) throws JobExecutionException;
public SessionFactory getHibernateSessionFactory() {
return hibernateSessionFactory;
}
public void setHibernateSessionFactory(SessionFactory hibernateSessionFactory) {
this.hibernateSessionFactory = hibernateSessionFactory;
}
}
