Java 在 Hibernate 中重新附加分离对象的正确方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/912659/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the proper way to re-attach detached objects in Hibernate?
提问by Stefan Kendall
I have a situation in which I need to re-attach detached objects to a hibernate session, although an object of the same identity MAY already exist in the session, which will cause errors.
我有一种情况,我需要将分离的对象重新附加到休眠会话,尽管会话中可能已经存在相同身份的对象,这会导致错误。
Right now, I can do one of two things.
现在,我可以做两件事之一。
getHibernateTemplate().update( obj )
This works if and only if an object doesn't already exist in the hibernate session. Exceptions are thrown stating an object with the given identifier already exists in the session when I need it later.getHibernateTemplate().merge( obj )
This works if and only if an object exists in the hibernate session. Exceptions are thrown when I need the object to be in a session later if I use this.
getHibernateTemplate().update( obj )
当且仅当休眠会话中不存在对象时,这才有效。当我稍后需要它时,会抛出异常,说明具有给定标识符的对象已存在于会话中。getHibernateTemplate().merge( obj )
当且仅当休眠会话中存在对象时,这才有效。如果我使用它,当我稍后需要对象在会话中时会抛出异常。
Given these two scenarios, how can I generically attach sessions to objects? I don't want to use exceptions to control the flow of this problem's solution, as there must be a more elegant solution...
鉴于这两种情况,我如何将会话一般附加到对象?我不想使用异常来控制这个问题的解决方案的流程,因为必须有一个更优雅的解决方案......
回答by Ben Hammond
try getHibernateTemplate().saveOrUpdate()
回答by Pavitar Singh
try getHibernateTemplate().replicate(entity,ReplicationMode.LATEST_VERSION)
尝试 getHibernateTemplate().replicate(entity,ReplicationMode.LATEST_VERSION)
回答by cwash
Undiplomatic answer:You're probably looking for an extended persistence context. This is one of the main reasons behind the Seam Framework... If you're struggling to use Hibernate in Spring in particular, check out this pieceof Seam's docs.
不外交的回答:您可能正在寻找扩展的持久性上下文。这是Seam 框架背后的主要原因之一……如果您特别在 Spring 中努力使用 Hibernate,请查看Seam 的这篇文档。
Diplomatic answer:This is described in the Hibernate docs. If you need more clarification, have a look at Section 9.3.2 of Java Persistence with Hibernatecalled "Working with Detached Objects." I'd stronglyrecommend you get this book if you're doing anything more than CRUD with Hibernate.
外交回答:这在Hibernate 文档中有所描述。如果您需要更多说明,请查看Java Persistence with Hibernate 的第 9.3.2 节,名为“使用分离的对象”。如果您正在使用 Hibernate 做 CRUD 以外的任何事情,我强烈建议您阅读这本书。
回答by John Rizzo
If you are sure that your entity has not been modified (or if you agree any modification will be lost), then you may reattach it to the session with lock.
如果您确定您的实体没有被修改(或者如果您同意任何修改都将丢失),那么您可以将其重新附加到带锁的会话中。
session.lock(entity, LockMode.NONE);
It will lock nothing, but it will get the entity from the session cache or (if not found there) read it from the DB.
它不会锁定任何东西,但它会从会话缓存中获取实体,或者(如果在那里找不到)从数据库中读取它。
It's very useful to prevent LazyInitException when you are navigating relations from an "old" (from the HttpSession for example) entities. You first "re-attach" the entity.
当您从“旧”(例如来自 HttpSession)实体导航关系时,防止 LazyInitException 非常有用。您首先“重新附加”实体。
Using get may also work, except when you get inheritance mapped (which will already throw an exception on the getId()).
使用 get 也可能有效,除非您获得继承映射(这将在 getId() 上引发异常)。
entity = session.get(entity.getClass(), entity.getId());
回答by mikhailfranco
So it seems that there is no way to reattach a stale detached entity in JPA.
所以似乎没有办法在 JPA 中重新附加一个陈旧的分离实体。
merge()
will push the stale state to the DB,
and overwrite any intervening updates.
merge()
将陈旧状态推送到数据库,并覆盖任何干预更新。
refresh()
cannot be called on a detached entity.
refresh()
不能在分离的实体上调用。
lock()
cannot be called on a detached entity,
and even if it could, and it did reattach the entity,
calling 'lock' with argument 'LockMode.NONE'
implying that you are locking, but not locking,
is the most counterintuitive piece of API design I've ever seen.
lock()
不能在分离的实体上调用,即使它可以,并且它确实重新附加了实体,调用带有参数“LockMode.NONE”的“lock”暗示您正在锁定,但没有锁定,这是 API 设计中最违反直觉的部分我见过的。
So you are stuck.
There's an detach()
method, but no attach()
or reattach()
.
An obvious step in the object lifecycle is not available to you.
所以你被卡住了。有一种detach()
方法,但没有attach()
或reattach()
。您无法使用对象生命周期中的一个明显步骤。
Judging by the number of similar questions about JPA, it seems that even if JPA does claim to have a coherent model, it most certainly does not match the mental model of most programmers, who have been cursed to waste many hours trying understand how to get JPA to do the simplest things, and end up with cache management code all over their applications.
从关于 JPA 的类似问题的数量来看,似乎即使 JPA 确实声称有一个连贯的模型,它也肯定不符合大多数程序员的心智模型,他们被诅咒浪费了很多时间试图了解如何获得JPA 做最简单的事情,最终在他们的应用程序中使用缓存管理代码。
It seems the only way to do it is discard your stale detached entity and do a find query with the same id, that will hit the L2 or the DB.
似乎唯一的方法是丢弃陈旧的分离实体并使用相同的 id 执行查找查询,这将命中 L2 或数据库。
Mik
米克
回答by cheesus
calling first merge() (to update persistent instance), then lock(LockMode.NONE) (to attach the current instance, not the one returned by merge()) seems to work for some use cases.
首先调用merge()(更新持久实例),然后调用lock(LockMode.NONE)(附加当前实例,而不是merge() 返回的实例)似乎适用于某些用例。
回答by John DeRegnaucourt
In the original post, there are two methods, update(obj)
and merge(obj)
that are mentioned to work, but in opposite circumstances. If this is really true, then why not test to see if the object is already in the session first, and then call update(obj)
if it is, otherwise call merge(obj)
.
在原来的职位,有两种方法,update(obj)
并且merge(obj)
被提到的工作,但在相反的情况。如果这是真的,那么为什么不先测试一下对象是否已经在会话中,update(obj)
如果是,则调用,否则调用merge(obj)
.
The test for existence in the session is session.contains(obj)
. Therefore, I would think the following pseudo-code would work:
会话中存在的测试是session.contains(obj)
。因此,我认为以下伪代码会起作用:
if (session.contains(obj))
{
session.update(obj);
}
else
{
session.merge(obj);
}
回答by WhoopP
I came up with a solution to "refresh" an object from the persistence store that will account for other objects which may already be attached to the session:
我想出了一个解决方案来“刷新”持久性存储中的一个对象,该对象将解释可能已经附加到会话的其他对象:
public void refreshDetached(T entity, Long id)
{
// Check for any OTHER instances already attached to the session since
// refresh will not work if there are any.
T attached = (T) session.load(getPersistentClass(), id);
if (attached != entity)
{
session.evict(attached);
session.lock(entity, LockMode.NONE);
}
session.refresh(entity);
}
回答by Steve Ebersole
All of these answers miss an important distinction. update() is used to (re)attach your object graph to a Session. The objects you pass it are the ones that are made managed.
所有这些答案都忽略了一个重要的区别。update() 用于(重新)将您的对象图附加到会话。你传递给它的对象是被管理的对象。
merge() is actually not a (re)attachment API. Notice merge() has a return value? That's because it returns you the managed graph, which may not be the graph you passed it. merge() is a JPA API and its behavior is governed by the JPA spec. If the object you pass in to merge() is already managed (already associated with the Session) then that's the graph Hibernate works with; the object passed in is the same object returned from merge(). If, however, the object you pass into merge() is detached, Hibernate creates a new object graph that is managed and it copies the state from your detached graph onto the new managed graph. Again, this is all dictated and governed by the JPA spec.
merge() 实际上不是(重新)附件 API。注意 merge() 有返回值吗?那是因为它返回的是托管图,它可能不是您传递给它的图。merge() 是一个 JPA API,其行为受 JPA 规范约束。如果您传递给 merge() 的对象已经被管理(已经与会话关联),那么这就是 Hibernate 使用的图形;传入的对象与merge() 返回的对象相同。但是,如果您传递给 merge() 的对象已分离,则 Hibernate 会创建一个新的托管对象图,并将状态从您的分离图复制到新的托管图上。同样,这一切都由 JPA 规范规定和管理。
In terms of a generic strategy for "make sure this entity is managed, or make it managed", it kind of depends on if you want to account for not-yet-inserted data as well. Assuming you do, use something like
就“确保该实体被管理或使其被管理”的通用策略而言,这在某种程度上取决于您是否还想考虑尚未插入的数据。假设你这样做,使用类似
if ( session.contains( myEntity ) ) {
// nothing to do... myEntity is already associated with the session
}
else {
session.saveOrUpdate( myEntity );
}
Notice I used saveOrUpdate() rather than update(). If you do not want not-yet-inserted data handled here, use update() instead...
注意我使用了 saveOrUpdate() 而不是 update()。如果您不想在此处处理尚未插入的数据,请改用 update()...
回答by Verena Haunschmid
I did it that way in C# with NHibernate, but it should work the same way in Java:
我在 C# 中使用 NHibernate 是这样做的,但它在 Java 中应该以相同的方式工作:
public virtual void Attach()
{
if (!HibernateSessionManager.Instance.GetSession().Contains(this))
{
ISession session = HibernateSessionManager.Instance.GetSession();
using (ITransaction t = session.BeginTransaction())
{
session.Lock(this, NHibernate.LockMode.None);
t.Commit();
}
}
}
First Lock was called on every object because Contains was always false. The problem is that NHibernate compares objects by database id and type. Contains uses the equals
method, which compares by reference if it's not overwritten. With that equals
method it works without any Exceptions:
对每个对象都调用了 First Lock,因为 Contains 始终为 false。问题是 NHibernate 通过数据库 id 和类型来比较对象。contains 使用该equals
方法,如果它没有被覆盖,则按引用进行比较。使用该equals
方法,它可以正常工作,没有任何异常:
public override bool Equals(object obj)
{
if (this == obj) {
return true;
}
if (GetType() != obj.GetType()) {
return false;
}
if (Id != ((BaseObject)obj).Id)
{
return false;
}
return true;
}