在Hibernate中重新挂接分离对象的正确方法是什么?
我有一种情况,我需要将分离的对象重新挂接到一个hibernate会话,虽然同一个标识的对象可能已经存在于会话中,这会导致错误。
现在,我可以做两件事之一。
-
getHibernateTemplate().update( obj )
当且仅当一个对象在hibernate会话中不存在时,它才起作用。 当我稍后需要时,会抛出exception,说明具有给定标识符的对象已经存在于会话中。 -
getHibernateTemplate().merge( obj )
当且仅当hibernate会话中存在一个对象时,它才起作用。 当我需要对象在会话中后,如果我使用这个exception抛出。
鉴于这两种情况,我怎样才能将会话一般附加到对象? 我不想用exception来控制这个问题解决scheme的stream程,因为必须有更优雅的解决scheme。
所以似乎没有办法在JPA中重新挂接一个陈旧的分离实体。
merge()
会将陈旧状态推送到数据库,并覆盖任何干预更新。
refresh()
不能在分离的实体上调用。
lock()
不能在分离的实体上调用,即使它可以,并且它重新连接了实体,用参数'LockMode.NONE'调用'lock'意味着你正在locking,但不locking是最不直观的部分我见过的APIdevise
所以你卡住了。 有一个detach()
方法,但没有attach()
或reattach()
。 对象生命周期中的一个显而易见的步骤不适用于您。
从关于JPA的类似问题的数量来看,似乎即使JPA确实声称有一个连贯的模型,但它绝大部分肯定不符合大多数程序员的心智模式,他们已经被诅咒浪费了许多时间,试图理解如何获得JPA做最简单的事情,并最终在他们的应用程序的cachingpipe理代码。
这似乎是做到这一点的唯一方法是放弃你陈旧的分离的实体,并做一个查询查询具有相同的ID,这将打击L2或数据库。
MIK
无回复的答案:您可能正在寻找一个扩展的持久化上下文。 这是Seam Framework背后的主要原因之一…如果您在Spring中苦于使用Hibernate,请查看Seam的这些文档。
外交回答:这在Hibernate文档中有描述。 如果您需要更多的解释,请参阅Hibernate的Java Persistence第9.3.2节“使用分离的对象”。 我强烈build议你拿到这本书,如果你做的不仅仅是使用Hibernate的CRUD。
所有这些答案都错过了一个重要的区别。 update()用于(重新)将对象graphics附加到Session。 你传递的对象是被pipe理的对象。
merge()实际上不是一个(重新)附件API。 注意merge()有一个返回值? 那是因为它会返回你的托pipe图,这可能不是你传递的图。 merge()是一个JPA API,其行为由JPA规范pipe理。 如果传递给merge()的对象已经被pipe理(已经和Session关联),那么这就是Hibernate使用的图; 传入的对象是merge()返回的同一个对象。 但是,如果传递给merge()的对象被分离,Hibernate将创build一个新的对象图,该对象图将被pipe理,并将分离图中的状态复制到新的受pipe图上。 同样,这一切都是由JPA规范支配的。
就“确保这个实体是被pipe理的,还是被pipe理的”的通用策略而言,这取决于你是否还要考虑尚未插入的数据。 假设你这样做,使用类似的东西
if ( session.contains( myEntity ) ) { // nothing to do... myEntity is already associated with the session } else { session.saveOrUpdate( myEntity ); }
注意我使用saveOrUpdate()而不是update()。 如果您不想在这里处理尚未插入的数据,请使用update(),而不是…
如果您确定您的实体没有被修改(或者您同意任何修改将会丢失),那么您可以将其重新连接到会话locking。
session.lock(entity, LockMode.NONE);
它不会locking任何东西,但会从会话caching中获取实体(如果没有find的话)从数据库中读取。
在从“旧”(例如来自HttpSession)实体的关系导航中防止LazyInitException是非常有用的。 你首先“重新join”实体。
使用get也可能工作,除非你得到映射的inheritance(这将已经在getId()上抛出一个exception)。
entity = session.get(entity.getClass(), entity.getId());
我回到了JavaDoc for org.hibernate.Session
,发现如下:
通过调用
save()
,persist()
或saveOrUpdate()
,可以使瞬态实例持久化。 通过调用delete()
可以使持久化实例变为暂态。 由get()
或load()
方法返回的任何实例都是持久的。 分离的实例可以通过调用update()
,saveOrUpdate()
,lock()
或replicate()
来持久化。 通过调用merge()
,瞬态或分离实例的状态也可以作为新的持久实例持久化。
因此update()
, saveOrUpdate()
, lock()
, replicate()
和merge()
是候选选项。
update()
:如果存在具有相同标识符的持久实例,将会抛出exception。
saveOrUpdate()
:保存或更新
lock()
:已弃用
replicate()
:保留给定分离实例的状态,重用当前的标识符值。
merge()
:返回具有相同标识符的持久对象。 给定的实例不会与会话关联。
因此, lock()
不应该被直接使用,并且根据function需求可以select一个或多个。
我用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和types。 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; }
Session.contains(Object obj)
检查引用,并且不会检测表示同一行并且已经附加到该行的其他实例。
在这里,我的具有标识符属性的实体的通用解决scheme。
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的几个方面之一,关于更改的实体及其属性的不同附加选项。
我想出了一个解决scheme来从持久性存储中“刷新”一个对象,该对象将考虑可能已经附加到会话中的其他对象:
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); }
对不起,似乎无法添加评论(还?)。
使用Hibernate 3.5.0-Final
鉴于Session#lock
方法已被弃用,javadoc 确实build议使用Session#buildLockRequest(LockOptions)#lock(entity)
,如果你确定你的关联有cascade=lock
,那么延迟加载也不是问题。
所以,我的附加方法看起来有点像
MyEntity attach(MyEntity entity) { if(getSession().contains(entity)) return entity; getSession().buildLockRequest(LockOptions.NONE).lock(entity); return entity;
初步testing表明,它是一种享受。
尝试getHibernateTemplate()。replicate(entity,ReplicationMode.LATEST_VERSION)
在原来的文章中,有两种方法, update(obj)
和merge(obj)
被提到起作用,但在相反的情况下。 如果这是真的,那么为什么不先testing一下对象是否已经在会话中,然后调用update(obj)
,否则调用merge(obj)
。
会话中存在的testing是session.contains(obj)
。 因此,我会认为下面的伪代码将起作用:
if (session.contains(obj)) { session.update(obj); } else { session.merge(obj); }
也许它在Eclipselink上的performance略有不同。 要重新附加分离的对象而不会得到陈旧的数据,我通常会这样做:
Object obj = em.find(obj.getClass(), id);
并作为一个可选的第二步(使caching失效):
em.refresh(obj)
要重新附加这个对象,你必须使用merge();
这个方法在参数中接受你的实体分离并返回一个实体将被附加并从数据库中重新加载。
Example : Lot objAttach = em.merge(oldObjDetached); objAttach.setEtat(...); em.persist(objAttach);
首先调用merge()(更新持久化实例),然后locking(LockMode.NONE)(附加当前实例,而不是由merge()返回的实例)似乎适用于某些用例。
try getHibernateTemplate().saveOrUpdate()