我相信我可能对持久性上下文的 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字段。 返回。long
6L
EntityManager#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()
。
哪种解释是正确的?我希望是后者。谢谢阅读。