We use Hibernate through JPA and Spring to manage object persistence in our web application. We use open-session-in-view pattern to create sessions for threads responding to http requests. We also use some threads that are not generating views - they just wake up from time to time to do their job. That generates problems because they don't have session opened by default so they generate exceptions like
org.hibernate.SessionException: Session is closed!
or
could not initialize proxy - no Session
We found out that if every background thread invokes its logic in a method annotated with @Transactional
there are no exceptions of this kind as @Transactional
makes sure that thread has session when it's inside the transaction.
It solved problems for some time but I don't think that it's a good solution - making long-running methods transactional causes problems because other threads can't see changes made in database until the transaction is committed.
I created a java-pseudocode example to better illustrate my problem:
public class FirstThread {
...
@Transactional
public void processQueue() {
for(element : queue){
if(elementCanBeProcessed(element)){
elementDao.saveIntoDatabase(element);
secondThread.addToQueue(element.getId());
}
}
}
private boolean elementCanBeProcessed(element){
//code that gets a few objects from database and processes them
}
}
If I annotate the whole processQueue
method with @Transactional
the changes made in
elementDao.saveIntoDatabase(element);
won't be seen in secondThread
until the transaction is committed (so until the whole queue is processed). If I don't do that then the thread won't have session inside the elementCanBeProcessed
and it won't be able to access the database. I also can't annotate elementCanBeProcessed
instead because it's a private method in this class and I would have to move this into another class so that Spring proxy could work.
Is it possible to bind session to thread without making the whole method transactional? How should I manage sessions and transactions in background threads like that one?
Here is the code I wrote after reading Amir Moghimi's answer. It seems a little bit 'hacky' because the documentation says that neither EntityManagerHolder nor TransactionSynchronizationManager should be used directly by a typical application code.
@Service
public class DatabaseSessionManager {
@PersistenceUnit
private EntityManagerFactory entityManagerFactory;
public void bindSession() {
if (!TransactionSynchronizationManager.hasResource(entityManagerFactory)) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
TransactionSynchronizationManager.bindResource(entityManagerFactory, new EntityManagerHolder(entityManager));
}
}
public void unbindSession() {
EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager
.unbindResource(entityManagerFactory);
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}
}
It seems to be working - the session is bound to my thread between bindSession()
and unbindSession()
calls and I don't have to create a transaction to achieve it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With