Friday, April 29, 2011

Spring 3 @Async with Hibernate and Spring Transactions

The other day we ran into a fun little problem while trying to use the Spring 3 @Async annotation. We had the following scenario:

A web service was invoked by a client app to update some things in the database. Based on what was updated several other calls had to be made to the database to update even more data. When all was said and done this process could take upwards of 15 minutes to complete which is pretty bad considering the SLA was 10-30 seconds. The obvious problem here is this class is doing WAY too much, but that is what the user's wanted so that is how things had to be.

The call stack looked something like this
->Call in to web service from client - (a few calls happen within this service besides the call listed below)
--->Web service calls service facade to run calculations - (This method is transactional since it reads and writes to the database, this method is also called twice in the web service.)
------>Service facade calls a DAO object (backed by hibernate) many many times.
<---Return to client after an eternity.


Our solution to this was to make the slow part asynchronous with Spring 3's new, awesome @Async annotation. So we annotated the service facade with the @Async annotation and thought our super
slow process would run in the background. We put some log statements in the beginning and end of the slow process so we could be sure the method was actually executing in it's own thread after the web service
call returned, it blew up big time. After searching on Google for a while I found out that having @Async and @Transactional on the same method can cause issues from time to time. So we created another service
facade to call the old service facade. We annotated the new facade with @Async and left @Transactional on the old facade. This approach got us further but failed on the second call to the service facades.

 You will notice above that the service facade is called two times from within the web service, the first call worked perfectly. The second call would fail with a org.hibernate.exception.GenericJDBCException: Cannot open connection exception when trying to access a member variable attahed to a parameter in the service facade. Once I dug into this I noticed the parameter object being passed to the service facades was actually a hibernate backed domain object which was populated inside the web service. The member
variable we were trying to access within the service facade was actually a collection of objects that was set to be lazy loaded. When this object was passed to the asyncronus service facade it was not fully
populated, just backed by hibernate. That being said when the async service tried to load up that collection of objects backed by hibernate the hibernate session the object was loaded on was killed
when the first thread exited, the web service thread, thus causing hibernate to fail. By moving the loading logic for that object into the ansync method everything started working!

3 comments:

  1. Thanks!!! Helped me solve this problem that I've been trying to figure out for hours.

    ReplyDelete
  2. can you explain me more how to move the loading logic for that object into the ansync method please

    ReplyDelete
  3. A quick code snip would be great here!

    ReplyDelete