为后台线程创建JPA会话

时间:2014-05-19 08:07:44

标签: java spring hibernate jpa transactions

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

这样的异常
org.hibernate.SessionException: Session is closed! 

 could not initialize proxy - no Session

我们发现如果每个后台线程在用@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
    }
}

如果我使用processQueue注释整个@Transactional方法

中所做的更改
elementDao.saveIntoDatabase(element);
在提交事务之前,不会在secondThread中看到

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

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

4 个答案:

答案 0 :(得分:8)

这是我在阅读Amir Moghimi的answer后写的代码。 看起来有点像哈克'因为文档说实际应用程序代码不应该直接使用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());
    }
}

它似乎正在运行 - 会话绑定到bindSession()unbindSession()来电之间的线程,我不必创建事务来实现它。

答案 1 :(得分:3)

我不知道任何针对此的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);
        }
    }

如果您实施了该代码,请在此处发布代码。

答案 2 :(得分:1)

如果您希望在自己的交易中处理每个元素,您需要:

  1. @Transactional
  2. 移除processQueue
  3. elementCanBeProcessed逻辑移至单独的ElementProcessor @Component班级,并使用elementCanBeProcessed
  4. 注释@Transactional
  5. 致电elementProcessor.elementCanBeProcessed(element);
  6. 因为elementProcessor是一个单独的bean,它会被Spring代理,因此调用将被TransactionInterceptor拦截。

答案 3 :(得分:0)

避免跨任务长时间运行的事务,以便事务很小并且不太可能发生冲突/回滚。以下是我在没有JTA和@Transactional的情况下依赖JPA的原因:

注入实体管理器或从Spring上下文获取它或将其作为参考传递:

@PersistenceContext
private EntityManager entityManager;    

然后创建一个新的实体管理器,以避免使用共享管理器:

EntityManager em = entityManager.getEntityManagerFactory().createEntityManager();

现在您可以启动事务并使用Spring DAO,Repository,JPA等

private void save(EntityManager em) {

    try
    {         
        em.getTransaction().begin();                

        <your database changes>

        em.getTransaction().commit();                        
    }
    catch(Throwable th) {
        em.getTransaction().rollback();
        throw th;
    }        
}

您还需要在数据库@Configuration中提供JPA bean,但很可能您已经拥有了。