java文件分离和附加,关于java:在Hibernate中重新附加分离对象的正确方法是什么?...

我遇到一种情况,我需要将分离的对象重新附加到休眠会话,尽管会话中可能已经存在相同标识的对象,这将导致错误。

现在,我可以做两件事之一。

getHibernateTemplate().update( obj )

当且仅当休眠会话中不存在对象时,此方法才有效。 当以后需要时,会抛出异常,说明会话中已经存在具有给定标识符的对象。

getHibernateTemplate().merge( obj )

仅当休眠会话中存在对象时,此方法才有效。 如果我以后使用该对象,则当我需要该对象进入会话时,将引发异常。

在这两种情况下,如何将会话附加到对象上? 我不想使用异常来控制此问题的解决方案的流程,因为必须有一个更优雅的解决方案...

因此,似乎没有办法在JPA中重新附加陈旧的独立实体。

merge()会将陈旧状态推送到数据库,

并覆盖所有中间更新。

refresh()不能在分离的实体上调用。

lock()不能在分离的实体上调用,

即使可以,而且确实重新附加了实体,

用参数'LockMode.NONE'调用'lock'

表示您正在锁定但未锁定,

是我见过的最违反直觉的API设计。

所以你被困住了。

有一个detach()方法,但没有attach()或reattach()。

您无法使用对象生命周期中明显的步骤。

从有关JPA的类似问题的数量来看,

似乎即使JPA确实声称拥有一致的模型,

它肯定与大多数程序员的思维模式不符,

那些被诅咒浪费很多时间试图理解的人

如何让JPA做最简单的事情,并最终获得缓存

所有应用程序中的管理代码。

看来唯一的方法就是丢弃陈旧的分离实体

并执行具有相同ID的查找查询,这将命中L2或数据库。

米克

我想知道是否有JPA规范在分离的实体上不允许refresh()的原因?查看2.0规范,我看不出任何理由。只是不允许这样做。

这绝对是不准确的。从JPwH:*Reattaching a modified detached instance* A detached instance may be reattached to a new Session (and managed by this new persistence context) by calling update() on the detached object. In our experience, it may be easier for you to understand the following code if you rename the update() method in your mind to reattach()—however, there is a good reason it’s called updating.更多内容请参见第9.3.2节

持久对象工作得很好,脏标志是根据初始负载和flush()时的值之间的差值设置的。分离的对象需要并且当前不具有此功能。休眠的方法是为分离的对象添加额外的哈希/ id。并保留可用的分离对象的最后状态快照,就像它们对持久对象一样。因此,他们可以利用所有现有代码,并使它适用于分离的对象。就像@mikhailfranco指出的那样,我们不会"将过时的状态推送到数据库,并覆盖所有中间的更新"

根据Hibernate javadoc(但不是JPA),实际上可以在临时对象上调用lock(LockMode.NONE),并且确实将实体重新附加到会话上。见stackoverflow.com/a/3683370/14379

锁对我不起作用:java.lang.IllegalArgumentException:实体不在org.hibernate.internal.SessionImpl.lock(SessionImpl.java:3491)的持久性上下文中,而在org.hibernate.internal.SessionImpl.lock(SessionImpl.java: 3482)在com.github.vok.framework.DisableTransactionControlEMDelegate.lock(DB.kt)

所有这些答案都没有一个重要的区别。 update()用于将对象图(重新)附加到会话。您传递给它的对象就是被管理的对象。

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()。

这是此问题的正确答案-结案了!

Session.contains(Object)通过引用进行检查。如果已经有另一个Entity代表会话中的同一行,并且您传递了一个分离的实例,则将获得异常。

正如Session.contains(Object)通过引用检查一样,如果会话中存在另一个表示同一行的Entity,它将返回false,并将对其进行更新。

不外交的答案:您可能正在寻找扩展的持久性上下文。这是Seam Framework背后的主要原因之一...如果您特别想在Spring中使用Hibernate,请查看此Seam的文档。

外交回答:在Hibernate文档中对此进行了描述。如果需要更多说明,请参阅Java Persistence with Hibernate的9.3.2节,称为"使用分离的对象"。如果您使用Hibernate进行CRUD以外的工作,我强烈建议您阅读这本书。

