Java 在Hibernate中重新连接分离对象的正确方法是什么?

Java 在Hibernate中重新连接分离对象的正确方法是什么?,java,hibernate,spring,session,detach,Java,Hibernate,Spring,Session,Detach,在这种情况下,我需要将分离的对象重新连接到hibernate会话,尽管会话中可能已经存在具有相同标识的对象,这将导致错误 现在,我可以做两件事中的一件 getHibernateTemplate().update(obj) 当且仅当对象在hibernate会话中不存在时,此方法才有效。当我以后需要时,抛出异常,声明会话中已存在具有给定标识符的对象 getHibernateTemplate().merge(obj) 当且仅当hibernate会话中存在对象时,此操作才有效。如果我以后使用它,当我需要

在这种情况下,我需要将分离的对象重新连接到hibernate会话,尽管会话中可能已经存在具有相同标识的对象,这将导致错误

现在,我可以做两件事中的一件

  • getHibernateTemplate().update(obj)
    当且仅当对象在hibernate会话中不存在时,此方法才有效。当我以后需要时,抛出异常,声明会话中已存在具有给定标识符的对象

  • getHibernateTemplate().merge(obj)
    当且仅当hibernate会话中存在对象时,此操作才有效。如果我以后使用它,当我需要对象在会话中时会抛出异常


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

    尝试getHibernateTemplate().replicate(实体,ReplicationMode.Last_版本)

    非爆破性回答:您可能正在寻找扩展的持久性上下文。这是…背后的主要原因之一。。。如果您特别难以在Spring中使用Hibernate,请查看Seam的文档

    try getHibernateTemplate().saveOrUpdate()
    

    外交回答:这在中有描述。如果您需要更多的说明,请参阅“使用分离对象”的第9.3.2节。如果您使用Hibernate进行的不仅仅是CRUD,我强烈建议您购买本书。

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

    session.lock(entity, LockMode.NONE);
    
    它不会锁定任何内容,但会从会话缓存中获取实体,或者(如果没有找到)从数据库中读取实体

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

    使用get也可以工作,除非您映射了继承(这将在getId()上引发异常)


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

    merge()
    将陈旧状态推送到数据库, 并覆盖任何中间更新

    无法对分离的实体调用
    refresh()

    无法对分离的实体调用
    lock()
    , 即使它可以,而且它确实重新连接了实体, 使用参数“LockMode.NONE”调用“lock” 暗示您正在锁定,但未锁定, 是我见过的最违反直觉的API设计

    所以你被卡住了。 有一个
    detach()
    方法,但没有
    attach()
    resattach()
    。 对象生命周期中的一个明显步骤对您不可用

    从有关JPA的类似问题的数量来看, 看来即使JPA声称有一个连贯的模型, 它肯定不符合大多数程序员的思维模式, 他们被诅咒浪费了很多时间试图理解 如何让JPA做最简单的事情,并最终得到缓存 管理代码遍布其应用程序

    看来唯一的办法就是抛弃你陈旧的分离实体 并使用相同的id执行一个查找查询,该查询将命中L2或DB


    Mik

    首先调用merge()(更新持久实例),然后调用lock(LockMode.NONE)(附加当前实例,而不是merge()返回的实例)似乎适用于某些用例。

    在最初的帖子中,有两种方法,
    更新(obj)
    合并(obj)
    ,它们都提到过要工作,但情况正好相反。如果这是真的,那么为什么不先测试对象是否已经在会话中,然后调用
    update(obj)
    如果已经在会话中,否则调用
    merge(obj)

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

    if (session.contains(obj))
    {
        session.update(obj);
    }
    else 
    {
        session.merge(obj);
    }
    

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

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

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

    merge()实际上不是(重新)附件API。注意merge()有一个返回值吗?这是因为它会返回托管图,而托管图可能不是您传递给它的图。merge()是一个JPA API,其行为由JPA规范控制。如果传入merge()的对象已被管理(已与会话关联),则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()。

    我在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();
            }
        }
    }
    
    对每个对象调用第一个锁,因为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;
    }
    

    我回到了
    org.hiberna的JavaDoc
    
    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;
    }
    
    MyEntity attach(MyEntity entity) {
        if(getSession().contains(entity)) return entity;
        getSession().buildLockRequest(LockOptions.NONE).lock(entity);
        return entity;
    
    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);
    }
    
    Object obj = em.find(obj.getClass(), id);
    
    em.refresh(obj)
    
    Example :
        Lot objAttach = em.merge(oldObjDetached);
        objAttach.setEtat(...);
        em.persist(objAttach);
    
    Book _book = doInJPA(entityManager -> {
        Book book = new Book()
        .setIsbn("978-9730228236")
        .setTitle("High-Performance Java Persistence")
        .setAuthor("Vlad Mihalcea");
     
        entityManager.persist(book);
     
        return book;
    });
    
    _book.setTitle(
        "High-Performance Java Persistence, 2nd edition"
    );
    
    doInJPA(entityManager -> {
        Book book = entityManager.merge(_book);
     
        LOGGER.info("Merging the Book entity");
     
        assertFalse(book == _book);
    });
    
    SELECT
        b.id,
        b.author AS author2_0_,
        b.isbn AS isbn3_0_,
        b.title AS title4_0_
    FROM
        book b
    WHERE
        b.id = 1
     
    -- Merging the Book entity
     
    UPDATE
        book
    SET
        author = 'Vlad Mihalcea',
        isbn = '978-9730228236',
        title = 'High-Performance Java Persistence, 2nd edition'
    WHERE
        id = 1
    
    Book _book = doInJPA(entityManager -> {
        Book book = new Book()
        .setIsbn("978-9730228236")
        .setTitle("High-Performance Java Persistence")
        .setAuthor("Vlad Mihalcea");
     
        entityManager.persist(book);
     
        return book;
    });
          
    _book.setTitle(
        "High-Performance Java Persistence, 2nd edition"
    );
    
    doInJPA(entityManager -> {
        Session session = entityManager.unwrap(Session.class);
     
        session.update(_book);
     
        LOGGER.info("Updating the Book entity");
    });
    
    -- Updating the Book entity
     
    UPDATE
        book
    SET
        author = 'Vlad Mihalcea',
        isbn = '978-9730228236',
        title = 'High-Performance Java Persistence, 2nd edition'
    WHERE
        id = 1
    
    @Entity(name = "Book")
    @Table(name = "book")
    @SelectBeforeUpdate
    public class Book {
     
        //Code omitted for brevity
    }
    
    Book _book = doInJPA(entityManager -> {
        Book book = new Book()
        .setIsbn("978-9730228236")
        .setTitle("High-Performance Java Persistence")
        .setAuthor("Vlad Mihalcea");
     
        Session session = entityManager.unwrap(Session.class);
        session.saveOrUpdate(book);
     
        return book;
    });
     
    _book.setTitle(
        "High-Performance Java Persistence, 2nd edition"
    );
     
    try {
        doInJPA(entityManager -> {
            Book book = entityManager.find(
                Book.class,
                _book.getId()
            );
     
            Session session = entityManager.unwrap(Session.class);
            session.saveOrUpdate(_book);
        });
    } catch (NonUniqueObjectException e) {
        LOGGER.error(
            "The Persistence Context cannot hold " +
            "two representations of the same entity",
            e
        );
    }
    
    org.hibernate.NonUniqueObjectException:
        A different object with the same identifier value was already associated with the session : [com.vladmihalcea.book.hpjp.hibernate.pc.Book#1]
        at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:651)
        at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
        at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227)
        at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92)
        at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
        at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:682)
        at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:674)