Creating JPA session for background threads
Asked Answered
T

5

8

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?

Tortile answered 19/5, 2014 at 8:7 Comment(0)
A
6

I do not know of any Spring-ready solution for this. So, I think you need to implement one similar to the OpenEntityManagerInViewInterceptor class.

Basically, you need to use TransactionSynchronizationManager to bindResource() an instance of EntityManagerHolder for your thread when it is started and unbindResource() when your thread is finished.

The core part of OpenEntityManagerInViewInterceptor is:

    if (TransactionSynchronizationManager.hasResource(getEntityManagerFactory())) {
        ...
    }
    else {
        logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewInterceptor");
        try {
            EntityManager em = createEntityManager();
            EntityManagerHolder emHolder = new EntityManagerHolder(em);
            TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), emHolder);

            ...
        }
        catch (PersistenceException ex) {
            throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
        }
    }

Please post the code here as an answer if you implemented it.

Angelitaangell answered 19/5, 2014 at 9:20 Comment(1)
Thank you! It seems to be what I was looking for, even though the way to implement it seems to be a little 'hacky' :-)Appropriation
T
14

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.

Tortile answered 21/5, 2014 at 9:26 Comment(4)
+1 for posting your code. Just be careful that the unbindSession() should always be called, no matter what, otherwise you may end up with an un-flushed EntityManager in your thread (quite dangerous!). It would be more robust if we wrap it in something like a JpaTemplate, e.g. OpenEntityManagerTemplate, which always calls unbindSession() in a finally block.Angelitaangell
It works perfect! Thank you. That's exactly what I was looking for. I'm surprised that there is no @Session annotation in Spring only to attach a session to a thread.Dotard
Dude, you saved me hours of debugging. I didn't know why my background transactions were not commited, hibernate didn't throw any errors, they just didn't execute.Levulose
Thanks for the answer. It was a big help. Used this bindSession( ) in an general purpose interceptor's preHandle method to open the session and inside afterCompletion() method used the unbindSession() method and it fixed my issue.Granulocyte
A
6

I do not know of any Spring-ready solution for this. So, I think you need to implement one similar to the OpenEntityManagerInViewInterceptor class.

Basically, you need to use TransactionSynchronizationManager to bindResource() an instance of EntityManagerHolder for your thread when it is started and unbindResource() when your thread is finished.

The core part of OpenEntityManagerInViewInterceptor is:

    if (TransactionSynchronizationManager.hasResource(getEntityManagerFactory())) {
        ...
    }
    else {
        logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewInterceptor");
        try {
            EntityManager em = createEntityManager();
            EntityManagerHolder emHolder = new EntityManagerHolder(em);
            TransactionSynchronizationManager.bindResource(getEntityManagerFactory(), emHolder);

            ...
        }
        catch (PersistenceException ex) {
            throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
        }
    }

Please post the code here as an answer if you implemented it.

Angelitaangell answered 19/5, 2014 at 9:20 Comment(1)
Thank you! It seems to be what I was looking for, even though the way to implement it seems to be a little 'hacky' :-)Appropriation
H
1

If you want each element to be processed in its own transaction you need to:

  1. Remove @Transactional from processQueue
  2. Move the elementCanBeProcessed logic into a separate ElementProcessor @Component class and annotate the elementCanBeProcessed with @Transactional
  3. Call elementProcessor.elementCanBeProcessed(element);

Because the elementProcessor is a separate bean, it gets proxied by Spring, therefore calls will be intercepted by the TransactionInterceptor.

Helvetia answered 19/5, 2014 at 8:22 Comment(4)
The question is how to keep the session open between these consecutive transactions?Angelitaangell
I agree that it solves the problem problem but it also forces me to move every little method into another class just to get @Transactional to work. It can be of course solved by using AspectJ to weave code at compile time, but that would complicate the whole project even more. Another issue is that if I had two 'little' transactional methods(for example: element getElementFromDatabase() , canElementBeProcessed(element)) then passing element between them would force me to reattach the element in the second method as it would be other session than in the first method. Am I right?Appropriation
I think the main concern was about moving the @Transactional from the whole batch to individual batch entries.Helvetia
@Paweł Yes you are absolutely right. Objects from the retrieval session must be reattached into the the batch processing session. If merge is suitable, you can even skip the re-attachment. The advantages of processing each entry in its own transaction is that you can isolate failure entries from successful ones, because the failures won't end up rolling back everything. Also, a single long transaction is susceptible to transaction timeouts, thrown by your current data source or transaction manager.Helvetia
C
0

Avoid long-running transactions across task, so that transactions are small and less likely to conflict/rollback. Here is what I did relying on JPA without JTA and @Transactional:

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;
    }        
}

You will also need to provide JPA beans in your database @Configuration, but most likely you already have that.

Condescendence answered 23/2, 2018 at 20:59 Comment(0)
B
0

Building upon Amir's and Pawel's answers and the JpaTemplate idea, I created a component to fulfill the template role:

@Component
@Slf4j
public class EntityManagerProvider {

    private final EntityManagerFactory entityManagerFactory;

    public EntityManagerProvider(EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
    }

    /**
     * Attaches a JPA {@link EntityManager} to the current thread (if none is already present).
     * For use within Springboot background threads (such as ones associated with a scheduler),
     * when we don't want to enclose all work within a single transaction.
     *
     * @param operation lambda that needs to run with a JPA session available.
     */
    public void withEntityManager(Operation operation) {
        if (isEntityManagerBoundToThread()) {
            operation.get();
        } else {
            withNewEntityManager(operation);
        }
    }

    @FunctionalInterface
    public interface Operation {
        void get();
    }

    private boolean isEntityManagerBoundToThread() {
        return TransactionSynchronizationManager.hasResource(entityManagerFactory);
    }

    private void withNewEntityManager(Operation operation) {
        try {
            bindEntityManager();
            operation.get();
        } finally {
            unbindEntityManager();
        }
    }

    private void bindEntityManager() {
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        TransactionSynchronizationManager.bindResource(entityManagerFactory, new EntityManagerHolder(entityManager));
    }

    private void unbindEntityManager() {
        try {
            EntityManagerHolder holder =
                    (EntityManagerHolder) TransactionSynchronizationManager.unbindResource(entityManagerFactory);
            EntityManagerFactoryUtils.closeEntityManager(holder.getEntityManager());
        } catch (Throwable t) {
            log.error("Failed to unbind EntityManager", t);
        }
    }
}

This is then used from the calling code like this:

entityManagerProvider.withEntityManager(() -> {
    ... code using JPA operations ...
});
Bowsprit answered 27/4, 2022 at 17:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.