Hibernate中不同的保存方法之间有什么区别?

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

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

  • save()
  • update()
  • saveOrUpdate()
  • saveOrUpdateCopy()
  • merge()
  • persist()
121922 次浏览

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

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

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

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

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

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

< >强劲持续 如上所述,这用于瞬态对象。不返回生成的ID

  • 参见Hibernate论坛中关于persist和save之间细微差别的解释。看起来区别在于INSERT语句最终执行的时间。由于保存确实返回标识符,INSERT语句必须立即执行,而不管事务的状态如何(这通常是一件坏事)。坚持不会在当前运行的事务之外执行任何语句,只是为了分配标识符。 保存/持久化都适用于瞬态实例,即尚未分配标识符的实例,因此不会保存在DB中

  • 更新合并都适用于分离实例,即在DB中有相应条目但当前没有附加到会话(或由会话管理)的实例。它们之间的区别是传递给函数的实例发生了什么。update尝试重新连接实例,这意味着现在必须没有其他持久实体的实例连接到Session,否则会抛出异常。然而,merge只是将所有值复制到Session中的持久实例(如果当前未加载则将加载该实例)。输入对象没有改变。所以mergeupdate更通用,但可能会占用更多资源。

请注意,如果对已分离对象调用更新,无论是否更改对象,数据库中总是会完成更新。如果不是你想要的,你应该使用Session.lock()和LockMode.None。

只有当对象在当前会话范围之外被更改时(当处于分离模式时),才应该调用update。

实际上,hibernate save()persist()方法之间的区别取决于我们使用的生成器类。

如果生成器类被赋值,则save()persist()方法没有区别。因为生成器' assigned '意味着,作为程序员,我们需要给出主键值以保存在数据库中[希望您了解生成器的概念] 在非赋值生成器类的情况下,假设我们的生成器类名是Increment,这意味着hibernate自己会将主键id值赋值到数据库中[除了赋值生成器,hibernate只用于照顾主键id值,记住],所以在这种情况下,如果我们调用save()persist()方法,那么它将正常地将记录插入数据库中 但我听到的是,save()方法可以返回由hibernate生成的主键id值,我们可以通过

看到它
long s = session.save(k);

在同样的情况下,persist()将永远不会返回任何值给客户端。

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

这个链接很好地解释了:

http://www.stevideter.com/2008/12/07/saveorupdate-versus-merge-in-hibernate/ < a href = " 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。最近,异常的原因是在验证引用不是递归的代码中。作为验证的一部分,我将相同的对象加载到会话中,导致了错误。

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

╔══════════════╦═══════════════════════════════╦════════════════════════════════╗
║    METHOD    ║            TRANSIENT          ║            DETACHED            ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║              ║       sets id if doesn't      ║   sets new id even if object   ║
║    save()    ║     exist, persists to db,    ║    already has it, persists    ║
║              ║    returns attached object    ║ to DB, returns attached object ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║              ║       sets id on object       ║             throws             ║
║   persist()  ║     persists object to DB     ║       PersistenceException     ║
║              ║                               ║                                ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║              ║                               ║                                ║
║   update()   ║           Exception           ║     persists and reattaches    ║
║              ║                               ║                                ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║              ║  copy the state of object in  ║    copy the state of obj in    ║
║    merge()   ║     DB, doesn't attach it,    ║      DB, doesn't attach it,    ║
║              ║    returns attached object    ║     returns attached object    ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║              ║                               ║                                ║
║saveOrUpdate()║           as save()           ║            as update()         ║
║              ║                               ║                                ║
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝

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

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

总之,根据以上链接:

save ()

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

persist ()

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

saveOrUpdate ()

  • 可以与事务一起使用,也可以不使用事务,就像save()一样,如果它在没有事务的情况下使用,映射的实体将不会被保存,除非我们刷新会话。

  • 根据所提供的数据将结果插入或更新查询。如果数据存在于数据库中,则执行更新查询。

update ()

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

合并()

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

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

上面的答案都不完整。虽然Leo Theobald的答案看起来很接近。

基本要点是hibernate如何处理实体的状态,以及当状态发生变化时如何处理它们。所有的事情都必须考虑到刷新和提交,而每个人似乎都完全忽略了这一点。

不要使用HIBERNATE的保存方法。忘记它甚至存在于冬眠!

坚持

