Hibernate使用旧的封闭式SessionImpl代替给定的SessionImpl

时间:2018-12-10 09:59:37

标签: ajax hibernate jsf hibernate-envers

我对Hibernate Enverse(版本5.2.0-最终版)有疑问。

上下文: 我正在审核一些存在懒惰关系的实体。我有一个jsf页,可加载一个实体的一个版本以及该版本的所有关系。很好所以现在我有一个页面,显示该实体的修订版以及该修订版的所有关系。在此页面上,我可以打开一个触发AJAX的字段集。在此请求中,我们通过调用entityManager.merge(entity)重新附加所有关系,以便能够获取此字段集中的惰性关系。 (EntityManager是RequestScoped)

问题: AJAX是一个新请求。服务器调用entityManager.merge(entity),它强制创建新的EntityManager(因此创建了新的org.hibernate.internal.SessionImpl)。在此对象上,休眠调用SessionImpl.merge(...)。但是在方法org.hibernate.internal.AbstractSharedSessionContract.createQuery(String)中,使用了另一个SessionImpl对象,该对象之前已在请求中关闭。这会强制执行java.lang.IllegalStateException: Session/EntityManager is closed

用一句话:尽管创建了一个新的entityManager,并在该新的entityManger上调用了合并,但是Hibernate以前使用了请求的旧Session / 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?

  • 有人知道该问题的解决方案或解决方法吗?

非常感谢您的任何想法。我花了几天时间解决这个错误。

1 个答案:

答案 0 :(得分:0)

  

为什么不使用Session中的o.h.type.CollectionType.getElementIterator参数?

简短的答案是它不是必需的,它只是8年前的向后兼容性问题。

长答案是一种类型系统,该类型系统用于根据用户是否已指定会话在EntityMode.MAPEntityMode.POJO中进行操作来实际偏离某些行为,因此需要知道什么类型会话所处的模式;因此,为什么要通过。

但是即使在2011年,当更改此设置时,会话参数也仅在会话在EntityMode.MAP中运行时才影响行为。换句话说,所有其他模式总是直接路由到基础集合Collection#iterator()方法。

尽管如此,这对您在 Debug3 屏幕截图中的体验没有任何影响。

  

这是Hibernate中的错误吗?

不,根据我所读的内容,我认为您正在考虑各种问题。

在Hibernate(无Envers)中,您基本上可以做到这一点

// 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之类的东西,因为它很容易导致真实数据产生意想不到的副作用。

如果您打算跨会话传递审核的实体实例,我强烈建议您改为考虑在获取实例的第一个会话中初始化需要的集合,因为目前尚无办法重新关联具有新会话的已审计实体实例。