我们通过JPA和Spring使用Hibernate来管理web应用程序中的对象持久性。我们使用open-session-in-view模式为响应http请求的线程创建会话。我们还使用了一些不生成视图的线程——它们只是不时地唤醒来完成它们的工作。这会产生问题因为默认情况下没有打开会话所以会产生异常,比如
org.hibernate.SessionException: Session is closed!
或
could not initialize proxy - no Session
我们发现,如果每个后台线程都在一个带有@Transactional
注释的方法中调用它的逻辑,那么就没有这种异常,因为@Transactional
确保线程在事务内部时有会话。
它解决了一段时间的问题,但我不认为这是一个很好的解决方案-使长时间运行的方法事务性会导致问题,因为其他线程无法看到在数据库中所做的更改,直到事务提交。
我创建了一个java伪代码示例来更好地说明我的问题:
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
}
}
如果我用@Transactional
注释整个processQueue
方法,
elementDao.saveIntoDatabase(element);
在事务提交之前(也就是在整个队列被处理之前),secondThread
中不会看到。如果我不这样做,那么线程将不会在elementCanBeProcessed
内部有会话,它将无法访问数据库。我也不能注释elementCanBeProcessed
,因为它是这个类中的私有方法,我必须将其移动到另一个类中,以便Spring代理可以工作。
这是我在阅读了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());
}
}
它似乎正在工作-会话在bindSession()
和unbindSession()
调用之间绑定到我的线程,我不必创建一个事务来实现它。
我不知道有什么spring现成的解决方案。我认为你需要实现一个类似于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);
}
}
如果你实现了,请把代码贴在这里作为答案。
如果您希望每个元素在自己的事务中处理,则需要:
- 从
processQueue
中删除@Transactional
- 将
elementCanBeProcessed
逻辑移动到单独的ElementProcessor @Component
类中,并使用@Transactional
注释elementCanBeProcessed
呼叫
elementProcessor.elementCanBeProcessed(element);
因为elementProcessor
是一个独立的bean,它被Spring代理,因此调用将被TransactionInterceptor
截获。
避免跨任务长时间运行的事务,使事务较小,并且不太可能发生冲突/回滚。以下是我在没有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,但很可能您已经有了。
基于Amir和Pawel的回答以及JpaTemplate的想法,我创建了一个组件来完成模板角色:
@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);
}
}
}
然后像这样从调用代码中使用:
entityManagerProvider.withEntityManager(() -> {
... code using JPA operations ...
});