为什么在 JPA 中需要分离的实体?

2022-09-01 06:55:15

总是有很多问题与分离实体的问题有关!

首先,它们经常在冬眠中引起。是的,还有另一个持久性提供程序,它们不会引发异常,但我认为它们在一致性方面存在一些问题。考虑我们有和实体,有引用()从到那要求是非空的。LazyInitializationExceptionAB@ManyToOneAB

我们启动了会话,加载了实例,然后关闭了会话。之后,我们尝试获取对 的引用。并假设另一个事务刚刚删除了我们的和实例。因此,当我们从数据库查询时,我们无法找到合适的实例并得到!ABABBnull

所以我们的合同被违反了。一些依赖于返回对象的事实的代码将抛出一个 .对于持久性实体,这是不可能的,因为我们在同一个事务中具有所有延迟加载并获取对象本身,因此所有操作都是原子的(当然,如果我们有适当的事务隔离)。a.getB()NullPointerException

此外,当您想要将持久性和分离的实体存储在一个 中时,也存在问题。在这种情况下,您应该始终覆盖 和 ,这通常看起来很尴尬,因为我看不到一个非常好的方法来做到这一点。SetequalshashCode

要使分离的实体恢复,您应该使用这是故障。EntityManagermerge

所以我的问题是:是否存在真正需要分离实体的合理方案?此外,何时必须混合分离的实体和持久性实体,并将分离的实体合并到新的实体中?EntityManager


答案 1

我将解释为什么这种情况不应该发生,以及为什么我们需要分离的实体。

假设您正在JTA事务中(JPA需要支持它)并获取。现在,您可以在此事务中调用 (1)(即实体被管理)或 (2) 在分离时调用 。aa.getB()aa

方案 1:现在,根据您的事务隔离级别,您可能会看到也可能看不到其他事务的作用。例如,如果您具有可序列化隔离级别,则即使该行已在并发事务中删除,您也会成功读取 。如果该行已被删除,并且您的事务看到该行,则表示您的数据库不一致(无外键),或者您使用了错误的事务隔离级别。a.getB()

方案 2:实体已分离。当抛出 a 时,对我来说,这意味着您调用得太晚了,以便保证应用程序中的一致性(因为不再被管理)。为了解决问题,您只需在实体仍处于托管状态时更早地调用它。NPE 不会发生。aLazyInitializationExceptiona.getB()a

为什么我们需要超然国家?好吧,我们需要一种状态,在该状态下,不会跟踪对实体实例的更改。为什么?

示例 1:假设您在 EJB 层中收到一个实体(具有持久标识),并且没有分离状态(意味着应管理所有实体)。但是,我们需要在持久化实体之前进行验证。如果该实体将自动管理,则其更改将自动保存到 DB 中。因此,这种新状态被引入。

示例 2:您在 EJB 层中收到一个实体,您只需要从该实体更新 5 个字段(共 10 个字段)。如果该实体将自动进入托管状态,则将保留所有 10 个字段。在这种情况下,解决方案是提取托管实体并仅更新该实体中的 5 个字段。


答案 2

已分离 - 已分离的实例是已持久但其会话已关闭的对象。当然,对对象的引用仍然有效,分离的实例甚至可能在此状态下被修改。分离的实例可以在以后的某个时间点重新附加到新会话,使其(以及所有修改)再次持久。此功能为需要用户思考时间的长时间运行的工作单元启用编程模型。我们称它们为应用程序事务,即从用户的角度来看的工作单元。

引用休眠 DOc

为什么?

会话缓存处于持久状态(由 Hibernate 监视并检查脏状态)的每个对象。如果您长时间保持打开状态或只是加载太多数据,它将无休止地增长,直到您获得OutOfMemoryException。一种解决方案是调用 clear() 和 evict() 来管理会话缓存,在用户会话期间保持会话打开也意味着过时数据的可能性更高。

再次引用休眠文档

我敢打赌,你还没有通读休眠文档本身,它有场景解释它们太:)

简单说明:参考持久对象。

假设用户必须更新表单,则通过 UserObject 获取用户的详细信息,此用户对象与会话持久化。现在,如果用户未提交表单,您的会话将处于打开状态,直到服务器会话过期,您将等待多长时间?如果您使用getCurrentSession,则在未提交前一个表单请求时出现另一个表单请求,您现在已经有了脏数据!如果您的对象正在等待 Web 服务的数据,并且花费的时间足够长,那么是否仍会保持会话打开状态,对象持久保存会话?


推荐