Hibernate的Session和EntityManager共享同一个事务吗?

3

我们目前在应用程序中使用Hibernate 3.6。应用程序中充满了对Hibernate Session、Spring HibernateTemplate的使用 - 最近我们开始将代码迁移到更标准的Entity Manager,但这是一个逐步的过程。

所以我遇到了一个问题,我们在一个方法(@Transactional的服务方法)中同时使用Hibernate Session和Entity Manager,并且一个操作依赖于另一个操作,就像这样:

hqlQuery = session.createQuery("DELETE FROM address WHERE user_id = 1");
hqlQuery.executeUpdate();

jpaQuery = em.createQuery("DELETE FROM user WHERE id = 1");
jpaQuery.executeUpdate();
// this throws org.hibernate.exception.ConstraintViolationException - cannot delete user because there are still references to this user in address

我怀疑这可能是因为Hibernate在Session和EntityManager中使用不同的事务(在不同的连接中)-地址的删除或添加在一个事务中完成,而用户的删除在第二个事务中完成-那么很明显,由于外键约束,用户的删除失败了。

Session和EntityManager共享相同的事务管理器,但我不知道这是否重要。

我想通过从session.getTransaction()和em.getTransaction()获取一些事务ID来测试这一点,但没有标识符。

所以问题是 - 是否可以在Session和EntityManager之间共享一个事务?如果可以,如何实现?

谢谢!


你是否使用Hibernate作为你的JPA提供者?所有实体都在Hibernate和JPA中映射了吗? - neildo
是的,Hibernate被用作JPA提供程序。所有实体都在JPA和Hibernate中进行了映射。本质上,我们正在使用JPA EntityManager来完成我们以前使用HibernateTemplate/Session完成的相同任务。 - qbd
1
你可以考虑使用EntityManager.unwrap(Session.class)来获取Hibernate Session,而不是同时拥有Hibernate SessionFactory和EntityManagerFactory:http://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html#unwrap(java.lang.Class) - neildo
2个回答

2

好的,EntityManager 是与 Java Persistence API 相关的接口,而 Session(或者更确切地说是 SessionImpl)是 Hibernate 提供的 EntityManager 的实现。

在运行时,em.createQuery() 返回 javax.persistence.Query,而 session.createQuery() 返回 org.hibernate.query.Query,后者又扩展了 javax.persistence.Query :)

因此,在底层你处理的是相同的代码。

现在,只要你的代码在同一个使用@Transactional注解的方法中执行,该方法内的所有操作将共享同一个事务。为了确保,你可以记录事务的开始和结束。

为了解决你的问题,我认为你需要重写你的代码来使用原生查询,因为据我所理解,user_id 引用的是表列而不是实体字段:

hqlQuery = session.createNativeQuery("DELETE FROM address WHERE user_id = 1");
hqlQuery.executeUpdate();

jpaQuery = em.createNativeQuery("DELETE FROM user WHERE id = 1");
jpaQuery.executeUpdate();

然而,如果您处理实体并且希望保留*.createQuery(),则需要在hqlQuery.executeUpdate()之后执行session.flush()

重要的是要理解,在没有映射到涉及表格的两个实体的情况下,em/Session.createQuery()将无法正常工作。请参阅此答案:https://stackoverflow.com/a/40645955/12473843


0

虽然已经有一段时间了,但如果我没记错的话,它应该是你所写的那样:Hibernate会话和EntityManager都与数据库建立连接 - 因此,在同时使用两者时必须确保它们共享同一个事务。 我会像这样直接从EntityManager中获取会话:

// the old way
Session session = ((HibernateEntityManager)em).getSession();

自从Hibernate 5版本起,HibernateEntityManager已被弃用,因此您应该使用unwrap方法,这是一个标准的JPA方法,可以使您的代码在不同的JPA提供商之间更加可移植。但是我提到它是为了防止您卡在旧版本的Hibernate上而无法升级...

Session session = em.unwrap(Session.class);

Query hqlQuery = session.createQuery("DELETE FROM address WHERE user_id = 1");
hqlQuery.executeUpdate();

Query jpaQuery = em.createQuery("DELETE FROM user WHERE id = 1");
jpaQuery.executeUpdate();

另一种可能性是完成第一笔交易,然后再进行用户删除。但这样做有几个缺点,比如手动管理回滚和基于注释和手动事务处理之间的“混合”。我还没有测试过这个方法,而且异常处理被省略了:

//Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction(); // or session.getTransaction()

hqlQuery = session.createQuery("DELETE FROM address WHERE user_id = 1");
hqlQuery.executeUpdate();

session.flush();
tx.commit();
//session.close();

//now do the user deletion
jpaQuery = em.createQuery("DELETE FROM user WHERE id = 1");
jpaQuery.executeUpdate();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接