我们通过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代理可以工作。
是否可以将会话绑定到线程而不会使整个方法成为事务?我应该如何管理像那样的后台线程中的会话和事务?
答案 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)
如果您希望在自己的交易中处理每个元素,您需要:
@Transactional
processQueue
elementCanBeProcessed
逻辑移至单独的ElementProcessor @Component
班级,并使用elementCanBeProcessed
@Transactional
elementProcessor.elementCanBeProcessed(element);
因为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,但很可能您已经拥有了。