2

我相信我可能对持久性上下文的 JPA 概念以及该方法如何使用它有一个根本性的误解EntityManager#merge(Object)

背景

动机

我希望能够依赖某个merge()我突然意识到可能不会发生的操作行为。具体来说,我想依靠它隐含的UPSERT能力。出于这个问题的目的,我对级联行为或任何类型的缓存不感兴趣。此外,在这个问题中,我对一个供应商可能如何做事或另一个供应商如何做事不感兴趣;我对语言和要求感兴趣。

这个问题很长,故意重复了好几次;我发现不是每个人都以同样的方式围绕它,所以我想以多种不同的方式呈现它。请原谅冗长。

规范引用

JPA 2.0 规范在第7.1 节中说:

持久性上下文是一组托管实体实例,其中任何持久性实体身份都有一个唯一的实体实例[强调我的]。在持久化上下文中,实体实例及其生命周期由实体管理器管理。

(所以这一段说:考虑一组对象;让我们将其命名为持久性上下文。它的成员将由 an 管理它们的生命周期EntityManager,并将对应于它们在数据库中匹配的持久性表示。@Id换句话说,它们的 s 将匹配数据库中对应的主键。)

请注意,本段没有说明对象如何进入集合(实体如何进入持久性上下文),只说明一旦您确实在其中有对象,哪些条件必须为真。关于事物如何进入集合的规则在规范的其他地方进行了规定。

最后,为了完整起见,说一个空的持久性上下文在语义上似乎是可以的——它是在你做之后产生的持久性上下文EntityManager#clear()

然后第 3.2.7.1 节说:

应用于实体 X 的合并操作的语义如下:

• 如果X 是分离实体,则X 的状态被复制到具有相同身份的预先存在的受管实体实例X' 上,或者创建X 的新受管副本X'。

问题

当我们谈到持久性上下文中预先存在的托管实体实例时,预先存在的确切含义是什么?

或者:以一种迂回的方式,我真的在问什么clear():它真的清空持久性上下文,还是只清空我当前可能保留引用的对象

或者:假设我的持久性上下文是空的(我刚刚调用了EntityManager#clear())。是否有任何身份的“预先存在的托管实体实例” ?是否所有数据库行本质上都存在于持久性上下文中——即clear()操作并不意味着持久性上下文真的是的,只是你不能在不添加任何东西的情况下从中得到任何东西?

或者:在它的merge()操作实现中,是本段要求的实现——有时EntityManager——去尝试即时查找、实例化和管理 X',这样就好像持久化上下文在时间被称为,像这样:merge()merge()

// Assuming a previously empty persistence context (em.clear()).
// Pseudocode follows as though this were part of the
// EntityManager merge() implementation.
//
// Here "pre-existing" means "not there to start with,
// but located just in time and thus snuck into the
// persistence context from disk as though it had been
// there all along".  This would mean that things like
// primary key violations and the like at flush() time would be 
// effectively prohibited by spec.
SomeEntity xPrime = this.find(SomeEntity.class, x.getID());
if (xPrime == null) {
  xPrime = new SomeEntity();
  manage(xPrime);
}
assert isManaged(xPrime); // hypothetical method
copyProperties(x, xPrime);
return xPrime;

在这种情况下是否X'说是预先存在的,即使当时merge()被称为持久性上下文是空的?

或者,一个符合EntityManager者可以简单地做:

// Assuming a previously empty persistence context.
// Pseudocode follows as though it were part of the
// EntityManager merge() implementation.
//
// Here "pre-existing" means "already in the persistence context",
// which given that we're assuming we started with an empty one,
// means we just do a new instance here.  This scares me but I can't
// see how the spec rules this out.
SomeEntity xPrime = new SomeEntity();
copyProperties(x, xPrime);
manage(xPrime);
assert isManaged(xPrime); // hypothetical method
return xPrime;

这种解释的理由是句子的一部分说“......或者创建了 X 的新托管副本 X”,这让我认为 JPA 供应商可以决定是否要费心去做find()第一个。在第二种解释下,在find()隐式尝试 no 的情况下,flush()有时您可能会遇到主键违规——JPA 提供程序可能会尝试实际发生的INSERT地方。UPDATE

这是更具体的同一个问题:

假设我的持久性上下文是空的(我有,比如说,称为EntityManager#clear())。

假设我手中有一个分离的实体X,带有一个包含 value的@Id-annotated字段。 返回。long6LEntityManager#contains(x)false

假设我的x数据库中有一个表,其中有一行,其主键是6,并进一步假设我的X实体映射到它。

(提早打破我的妙语:我们可以说X'在这一点上作为托管实体实例预先存在吗?它是否隐含地“预先存在”,因为它可以立即,即时管理,所以就好像它存在吗?回想一下我们刚刚调用了em.clear().)

现在假设我调用EntityManager#merge(Object)了那个分离的实体(X)。

根据规范,“将 X 的状态复制到具有相同身份的预先存在的托管实体实例 X' 上,或者创建 X 的新托管副本 X'”。

在持久性上下文中是否存在“具有相同身份的预先存在的托管实体实例 X”(6在这种情况下)?回想一下我们刚刚调用了em.clear().

一种解释说:不,当然没有;持久化上下文为空;我们clear()编辑了它;里面什么都没有;所以EntityManager可以回退到句子的第二部分(“...或创建 X 的新托管副本 X'”)。

另一个说:是的,有。在这种EntityManager情况下,规范要求获取 myX的标识符,使用它来快速尝试等效的find()操作,该操作在执行时会静默放置到持久性上下文中,就好像它在有效开始实际X'操作之前一直存在一样操作的语义merge()

哪种解释是正确的?我希望是后者。谢谢阅读。

4

1 回答 1

0

虽然这不是对此处提出的问题的答案,但我想添加与所述上下文相关的 2 美分

如果 X 是分离实体,则 X 的状态被复制到具有相同身份的预先存在的受管实体实例 X' 上,或者创建 X 的新受管副本 X'

我注意到,当从使用某些 ETL 作业填充的表中加载 JPA 中的实体时,这些实体“似乎”在 JPA 中分离。当我使用它们的查找器方法读取这些实体时,它们可以正常读取。但是当我想更新其中一个时,在此处和那里更改字段值后,通过调用 JPA 存储库上的 saveEntity 或 updateEntity 方法,结果是一个新实体 - 精确副本 - 但具有不同的 ID,并且我所做的更改做了一些领域。

原始 ID 继续坐着,拒绝更改/更新。从这一点开始,新实体 (X') 将在更新时表现良好,这意味着 X' 将接受程序命令的更改和更新。然而,原始实体继续拒绝更新。

因此,通过创建“X 的新托管副本 X”,它看起来符合规范。但我(还)不明白的是,为什么现有的实体/行应该从一开始就被视为分离的。

这是更详细描述的链接: SqlExceptionHelper - Duplicate entry for Unique key Using Spring Data JPA

于 2013-09-30T19:02:17.813 回答