I\'m building a simple Tomcat webapp that\'s using Spring Data and Hibernate. There\'s one end point that does a lot of work, so I want to offload the work to a background t
With Spring you don't need your own executor. A simple annotation @Async
will do the work for you. Just annotate your heavyMethod
in your service with it and return void or a Future
object and you will get a background thread. I would avoid using the async annotation on the controller level, as this will create an asynchronous thread in the request pool executor and you might run out of 'request acceptors'.
The problem with your lazy exception comes as you suspected from the new thread which does not have a session. To avoid this issue your async method should handle the complete work. Don't provide previously loaded entities as parameters. The service can use an EntityManager and can also be transactional.
I for myself dont merge @Async
and @Transactional
so i can run the service in either way. I just create async wrapper around the service and use this one instead if needed. (This simplifies testing for example)
@Service
public class AsyncService {
@Autowired
private Service service;
@Async
public void doAsync(int entityId) {
service.doHeavy(entityId);
}
}
@Service
public class Service {
@PersistenceContext
private EntityManager em;
@Transactional
public void doHeavy(int entityId) {
// some long running work
}
}
What happens is, probably, you have transaction on your DAO piece of code and Spring is closing the session on transaction close.
You should squeeze all your business logic into single transaction.
You can inject SessionFactory
into your code and use SessionFactory.openSession()
method.
The problem is, that you will have to manage your transactions.
Method #1: JPA Entity Manager
In background thread: Inject entity manager or get it from Spring context or pass it as reference:
@PersistenceContext
private EntityManager entityManager;
Then create a new entity manager, to avoid using a shared one:
EntityManager em = entityManager.getEntityManagerFactory().createEntityManager();
Now you can start transaction and use Spring DAO, Repository, JPA, etc
private void save(EntityManager em) {
try
{
em.getTransaction().begin();
<your database changes>
em.getTransaction().commit();
}
catch(Throwable th) {
em.getTransaction().rollback();
throw th;
}
}
Method #2: JdbcTemplate
In case you need low-level changes or your task is simple enough, you can do it with JDBC and queries manually:
@Autowired
private JdbcTemplate jdbcTemplate;
and then somewhere in your method:
jdbcTemplate.update("update task set `status`=? where id = ?", task.getStatus(), task.getId());
Side note: I would recommend to stay away from @Transactional unless you use JTA or rely on JpaTransactionManager.