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

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

getHibernateTemplate()。更新(obj) 当且仅当对象在hibernate会话中还不存在时,这才有效。当我以后需要它时,抛出异常,声明具有给定标识符的对象已经存在于会话中。 getHibernateTemplate()。合并(obj) 当且仅当hibernate会话中存在对象时,此操作才有效。如果稍后使用此方法,则在需要对象处于会话中时抛出异常。

对于这两种场景,我如何将会话附加到对象?我不想使用异常来控制这个问题解决方案的流程,因为一定有更优雅的解决方案……


当前回答

我这样做在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,因为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;
}

其他回答

如果您确定您的实体没有被修改(或者您同意任何修改都将丢失),那么您可以将其重新绑定到带锁的会话。

session.lock(entity, LockMode.NONE);

它不会锁定任何东西,但它会从会话缓存中获取实体,或者(如果在那里没有找到)从DB中读取它。

当你从一个“旧的”实体(例如HttpSession)中导航关系时,防止LazyInitException是非常有用的。首先“重新附加”实体。

使用get也可以工作,除非你映射了继承(这已经会在getId()上抛出异常)。

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

因此,似乎没有办法在JPA中重新附加一个过时的分离实体。

merge()会将过期状态推送到DB, 并覆盖任何中间更新。

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

不能在分离实体上调用Lock (), 即使它可以,而且它确实重新连接了实体, 使用参数LockMode调用lock。没有' 这意味着你在锁定,但不是锁定, 是我见过的最违反直觉的API设计。

所以你陷入了困境。 有一个detach()方法,但没有attach()或reattach()。 对象生命周期中的一个明显步骤对您来说是不可用的。

根据关于JPA的类似问题的数量判断, 似乎即使JPA声称有一个一致的模型, 它肯定不符合大多数程序员的思维模式, 谁被诅咒浪费了很多时间试图理解 如何让JPA做最简单的事情,并以缓存结束 管理代码遍布他们的应用程序。

要做到这一点,似乎唯一的方法就是抛弃你陈旧的超然实体 并使用相同的id执行find查询,这将命中L2或DB。

Mik

我回到JavaDoc的org.hibernate.Session,发现如下:

通过调用save()、persist()或 saveOrUpdate()。通过调用delete()可以使持久实例成为瞬态实例。get()或load()方法返回的任何实例都是持久的。分离的实例可以通过调用update()、saveOrUpdate()、lock()或replication()来持久化。通过调用merge(),也可以将瞬态或分离实例的状态持久化为新的持久化实例。

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

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

saveOrUpdate():保存或更新

锁():弃用

replication():持久化给定分离实例的状态,重用当前标识符值。

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

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

我想出了一个解决方案,从持久存储中“刷新”一个对象,这将解释其他可能已经附加到会话的对象:

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);
}

也许它在Eclipselink上的表现略有不同。为了重新连接分离的对象而不获得陈旧的数据,我通常这样做:

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

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

em.refresh(obj)