java - 在Hibernate中重新附加分离对象的正确方法是什么?
我有一种情况需要将分离的对象重新附加到休眠会话,尽管会话中已经存在相同标识的对象,这将导致错误。
现在,我可以做两件事之一。
getHibernateTemplate().merge( obj )当且仅当对象在hibernate会话中不存在时,这才有效。 抛出异常,说明当我稍后需要时,会话中已存在具有给定标识符的对象。
getHibernateTemplate().merge( obj )当且仅当hibernate会话中存在对象时,此方法才有效。 如果我使用它,当我需要稍后在会话中的对象时抛出异常。
鉴于这两种情况,我如何一般地将会话附加到对象? 我不想使用异常来控制这个问题解决方案的流程,因为必须有一个更优雅的解决方案......
15个解决方案
170 votes
所以似乎没有办法在JPA中重新附加陈旧的分离实体。
detach()将陈旧状态推送到DB,并覆盖任何干预更新。
无法在分离的实体上调用detach()。
detach()无法在分离的实体上调用,即使它可以,它确实重新附加了实体,用参数'LockMode.NONE'调用'lock'暗示你是锁定的,但没有锁定,是我见过的最具反直觉的API设计。
所以你被卡住了。有一个detach()方法,但没有attach()或reattach()。您无法使用对象生命周期中的明显步骤。
根据有关JPA的类似问题的数量来判断,似乎即使JPA声称拥有一个连贯的模型,它肯定与大多数程序员的心理模型不符,谁被诅咒浪费了很多时间才能理解如何让JPA做最简单的事情,最终得到缓存管理代码遍布其应用程序。
似乎唯一的方法是丢弃陈旧的分离实体并执行具有相同ID的查找查询,该查询将命中L2或DB。
MIK
mikhailfranco answered 2019-04-02T17:03:29Z
23 votes
所有这些答案都错过了一个重要的区别。 update()用于(重新)将对象图附加到Session。 您传递的对象是被管理的对象。
merge()实际上不是(重新)附件API。 注意merge()有一个返回值? 那是因为它会返回托管图,这可能不是您传递的图。 merge()是一个JPA API,其行为受JPA规范的约束。 如果传入merge()的对象已经被管理(已经与Session关联)那么这就是Hibernate使用的图形; 传入的对象是从merge()返回的相同对象。 但是,如果传递给merge()的对象被分离,则Hibernate会创建一个受管理的新对象图,并将状态从已分离的图复制到新的托管图上。 同样,这完全由JPA规范决定和管理。
就“确保管理此实体或使其管理”的通用策略而言,这取决于您是否还要考虑尚未插入的数据。 假设你这样做,请使用类似的东西
if ( session.contains( myEntity ) ) {
// nothing to do... myEntity is already associated with the session
}
else {
session.saveOrUpdate( myEntity );
}
注意我使用了saveOrUpdate()而不是update()。 如果您不想在此处理尚未插入的数据,请使用update()代替...
Steve Ebersole answered 2019-04-02T17:04:31Z
20 votes
Undiplomatic答案:您可能正在寻找扩展的持久化上下文。 这是Seam框架背后的主要原因之一...如果您特别想在Spring中使用Hibernate,请查看这篇Seam的文档。
外交答案:这在Hibernate文档中有所描述。 如果您需要更多说明,请查看Java Persistence with Hibernate的第9.3.2节“使用分离对象”。 如果你做的不仅仅是使用Hibernate的CRUD,我强烈建议你拿到这本书。
cwash answered 2019-04-02T17:05:09Z
12 votes
如果您确定您的实体未被修改(或者您同意任何修改将丢失),那么您可以将其重新连接到具有锁定的会话。
session.lock(entity, LockMode.NONE);
它将不会锁定任何内容,但它会从会话缓存中获取实体,或者(如果没有找到)从数据库中读取它。
当您从“旧”(例如来自HttpSession)实体导航关系时,防止LazyInitException非常有用。 您首先“重新附加”该实体。
使用get也可以工作,除非你获得映射的继承(这将在getId()上抛出异常)。
entity = session.get(entity.getClass(), entity.getId());
John Rizzo answered 2019-04-02T17:06:03Z
9 votes
我回到了JavaDoc lock(),发现了以下内容:
可通过致电lock(),saveOrUpdate()或 lock().通过调用replicate()可以使持久化实例成为瞬态。merge()或load()方法返回的任何实例都是持久的。 可以通过调用update(),saveOrUpdate(),lock()或replicate()使分离的实例持久化。也可以通过调用merge()使瞬态或分离的实例的状态成为新的持久实例。
因此,lock(),saveOrUpdate(),lock(),replicate()和merge()是候选选项。
lock():如果存在具有相同标识符的持久性实例,则将抛出异常。
lock():保存或更新
lock():已弃用
lock():保留给定分离实例的状态,重用当前标识符值。
lock():返回具有相同标识符的持久对象。 给定的实例不会与会话关联。
因此,不应该直接使用lock(),并且基于功能要求,可以选择它们中的一个或多个。
Amitabha Roy answered 2019-04-02T17:07:40Z
7 votes
我用NHibernate在C#中这样做了,但它应该在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,因为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;
}
Verena Haunschmid answered 2019-04-02T17:08:23Z
4 votes
Session.contains(Object obj)检查引用,并且不会检测表示同一行并且已附加到其上的其他实例。
这是我的具有标识符属性的实体的通用解决方案。
public static void update(final Session session, final Object entity)
{
// if the given instance is in session, nothing to do
if (session.contains(entity))
return;
// check if there is already a different attached instance representing the same row
final ClassMetadata classMetadata = session.getSessionFactory().getClassMetadata(entity.getClass());
final Serializable identifier = classMetadata.getIdentifier(entity, (SessionImplementor) session);
final Object sessionEntity = session.load(entity.getClass(), identifier);
// override changes, last call to update wins
if (sessionEntity != null)
session.evict(sessionEntity);
session.update(entity);
}
这是我喜欢的.Net EntityFramework的少数方面之一,关于已更改实体及其属性的不同附加选项。
djmj answered 2019-04-02T17:09:08Z
3 votes
我想出了一个解决方案来从持久性存储中“刷新”一个对象,该对象将解释可能已经附加到会话的其他对象:
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);
}
WhoopP answered 2019-04-02T17:09:41Z
2 votes
对不起,似乎无法添加评论(还没?)。
使用Hibernate 3.5.0-Final
虽然不推荐使用Session#lock方法,但javadoc建议使用Session#buildLockRequest(LockOptions)#lock(entity),如果确保您的关联有cascade=lock,则延迟加载也不是问题。
所以,我的attach方法看起来有点像
MyEntity attach(MyEntity entity) {
if(getSession().contains(entity)) return entity;
getSession().buildLockRequest(LockOptions.NONE).lock(entity);
return entity;
初步测试表明它是一种享受。
Gwaptiva answered 2019-04-02T17:10:44Z
1 votes
尝试getHibernateTemplate()。replicate(entity,ReplicationMode.LATEST_VERSION)
Pavitar Singh answered 2019-04-02T17:11:28Z
1 votes
在原帖中,有两种方法,session.contains(obj)和merge(obj)被提到工作,但在相反的情况下。 如果确实如此,那么为什么不先测试对象是否已经在会话中,然后再调用update(obj),否则请调用merge(obj)。
会话中存在的测试是session.contains(obj).因此,我认为以下伪代码可以工作:
if (session.contains(obj))
{
session.update(obj);
}
else
{
session.merge(obj);
}
John DeRegnaucourt answered 2019-04-02T17:12:09Z
1 votes
也许它在Eclipselink上的表现略有不同。 要重新附加分离的对象而不获取过时的数据,我通常会这样做:
Object obj = em.find(obj.getClass(), id);
并作为可选的第二步(使缓存无效):
em.refresh(obj)
Hartmut P. answered 2019-04-02T17:13:02Z
1 votes
要重新附加此对象,必须使用merge();
此方法接受参数您的实体分离并返回一个实体将附加并从数据库重新加载。
Example :
Lot objAttach = em.merge(oldObjDetached);
objAttach.setEtat(...);
em.persist(objAttach);
Ryuku answered 2019-04-02T17:13:44Z
0 votes
调用第一个merge()(更新持久化实例),然后锁定(LockMode.NONE)(以附加当前实例,而不是merge()返回的实例)似乎适用于某些用例。
cheesus answered 2019-04-02T17:14:41Z
-6 votes
try getHibernateTemplate().saveOrUpdate()
Ben Hammond answered 2019-04-02T17:15:11Z