Hibernate有一些方法,它们以某种方式获取对象并将其放入数据库中。它们之间的区别是什么,什么时候使用哪个,为什么没有一种智能方法知道什么时候使用什么?

到目前为止,我已经确定的方法是:

save () (更新) saveOrUpdate () saveOrUpdateCopy () 合并() persist ()


当前回答

以下答案没有一个是正确的。 所有这些方法看似相似,但实际上做的事情完全不同。 很难给出简短的评论。最好提供关于这些方法的完整文档链接: http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html

其他回答

以下答案没有一个是正确的。 所有这些方法看似相似,但实际上做的事情完全不同。 很难给出简短的评论。最好提供关于这些方法的完整文档链接: http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html

这个链接很好地解释了:

http://www.stevideter.com/2008/12/07/saveorupdate-versus-merge-in-hibernate/

我们都有一些偶尔遇到的问题,当我们再次看到它们时,我们知道我们已经解决了这个问题,但不记得是如何解决的。

在Hibernate中使用Session.saveOrUpdate()时抛出的NonUniqueObjectException就是我的一个。我将为一个复杂的应用程序添加新功能。我所有的单元测试都运行正常。然后在测试UI时,试图保存一个对象,我开始得到一个异常,消息是“具有相同标识符值的不同对象已经与会话关联”。下面是一些来自Java Persistence with Hibernate的示例代码。

            Session session = sessionFactory1.openSession();
            Transaction tx = session.beginTransaction();
            Item item = (Item) session.get(Item.class, new Long(1234));
            tx.commit();
            session.close(); // end of first session, item is detached

            item.getId(); // The database identity is "1234"
            item.setDescription("my new description");
            Session session2 = sessionFactory.openSession();
            Transaction tx2 = session2.beginTransaction();
            Item item2 = (Item) session2.get(Item.class, new Long(1234));
            session2.update(item); // Throws NonUniqueObjectException
            tx2.commit();
            session2.close();

要理解此异常的原因,重要的是要理解分离对象以及在分离对象上调用saveOrUpdate()(或仅仅是update())时会发生什么。

当我们关闭一个单独的Hibernate Session时,我们正在使用的持久对象被分离。这意味着数据仍然在应用程序的内存中,但Hibernate不再负责跟踪对象的更改。

如果我们随后修改分离对象并想要更新它,则必须重新附加该对象。在重新连接过程中,Hibernate将检查是否有相同对象的其他副本。如果它找到了,它必须告诉我们它不再知道“真正的”副本是什么了。也许对我们期望保存的其他副本做了其他更改,但Hibernate不知道这些更改,因为它当时没有管理它们。

Hibernate不是保存可能的坏数据,而是通过NonUniqueObjectException告诉我们问题。

那么我们该怎么办呢?在Hibernate 3中,我们有merge()(在Hibernate 2中,使用saveOrUpdateCopy())。此方法将强制Hibernate将其他分离实例中的任何更改复制到您想要保存的实例上,从而在保存之前将所有更改合并到内存中。

        Session session = sessionFactory1.openSession();
        Transaction tx = session.beginTransaction();
        Item item = (Item) session.get(Item.class, new Long(1234));
        tx.commit();
        session.close(); // end of first session, item is detached

        item.getId(); // The database identity is "1234"
        item.setDescription("my new description");
        Session session2 = sessionFactory.openSession();
        Transaction tx2 = session2.beginTransaction();
        Item item2 = (Item) session2.get(Item.class, new Long(1234));
        Item item3 = session2.merge(item); // Success!
        tx2.commit();
        session2.close();

需要注意的是,merge返回对实例最新更新版本的引用。它不是将项重新附加到Session。如果您测试实例是否相等(item == item3),您将发现在这种情况下它返回false。从现在开始,您可能希望使用item3。

还需要注意的是,Java Persistence API (JPA)没有分离和重新连接对象的概念,而是使用EntityManager.persist()和EntityManager.merge()。

我发现在使用Hibernate时,saveOrUpdate()通常足以满足我的需要。通常只有当我的对象可以引用相同类型的对象时,我才需要使用merge。最近,异常的原因是在验证引用不是递归的代码中。作为验证的一部分,我将相同的对象加载到会话中,导致了错误。

您在哪里遇到过这个问题?合并对你有用吗?还是你需要另一种解决方案?你喜欢总是使用归并,还是喜欢只在特定情况下使用它

在大多数情况下,您应该更喜欢JPA方法,而更新用于批处理任务。

