I recently come across a problem in a Spring+Hibernate project when trying to execute an operation with a delay.
I had a service bean (let's say EntityService):
public class EntityService{
...
@Transactional(readOnly = false)
public void generateWithDelay(String id, long delay){
Entity e = new Entity();
e.setStatus("IN_CONSTRUCTION");
e.setId(id);
this.dao.save(e);
this.scheduler.addTask(id, delay, this);
}
@Transactional(readOnly = false)
public void finishGeneration(String id){
Entity e = this.dao.get(id);
e.setStatus("DONE");
this.dao.save(e);
}
....
}I wanted to call
generateWithDelay at a user action then after N seconds
finishGeneration to be called.
I created a "Scheduler" an
addTask method, that accepted the delay, the parameters of the call and a callback (for wich I created a
SchedulerCallback interface that my service implemented).
Under the hood the scheduler would simply create a
TimeTask than when the time passed it called the callback with the suplied parameters. This way the timerTask is as generic as possible and the finishGeneration is where it belongs in the entityService.
All nice and joly. generateWithDelay would work ok. It ads the entity in the DB and it starts the timer. However when the timer called the finishGeneration method I would get:
org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
at org.springframework.orm.hibernate3.AbstractSession FactoryBean$TransactionAwareInvocationHandler.invo ke(AbstractSessionFactoryBean.java:300)Obviously something is wrong with the transaction. A transaction never gets created before reaching the dao even thow
finishGeneration is annotated with
@Transactional. The annotation should work beacause it works just fine for
generateWithDealy.
What to do? what to do? My first thought was that it's the source of the call that creates the problem. The hibernate transaction usually is the JPA transaction that the J2EE container provides. So if the call is not initiated by a user action the transaction might never get called. But I'm not running in a J2EE environment and I have:
<prop key="hibernate.current_session_context_class">org.hibernate.transaction.JDBCTransactionFactory</prop>
<prop key="hibernate.transaction.factory_class">thread</prop>
so it can't be it.
As a first fix I solved this by programatically opening a transaction in the timerTask just before invoking the callback. With springs
Transaction Template it's actually quite easy. After this little trick it works just fine.
Lucky for me I remained curious about why it won't work with declarative transaction. In the end I found the problem.
When I provide the callback to the scheduler I do:
this.scheduler.addTask(id, delay, this);
As you know, in Spring the transaction is started by the proxy that wraps the entityService. However, because I use JDK proxies, this won't be the proxy, but the proxy target (the original service). So when the callback is invoked it bypasses the transaction mechanism completely. The trick is to provide the actual proxy to the scheduler. I did this by letting EntityService implement ApplicationContextAware and BeanNameAware and fetching the proxy from the applicationContext at initialization:
public class EntityService implements ApplicationContextAware, BeanNameAware, InitializingBean{
...
public void setApplicationContext(ApplicationContext ac){
this.ac = ac;
}
public void setBeanName(String beanName){
this.beanName = beanName;
}
public void afterPropertiesSet(){
this.myProxy = this.ac.getBean(this.beanName);
}
...
@Transactional(readOnly = false)
public void generateWithDelay(String id, long delay){
Entity e = new Entity();
e.setStatus("IN_CONSTRUCTION");
e.setId(id);
this.dao.save(e);
this.scheduler.addTask(id, delay, this.myProxy);
}