为后台线程创建JPA会话

Paw*_*żyk 5 java spring hibernate jpa transactions

我们通过JPA和Spring使用Hibernate来管理Web应用程序中的对象持久性.我们使用open-session-in-view模式为响应http请求的线程创建会话.我们还使用了一些不产生视图的线程 - 它们只是不时醒来完成工作.这会产生问题,因为默认情况下它们没有打开会话,所以它们会产生异常

org.hibernate.SessionException: Session is closed! 
Run Code Online (Sandbox Code Playgroud)

要么

 could not initialize proxy - no Session
Run Code Online (Sandbox Code Playgroud)

我们发现如果每个后台线程在一个注释的方法中调用它的逻辑,@Transactional就没有这种类型的例外,因为@Transactional确保线程在事务内部时有会话.

它解决了一段时间的问题,但我不认为这是一个很好的解决方案 - 使长时间运行的方法事务导致问题,因为在提交事务之前,其他线程无法看到数据库中所做的更改.

我创建了一个java-pseudocode示例来更好地说明我的问题:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我使用所做的更改来注释整个processQueue方法@Transactional

elementDao.saveIntoDatabase(element);
Run Code Online (Sandbox Code Playgroud)

secondThread在事务提交之前不会被看到(因此直到整个队列被处理).如果我不这样做,那么线程将不会在其中进行会话,elementCanBeProcessed并且它将无法访问数据库.我也无法注释,elementCanBeProcessed因为它是这个类中的私有方法,我必须将它移动到另一个类,以便Spring代理可以工作.

是否可以将会话绑定到线程而不使整个方法具有事务性?我应该如何管理像那样的后台线程中的会话和事务?

Paw*_*żyk 8

这是我在阅读Amir Moghimi的回答后写的代码.它似乎有点'hacky',因为文档说EntityManagerHolder和TransactionSynchronizationManager都不应该由典型的应用程序代码直接使用.

@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());
    }
}
Run Code Online (Sandbox Code Playgroud)

它似乎工作 - 会话绑定到我的线程bindSession()unbindSession()调用之间,我不必创建一个事务来实现它.


Ami*_*imi 5

我不知道有任何 Spring-ready 解决方案。所以,我认为你需要实现一个类似于 OpenEntityManagerInViewInterceptor 类。

基本上,您需要使用TransactionSynchronizationManager在线程启动时为您的线程bindResource() 一个EntityManagerHolder的实例,并在您的线程完成时 unbindResource() 。

OpenEntityManagerInViewInterceptor 的核心部分是:

    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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

如果您实施了代码,请在此处发布代码作为答案。