此错误的原因是由于更改了托管实体的实体标识符。
在 PersistenceContext 的生命周期内,任何给定实体只能有一个托管实例。为此,您无法更改现有的托管实体标识符。
在您的示例中,即使您启动一个新事务,您也必须记住 PersistenContext 尚未关闭,因此您仍然有一个托管的c1
附加到 Hibernate 会话的实体。
当您尝试寻找该公司时:
c1 = em.find (Company.class, new Company.Identity("ACURA"));
该标识符与附加到当前会话的公司的标识符不匹配,因此发出查询:
Hibernate: select company0_.name as name1_0_0_ from Company company0_ where company0_.name=?
由于 SQL 不区分大小写,因此您实际上将选择与当前托管 Company 实体相同的数据库行(持久化的c1
).
但是同一数据库行只能有一个托管实体,因此 Hibernate 将重用托管实体实例,但会将标识符更新为:
new Company.Identity("ACURA");
您可以通过以下测试来检查该假设:
String oldId = c1.name;
Company c2 = em.find (Company.class, new Company.Identity("ACURA"));
assertSame(c1, c2);
assertFalse(oldId.equals(c2.name));
当第二个事务提交时,刷新将尝试更新实体标识符(从“Acura”更改为“ACURA”),因此DefaultFlushEntityEventListener.checkId() https://docs.jboss.org/hibernate/orm/4.3/javadocs/org/hibernate/event/internal/DefaultFlushEntityEventListener.html#checkId%28java.lang.Object,%20org.hibernate.persister.entity.EntityPersister,%20java.io.Serializable,%20org.hibernate.engine.spi.SessionImplementor%29方法将会失败。
根据 JavaDoc,此检查用于:
确保用户没有破坏 id
要修复它,您需要删除此 find 方法调用:
c1 = em.find (Company.class, new Company.Identity("ACURA"));
你可以检查一下c1
已经附上:
assertTrue(em.contains(c1));