Hibernate有一些方法,这些方法以这种或那种方式获取对象并将其放入数据库中。它们之间有什么区别,什么时候使用哪个,为什么不存在一个知道何时使用什么的智能方法?
到目前为止我发现的方法是:
save()
update()
saveOrUpdate()
saveOrUpdateCopy()
merge()
persist()
答案 0 :(得分:117)
这是我对这些方法的理解。主要是基于API,但我并没有在实践中使用所有这些。
<强> saveOrUpdate 强> 根据某些检查调用保存或更新。例如。如果不存在标识符,则调用save。否则调用更新。
保存强> 坚持一个实体。如果不存在标识符,将分配标识符。如果有的话,它实质上是在进行更新。返回生成的实体ID。
<强>更新强> 尝试使用现有标识符持久保存实体。如果不存在标识符,我相信会抛出异常。
<强> saveOrUpdateCopy 强> 这已弃用,不应再使用。相反,有......
<强>合并强> 现在,这是我的知识开始动摇的地方。这里重要的是瞬态,分离和持久实体之间的区别。有关对象状态的更多信息,take a look here。随着保存&amp;更新,您正在处理持久对象。它们链接到一个Session,因此Hibernate知道发生了什么变化。但是当你有一个瞬态对象时,就没有涉及会话。在这些情况下,您需要使用merge进行更新并保留以进行保存。
<强>持续强> 如上所述,这用于瞬态对象。它不会返回生成的ID。
答案 1 :(得分:114)
╔══════════════╦═══════════════════════════════╦════════════════════════════════╗
║ 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() ║
║ ║ ║ ║
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝
答案 2 :(得分:65)
有关persist和save之间细微差别的解释,请参阅Hibernate Forum。看起来不同的是INSERT语句最终执行的时间。由于 save 确实返回了标识符,因此无论事务的状态如何(通常都是坏事),INSERT语句都必须立即执行。 持久不会执行当前正在运行的事务之外的任何语句,只是为了分配标识符。 保存/保持对瞬态实例的工作,即尚未分配标识符的实例,因此不会保存在数据库中。
更新和合并都适用于分离的实例,即在数据库中具有相应条目但当前具有相应条目的实例没有附加到(或由会话管理)。它们之间的区别是传递给函数的实例会发生什么。 更新尝试重新附加实例,这意味着现在可能没有附加到Session的持久实体的其他实例,否则抛出异常。但是, merge 只是将所有值复制到Session中的持久化实例(如果当前未加载,将加载它)。输入对象不会更改。因此,合并比更新更通用,但可能会使用更多资源。
答案 3 :(得分:12)
此链接以良好的方式解释:
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会话时,我们正在处理的持久对象被分离。这意味着数据仍然在应用程序的内存中,但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返回对实例的新更新版本的引用。它不会将项目重新附加到会话。如果你测试实例相等(item == item3),你会发现在这种情况下它返回false。从这一点开始,您可能希望使用item3。
同样重要的是要注意Java Persistence API(JPA)没有分离和重新附加对象的概念,并使用EntityManager.persist()和EntityManager.merge()。
我发现在使用Hibernate时,saveOrUpdate()通常足以满足我的需求。当我拥有可以引用相同类型对象的对象时,我通常只需要使用merge。最近,异常的原因在于代码验证引用不是递归的。作为验证的一部分,我将同一个对象加载到我的会话中,导致错误。
你在哪里遇到过这个问题?合并是否适用于您,或者您是否需要其他解决方案?您是否希望始终使用合并,或者仅在特定情况下根据需要使用合并
答案 4 :(得分:5)
实际上,hibernate save()
和persist()
方法之间的区别取决于我们使用的生成器类。
如果我们的生成器类已分配,则save()
和persist(
)方法之间没有区别。因为生成器'已分配'意味着,作为程序员,我们需要将主键值保存在数据库中[希望你知道这个生成器的概念]
如果不是指定的生成器类,假设我们的生成器类名称是递增意味着休眠它自己会将主键id值分配到数据库中[除了分配的生成器之外,hibernate仅用于注意主键id值记住],所以在这种情况下,如果我们调用save()
或persist()
方法,那么它会将记录正常插入数据库
但是听到的是,save()
方法可以返回由hibernate生成的主键id值,我们可以通过
long s = session.save(k);
在同样的情况下,persist()
永远不会向客户提供任何价值。
答案 5 :(得分:5)
我找到了一个很好的例子,显示了所有hibernate保存方法之间的差异:
http://www.journaldev.com/3481/hibernate-session-merge-vs-update-save-saveorupdate-persist-example
简而言之,根据以上链接:
保存()强>
<强>坚持()强>
<强> saveOrUpdate()方法强>
可以在有或没有事务的情况下使用,就像save()一样,如果没有事务使用它,映射的实体就不会被保存为un; ess我们刷新会话。
根据提供的数据生成插入或更新查询。如果数据存在于数据库中,则执行更新查询。
<强>更新()强>
<强>合并()强>
另外,对于所有这些的实际例子,请参考我上面提到的链接,它显示了所有这些不同方法的示例。
答案 6 :(得分:2)
请注意,如果您对分离的对象调用更新,则无论您是否更改了对象,都会在数据库中完成更新。如果它不是你想要的,你应该使用带有LockMode.None的Session.lock()。
只有当对象在当前会话范围之外更改时(处于分离模式时),才应调用update。
答案 7 :(得分:2)
正如我在this article中所解释的那样,大多数情况下,您应该首选JPA方法,而对于批处理任务,则更喜欢update
。
JPA或Hibernate实体可以处于以下四个状态之一:
从一种状态到另一种状态的转换是通过EntityManager或Session方法完成的。
例如,JPA EntityManager
提供以下实体状态转换方法。
Hibernate Session
实现了所有JPA EntityManager
方法,并提供了其他一些实体状态转换方法,例如save
,saveOrUpdate
和update
。
要将实体的状态从“瞬态(新)”更改为“托管(持久)”,我们可以使用JPA persist
提供的EntityManager
方法,该方法也由Hibernate {{1 }}。
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 generator,它会立即触发INSERT,因为这是它获取实体标识符的唯一方法。因此,Hibernate无法使用IDENTITY生成器批量插入实体。有关此主题的更多详细信息,请查看this article。
特定于Hibernate的save
方法早于JPA,自Hibernate项目开始以来便已可用。
save
方法触发SaveOrUpdateEvent
,该DefaultSaveOrUpdateEventListener
由save
Hibernate事件侦听器处理。因此,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
);
});
方法的工作原理,请考虑以下测试案例:
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
)
运行上述测试用例时,Hibernate生成以下SQL语句:
persist
如您所见,结果与persist
方法调用相同。但是,与save
不同,update
方法返回实体标识符。
有关更多详细信息,请查看this article。
特定于Hibernate的update
方法旨在绕过dirty checking mechanism并在刷新时强制实体更新。
SaveOrUpdateEvent
方法触发DefaultSaveOrUpdateEventListener
,该update
由save
Hibernate事件侦听器处理。因此,saveOrUpdate
方法等效于update
和Book
方法。
要了解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");
});
实体保留在一个事务中,然后在该实体处于分离状态时对其进行修改,并强制执行SQL UPDATE使用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
在执行上述测试用例时,Hibernate生成以下SQL语句:
Updating the Book entity
请注意,@SelectBeforeUpdate
是在持久性上下文刷新期间(即在提交之前)执行的,这就是为什么首先记录@SelectBeforeUpdate
消息的原因。
SELECT
避免不必要的更新现在,即使实体处于分离状态时也未更改,总是将执行UPDATE。为防止这种情况,可以使用loaded state
休眠注释,该注释将触发一条Book
语句,该语句提取了@SelectBeforeUpdate
,然后由脏检查机制使用。
因此,如果我们用@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);
});
实体进行注释:
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执行以下SQL语句:
saveOrUpdate
请注意,由于Hibernate脏检查机制检测到该实体未被修改,因此这次没有执行save
。
特定于Hibernate的update
方法只是saveOrUpdate
和SaveOrUpdateEvent
的别名。
DefaultSaveOrUpdateEventListener
方法触发update
,该save
由saveOrUpdate
Hibernate事件侦听器处理。因此,saveOrUpdate
方法等效于UPDATE
和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
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
);
}
,NonUniqueObjectException
和EntityManager
可能出现的一个问题是,如果持久性上下文已经包含具有与以下相同的ID和相同类型的实体引用,例如:
Book
现在,在执行上述测试用例时,Hibernate将抛出一个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
实体,该实体的标识符与我们传递给的实体相同merge
,并且持久性上下文不能包含同一实体的两种表示形式。
EntityManager
为避免使用Session
,您需要使用JPA merge
提供的merge
方法,该方法也由Hibernate merge
继承。
如this article中所述,如果在持久性上下文中未找到实体引用,则MergeEvent
从数据库中获取一个新的实体快照,并将复制的分离实体的状态复制到DefaultMergeEventListener
方法。
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事件侦听器处理。
要了解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
在子序列持久性上下文中。
merge
运行上述测试用例时,Hibernate执行以下SQL语句:
SELECT
请注意,update
返回的实体引用与我们传递给persist
方法的独立引用不同。
现在,尽管在复制分离的实体状态时应该首选使用JPA merge
,但是执行批处理任务时多余的update
可能会出现问题。
由于这个原因,当您确定当前运行的持久性上下文中没有附加任何实体引用并且分离的实体已被修改时,您应该首选使用save
。
有关此主题的更多详细信息,请查看this article。
要保留实体,应使用JPA saveOrUpdate
方法。要复制分离的实体状态,应首选update
。 save
方法仅对批处理任务有用。 {{1}}和{{1}}只是{{1}}的别名,您根本不应该使用它们。
即使开发人员已经管理了实体,有些开发人员仍会调用{{1}},但这是一个错误并触发了冗余事件,因为对于托管实体,UPDATE是在持久性上下文刷新时自动处理的。
有关更多详细信息,请查看this article。
答案 8 :(得分:1)
以下答案都不对。 所有这些方法似乎都是相似的,但在实践中做的绝对不同。 很难给出简短的评论。最好提供有关这些方法的完整文档的链接: http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html
答案 9 :(得分:0)
以上答案均未完成。尽管Leo Theobald的答案看起来是最接近的答案。
基本要点是休眠状态如何处理实体的状态以及状态发生变化时如何处理它们。一切都必须与刷新和提交有关,所有人似乎都完全忽略了这些。
切勿使用HIBERNATE的保存方法。忘了它甚至还存在!
坚持
正如每个人所解释的,Persist基本上将实体从“瞬态”状态转换为“托管”状态。此时,slush或commit可以创建一个insert语句。但是该实体仍将保持“托管”状态。同花顺不会改变。
在这一点上,如果您再次“坚持”,将没有任何改变。如果我们尝试保留一个持久化的实体,将不会再有任何节省。
当我们试图驱逐实体时,乐趣就开始了。
逐出是Hibernate的一项特殊功能,它将使实体从“托管”过渡到“独立”。我们不能对分离的实体调用持久化。如果这样做,则Hibernate会引发异常,并且整个事务都会在提交时回滚。
合并与更新
这是两个有趣的函数,它们以不同的方式处理时会产生不同的作用。他们俩都试图将实体从“已分离”状态转换为“托管”状态。但是,方法有所不同。
了解一个事实,即“分离”意味着某种“离线”状态。并且托管表示“在线”状态。
观察以下代码:
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已对处于分离状态的实体对象起作用。但这不会改变对象的状态。
在后台,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实际上不会带来合并之类的实体副本。
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并没有引发select之类的SQL查询。
删除
在上面的示例中,我使用了delete而不是谈论delete。删除基本上会将实体从托管状态转换为“已删除”状态。并且在刷新或提交后将发出删除命令来存储。
但是,可以使用persist方法将实体从“已删除”状态恢复为“托管”状态。
希望以上解释可以澄清任何疑问。