JPA或Hibernate实体可以处于以下四种状态之一:

瞬态(新) 管理(持续) 分离 删除(删除)

从一个状态到另一个状态的转换是通过EntityManager或Session方法完成的。

例如,JPA EntityManager提供了以下实体状态转换方法。

Hibernate会话实现了所有JPA EntityManager方法,并提供了一些附加的实体状态转换方法,如save、saveOrUpdate和update。

坚持

要将一个实体的状态从Transient (New)更改为Managed (persistent),我们可以使用JPA EntityManager提供的持久化方法,该方法也由Hibernate会话继承。

持久化方法触发了一个PersistEvent,该事件由Hibernate事件监听器DefaultPersistEventListener处理。

因此,当执行以下测试用例时:

doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);
    
    LOGGER.info(
        "Persisting the Book entity with the id: {}", 
        book.getId()
    );
});

Hibernate生成以下SQL语句:

CALL NEXT VALUE FOR hibernate_sequence

-- Persisting the Book entity with the id: 1

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

请注意,id是在将Book实体附加到当前持久性上下文之前分配的。这是必需的,因为托管实体存储在Map结构中,其中键由实体类型及其标识符组成,值是实体引用。这就是为什么JPA实体管理器和Hibernate会话被称为一级缓存的原因。

调用persist时,实体只附加到当前运行的Persistence Context, INSERT可以推迟到调用flush为止。

唯一的例外是IDENTITY,它会立即触发INSERT,因为这是它获得实体标识符的唯一方法。因此,Hibernate不能使用IDENTITY生成器批量插入实体。

Save

特定于Hibernate的保存方法比JPA更早,并且在Hibernate项目开始时就可用了。

保存方法触发一个SaveOrUpdateEvent事件,该事件由DefaultSaveOrUpdateEventListener Hibernate事件监听器处理。因此,save方法等价于update和saveOrUpdate方法。

要了解save方法是如何工作的,请考虑以下测试用例:

doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);

    Long id = (Long) session.save(book);

    LOGGER.info(
        "Saving the Book entity with the id: {}", 
        id
    );
});

当运行上面的测试用例时,Hibernate生成以下SQL语句:

CALL NEXT VALUE FOR hibernate_sequence

-- Saving the Book entity with the id: 1

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

如您所见,结果与持久化方法调用相同。但是,与persist方法不同,save方法返回实体标识符。

更新

特定于hibernate的更新方法旨在绕过脏检查机制,并强制在刷新时更新实体。

更新方法触发一个SaveOrUpdateEvent事件,该事件由DefaultSaveOrUpdateEventListener Hibernate事件监听器处理。因此,update方法等价于save和saveOrUpdate方法。

要了解更新方法是如何工作的,请考虑以下示例,该示例将Book实体持久化在一个事务中,然后在实体处于分离状态时修改它,并使用更新方法调用强制执行SQL update。

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

LOGGER.info("Modifying the Book entity");

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

当执行上面的测试用例时,Hibernate生成以下SQL语句:

CALL NEXT VALUE FOR hibernate_sequence

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

-- Modifying 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

注意,UPDATE是在持久化上下文刷新期间执行的,就在提交之前,这就是为什么首先记录UPDATE the Book实体消息的原因。

使用@SelectBeforeUpdate避免不必要的更新

现在,即使实体在分离状态下没有更改,也将始终执行UPDATE。为了防止这种情况,您可以使用@SelectBeforeUpdate Hibernate注释,它将触发一个SELECT语句,获取加载状态,然后由脏检查机制使用。

因此,如果我们用@SelectBeforeUpdate注释Book实体:

@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");

    entityManager.persist(book);

    return book;
});

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);

    session.update(_book);
});

Hibernate执行以下SQL语句:

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

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

注意,这一次没有执行UPDATE,因为Hibernate脏检查机制检测到实体没有被修改。

SaveOrUpdate

特定于hibernate的saveOrUpdate方法只是保存和更新的别名。

saveOrUpdate方法触发一个SaveOrUpdateEvent事件,该事件由DefaultSaveOrUpdateEventListener Hibernate事件监听器处理。因此,update方法等价于save和saveOrUpdate方法。

现在,当您想要持久化一个实体或强制执行一个UPDATE时,您可以使用saveOrUpdate,如下例所示。

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

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(_book);
});

注意NonUniqueObjectException

使用save、update和saveOrUpdate可能会出现的一个问题是,如果持久性上下文已经包含了与下面示例中相同id和相同类型的实体引用:

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