来自seamframework.org:" Seam 3的积极开发已被Red Hat停止。"" Seams docs"链接也已失效。

如果您确定您的实体尚未被修改(或者如果您同意任何修改将丢失),则可以使用锁定将其重新连接到会话。

session.lock(entity, LockMode.NONE);

它不会锁定任何内容,但是会从会话缓存中获取实体,或者(如果未在其中找到)从数据库读取实体。

当您从"旧"(例如HttpSession)实体导航关系时,防止LazyInitException非常有用。您首先要"重新附加"实体。

使用get也可能会起作用,除非您继承映射时(继承已在getId()上引发异常)。

entity = session.get(entity.getClass(), entity.getId());

我想将一个实体与会话重新关联。不幸的是,Session.lock(entity, LockMode.NONE)失败,并带有异常消息:无法重新关联未初始化的瞬时集合。如何克服这个问题?

实际上我并不完全正确。使用lock()可重新附加您的实体,但不附加绑定到该实体的其他实体。因此,如果您执行entity.getOtherEntity()。getYetAnotherEntity(),则可能会有LazyInit异常。我知道要克服的唯一方法是使用查找。实体= em.find(entity.getClass(),实体.getId();

没有Session.find() API方法。也许您的意思是Session.load(Object object, Serializable id)。

回到org.hibernate.Session的JavaDoc,发现以下内容:

Transient instances may be made persistent by calling save(), persist() or

saveOrUpdate(). Persistent instances may be made transient by calling delete(). Any instance returned by a get() or load() method is persistent. Detached instances may be made persistent by calling update(), saveOrUpdate(), lock() or replicate(). The state of a transient or detached instance may also be made persistent as a new persistent instance by calling merge().

因此,update(),saveOrUpdate(),lock(),replicate()和merge()是候选选项。

update():如果存在具有相同标识符的持久实例,则将引发异常。

saveOrUpdate():保存或更新

lock():已弃用

replicate():保留给定的分离实例的状态,重新使用当前标识符值。

merge():返回具有相同标识符的持久对象。给定的实例不与会话关联。

因此,不应直接使用lock(),并且可以根据功能要求选择其中的一个或多个。

我用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();

}

}

}

因为Contains始终为false,所以在每个对象上调用First Lock。问题是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;

}

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的几个方面之一,是有关更改的实体及其属性的不同附加选项。

我想出了一种从持久性存储中"刷新"对象的解决方案,该对象将考虑可能已附加到会话的其他对象:

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确实建议使用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;

初步测试表明它可以治疗。

也许它在Eclipselink上的行为略有不同。为了重新附加分离的对象而又不获取陈旧的数据,我通常这样做:

Object obj = em.find(obj.getClass(), id);

作为可选的第二步(使缓存无效):

em.refresh(obj)

要重新附加该对象,必须使用merge();。

此方法在参数中接受您的实体已分离并返回的实体将被附加并从数据库中重新加载。

Example :

Lot objAttach = em.merge(oldObjDetached);

objAttach.setEtat(...);

em.persist(objAttach);

尝试getHibernateTemplate()。replicate(entity,ReplicationMode.LATEST_VERSION)

在原始帖子中,提到了两种方法,即update(obj)和merge(obj)可以工作,但是在相反的情况下。如果确实如此,那么为什么不先测试以查看对象是否已在会话中,然后调用update(obj)(如果存在),否则调用merge(obj)。

会话中是否存在测试为session.contains(obj)。因此,我认为以下伪代码会起作用:

if (session.contains(obj))

{

session.update(obj);

}

else

{

session.merge(obj);

}

contains()检查通过引用进行比较,但是休眠函数通过数据库ID进行工作。 session.merge将永远不会在您的代码中被调用。

Hibernate支持通过服务方式重新连接分离的实体,请参见[https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#pc-detach-reattach]

在某些用例中,先调用merge()(以更新持久性实例),然后调用lock(LockMode.NONE)(以附加当前实例,而不是由merge()返回的实例)。

try getHibernateTemplate().saveOrUpdate()

与更新一样,saveOrUpdate将引发异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值