正如大家所解释的,持久化基本上是将一个实体从“瞬态”状态转换为“托管”状态。此时,slush或commit可以创建插入语句。但实体仍将保持“托管”状态。它不随同花顺而改变。

在这一点上,如果你再次“坚持”,将不会有任何改变。如果我们试图持久化一个持久化实体,就不会有更多的保存。

当我们试图驱逐实体时,乐趣就开始了。

驱逐是Hibernate的一个特殊功能,它将实体从“托管”转换为“分离”。不能在分离实体上调用持久化。如果我们这样做,那么Hibernate将引发一个异常,整个事务将在提交时回滚。

合并与更新

这是两个有趣的函数,用不同的方法处理会产生不同的效果。它们都试图将实体从“分离”状态转换为“托管”状态。但是用不同的方式。

理解一个事实,即Detached意味着一种“脱机”状态。managed表示“在线”状态。

观察下面的代码:

Session ses1 = sessionFactory.openSession();


Transaction tx1 = ses1.beginTransaction();


HibEntity entity = getHibEntity();


ses1.persist(entity);
ses1.evict(entity);


ses1.merge(entity);


ses1.delete(entity);


tx1.commit();

当你这样做?你认为会发生什么? 如果你说这会引起异常,那么你是正确的。这将引发异常,因为合并已经对实体对象进行了操作,该实体对象处于分离状态。但它不会改变对象的状态。

在后台,merge将引发一个选择查询,并返回一个处于附加状态的实体副本。观察下面的代码:

Session ses1 = sessionFactory.openSession();


Transaction tx1 = ses1.beginTransaction();
HibEntity entity = getHibEntity();


ses1.persist(entity);
ses1.evict(entity);


HibEntity copied = (HibEntity)ses1.merge(entity);
ses1.delete(copied);


tx1.commit();

上面的示例之所以有效,是因为merge将一个新的实体带入了处于持久状态的上下文中。

当与Update一起应用时,同样的工作很好,因为Update实际上不像merge那样带来实体的副本。

Session ses1 = sessionFactory.openSession();


Transaction tx1 = ses1.beginTransaction();


HibEntity entity = getHibEntity();


ses1.persist(entity);
ses1.evict(entity);


ses1.update(entity);


ses1.delete(entity);


tx1.commit();

同时在调试跟踪中,我们可以看到Update并没有引发像merge这样的select SQL查询。

删除

在上面的例子中,我使用了delete而没有谈论delete。Delete基本上会将实体从托管状态转换为“已删除”状态。当刷新或提交时,将发出删除命令来存储。

然而,使用persist方法可以将实体从“已删除”状态带回“已管理”状态。

希望以上解释澄清了您的疑问。

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

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

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

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

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

JPA实体状态转换

Hibernate Session实现了所有JPA EntityManager方法,并提供了一些额外的实体状态转换方法,如savesaveOrUpdateupdate

Hibernate实体状态转换

坚持

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

persist方法触发一个PersistEvent,由DefaultPersistEventListener Hibernate事件监听器处理。

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

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 EntityManager和Hibernate Session被称为一级缓存的原因。

当调用persist时,实体只附加到当前运行的持久性上下文,INSERT可以推迟到调用flush为止。

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

保存

特定于Hibernate的save方法早于JPA,它在Hibernate项目开始时就可用了。

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

要了解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方法调用相同。然而,与persist不同,save方法返回实体标识符。

更新

特定于hibernate的update方法意味着绕过脏检机制并强制在刷新时更新实体。

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

要了解update方法如何工作,请考虑以下示例,该示例在一个事务中持久化Book实体,然后在实体处于分离状态时修改它,并使用update方法调用强制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是在持久化上下文刷新期间执行的,就在提交之前,这就是Updating the Book entity消息首先被记录的原因。

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

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

因此,如果我们用@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方法只是saveupdate的别名。

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

现在,当你想要持久化一个实体或强制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

saveupdatesaveOrUpdate可能会出现的一个问题是,如果持久性上下文已经包含了与下面示例中相同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提供的merge方法,并由Hibernate Session继承。

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

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

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

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 merge,但在执行批处理任务时,额外的SELECT可能会有问题。

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

结论

为了持久化一个实体,你应该使用JPA persist方法。要复制分离的实体状态,应该优先使用mergeupdate方法仅对批处理任务有用。savesaveOrUpdate只是update的别名,你可能根本不应该使用它们。

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