现在,当执行上面的测试用例时,Hibernate将抛出一个NonUniqueObjectException,因为第二个EntityManager已经包含了一个Book实体,其标识符与我们传递给update的标识符相同,而持久性上下文不能容纳同一实体的两个表示。

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)

为了避免NonUniqueObjectException,您需要使用JPA EntityManager提供的合并方法,并由Hibernate会话继承。

如果在Persistence Context中没有找到实体引用,那么merge将从数据库中获取一个新的实体快照,并复制传递给merge方法的分离实体的状态。

merge方法触发一个MergeEvent,由DefaultMergeEventListener Hibernate事件监听器处理。

要了解merge方法是如何工作的,请考虑以下示例,该示例在一个事务中持久化Book实体,然后在实体处于分离状态时修改它,并将分离实体传递到子序列持久化上下文中进行合并。

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

LOGGER.info("Modifying the Book entity");

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

doInJPA(entityManager -> {
    Book book = entityManager.merge(_book);

    LOGGER.info("Merging the Book entity");

    assertFalse(book == _book);
});

当运行上面的测试用例时,Hibernate执行以下SQL语句:

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

-- Modifying the Book entity

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

注意,merge返回的实体引用与传递给merge方法的分离实体引用不同。

现在,尽管您在复制分离实体状态时更喜欢使用JPA合并,但在执行批处理任务时,额外的SELECT可能会产生问题。

出于这个原因,当您确定没有实体引用已经附加到当前运行的持久性上下文,并且分离的实体已经被修改时,您应该更喜欢使用update。

结论

要持久化一个实体,您应该使用JPA持久化方法。要复制分离的实体状态,应优先使用merge。更新方法仅对批处理任务有用。save和saveOrUpdate只是用于更新的别名,您可能根本不应该使用它们。

有些开发人员甚至在实体已经被管理时调用save,但这是一个错误,并且会触发冗余事件,因为对于托管实体,UPDATE会在Persistence上下文刷新时自动处理。

我找到了一个很好的例子来说明所有hibernate保存方法之间的差异:

http://www.journaldev.com/3481/hibernate-session-merge-vs-update-save-saveorupdate-persist-example

总之,根据以上链接:

save ()

我们可以在事务外部调用此方法。如果我们在没有事务的情况下使用它,并且在实体之间有级联,那么只有主实体会被保存,除非我们刷新会话。 因此,如果主对象映射了其他对象,则在提交事务或刷新会话时保存它们。

persist ()

它类似于在事务中使用save(),所以它是安全的,并照顾任何级联对象。

saveOrUpdate ()

可以与事务一起使用,也可以不使用事务,就像save()一样,如果它在没有事务的情况下使用,映射的实体将不会被保存,除非我们刷新会话。 根据所提供的数据将结果插入或更新查询。如果数据存在于数据库中,则执行更新查询。

update ()

当我们知道我们只是在更新实体信息时,应该使用Hibernate更新。该操作将实体对象添加到持久上下文,并在事务提交时跟踪和保存进一步的更改。 因此,即使在调用update之后,如果我们在实体中设置了任何值,它们将在事务提交时被更新。

go()

Hibernate merge可用于更新现有值,但此方法从传递的实体对象创建一个副本并返回它。返回的对象是持久上下文的一部分,并跟踪任何更改,传递的对象不跟踪。这是merge()与其他方法的主要区别。

对于所有这些方法的实际示例,请参考我上面提到的链接,它展示了所有这些不同方法的示例。

以下是我对这些方法的理解。这些主要是基于API的,尽管我在实践中没有使用所有这些。

saveOrUpdate 根据某些检查调用save或update。例如,如果不存在标识符,则调用save。否则调用update。

保存 持久化一个实体。如果标识符不存在,将分配标识符。如果有,它本质上是在进行更新。返回生成的实体ID。

更新 尝试使用现有标识符持久化实体。如果不存在标识符,则抛出异常。

saveOrUpdateCopy 这已弃用,不应再使用。取而代之的是……

合并 现在我的知识开始动摇了。这里重要的是瞬态实体、分离实体和持久实体之间的区别。有关对象状态的更多信息,请查看这里。使用save & update,你处理的是持久对象。它们被链接到一个会话,因此Hibernate知道发生了什么变化。但是当你有一个瞬态对象时,就不涉及会话了。在这些情况下,您需要使用合并进行更新,使用持久化进行保存。

坚持 如上所述,这用于瞬态对象。它不返回生成的ID。