我的Hibernate Enverse(5.2.0-Final版本(有问题。
上下文:我正在审核一些具有惰性关系的实体。我有一个jsf页面,它加载一个实体的一个版本以及该版本的所有关系。这很好。现在我有一个页面,显示了实体的一个修订,以及该修订的所有关系。在这个页面上,我可以打开一个触发AJAX的字段集。在这个请求中,我们通过调用entityManager.merge(entity)
来重新附加所有关系,以便能够获取该字段集中的惰性关系。(EntityManager被RequestScoped(
问题:AJAX是一个新的请求。服务器调用entityManager.merge(entity)
,强制创建新的EntityManager(因此创建了一个新的org.hibernate.internal.SessionImpl
(。在这个对象上hibernate调用SessionImpl.merge(...)
。但是在方法org.hibernate.internal.AbstractSharedSessionContract.createQuery(String)
中使用了另一个SessionImpl对象,该对象以前已经在请求中关闭。这强制执行java.lang.IllegalStateException: Session/EntityManager is closed
。
在一句话中:尽管创建了一个新的entityManager,并对该新的entiityManger调用了合并,但Hibernate使用了以前请求的旧会话/entityManager。
我调试了这个问题,发现了以下内容:
-
Debug1:显示具有会话对象id 的
SessionImpl.merge(...)
的堆栈跟踪 -
Debug2:显示具有正确SessionImpl对象的最后一个方法(请参阅其id(。此对象不会在下一个方法中使用。
- Debug3:Debug2之后的步骤不知道给定的SessionImpl对象。它在
collection.initializor.versionsReader
中有自己的SessionImpl对象。此会话是在之前(加载页面时(在请求中创建并关闭的 - Debug4:现在Hibernate想要创建带有关闭的SessionImpl的查询
- Debug5:当会话关闭时,这将强制执行异常
我的问题:
-
这是Hibernate的错误吗?
-
为什么未使用方法
org.hibernate.type.CollectionType.getElementIterater(...)
中给定的SessionImpl? -
有人知道这个问题的解决方案或变通方法吗?
非常感谢你的任何想法。我在这个bug上花了好几天时间。
为什么不使用
o.h.type.CollectionType.getElementIterator
中的Session
arg?
简单的回答是它不是必需的,它只是8年前的向后兼容性问题。
长答案是用于实际偏离一些行为的类型系统,该行为基于用户是否指定了会话以在EntityMode.MAP
或EntityMode.POJO
中操作,因此需要知道会话处于什么模式的类型;因此它被通过了。
但即使早在2011年,当这一点发生变化时,会话参数也只有在会话在EntityMode.MAP
中运行时才会影响行为。换句话说,所有其他模式总是直接路由到底层集合Collection#iterator()
方法。
然而,抛开这些不谈,这对你在Debug3屏幕截图中的体验没有任何影响。
这是Hibernate中的一个错误吗?
不,根据我所读到的内容,我相信你在混合担忧。
在Hibernate(没有信封(中,你基本上可以做这个
// Request 1
request1EntityManager = getEntityManager();
sessionScopeEntity = request1EntityManager.find( MyEntity.class, myEntityId );
// Request 2
request2EntityManager = getEntityManager();
sessionScopeEntity = request2EntityManager.merge( sessionScopeEntity );
for ( SomeCollectionItem Item : sessionScopeEntity.getSomeCollection() ) {
// do things here
}
以上操作之所以有效,是因为您将实体与新会话重新关联,而新会话又将会话注入实体维护的所有未初始化的代理中。但您也可以将上面的内容重写为
// Request 1
request1EntityManager = getEntityManager();
sessionScopeEntity = request1EntityManager.find( MyEntity.class, myEntityId );
sessionScopeEntity.getSomeCollection().size() // initialize collection w/request1Session
// Request 2
request2EntityManager = getEntityManager();
for ( SomeCollectionItem Item : sessionScopeEntity.getSomeCollection() ) {
// do things here
}
不同的是,集合是用第一个会话初始化的,因此当您尝试用第二个会话访问它时,实体不一定需要合并,因为集合不再是代理,而是像正常提取的集合一样实际填充。
Hibernate返回的实体实例和Envers返回的已审核实体实例之间的主要区别在于,已审核的实体实例不是托管持久实体
根据您的场景,您可能决定只审核实体映射上的字段子集。这就是为什么您不能也不应该在该实例中使用merge
之类的东西,因为它很容易导致实际数据出现意外的副作用。
如果您打算在会话之间传递已审核的实体实例,我强烈建议您考虑在提取实例的第一个会话中预先初始化所需的集合,因为目前无法将已审核实体实例与新会话重新关联。