Hibernate有一些方法,它们以某种方式获取对象并将其放入数据库中。它们之间的区别是什么,什么时候使用哪个,为什么没有一种智能方法知道什么时候使用什么?
到目前为止,我已经确定的方法是:
save () (更新) saveOrUpdate () saveOrUpdateCopy () 合并() persist ()
Hibernate有一些方法,它们以某种方式获取对象并将其放入数据库中。它们之间的区别是什么,什么时候使用哪个,为什么没有一种智能方法知道什么时候使用什么?
到目前为止,我已经确定的方法是:
save () (更新) saveOrUpdate () saveOrUpdateCopy () 合并() persist ()
当前回答
在大多数情况下,您应该更喜欢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上下文刷新时自动处理。
其他回答
以下答案没有一个是正确的。 所有这些方法看似相似,但实际上做的事情完全不同。 很难给出简短的评论。最好提供关于这些方法的完整文档链接: http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html
以下是我对这些方法的理解。这些主要是基于API的,尽管我在实践中没有使用所有这些。
saveOrUpdate 根据某些检查调用save或update。例如,如果不存在标识符,则调用save。否则调用update。
保存 持久化一个实体。如果标识符不存在,将分配标识符。如果有,它本质上是在进行更新。返回生成的实体ID。
更新 尝试使用现有标识符持久化实体。如果不存在标识符,则抛出异常。
saveOrUpdateCopy 这已弃用,不应再使用。取而代之的是……
合并 现在我的知识开始动摇了。这里重要的是瞬态实体、分离实体和持久实体之间的区别。有关对象状态的更多信息,请查看这里。使用save & update,你处理的是持久对象。它们被链接到一个会话,因此Hibernate知道发生了什么变化。但是当你有一个瞬态对象时,就不涉及会话了。在这些情况下,您需要使用合并进行更新,使用持久化进行保存。
坚持 如上所述,这用于瞬态对象。它不返回生成的ID。
╔══════════════╦═══════════════════════════════╦════════════════════════════════╗
║ 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() ║
║ ║ ║ ║
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝
这个链接很好地解释了:
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。最近,异常的原因是在验证引用不是递归的代码中。作为验证的一部分,我将相同的对象加载到会话中,导致了错误。
您在哪里遇到过这个问题?合并对你有用吗?还是你需要另一种解决方案?你喜欢总是使用归并,还是喜欢只在特定情况下使用它
我找到了一个很好的例子来说明所有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()与其他方法的主要区别。
对于所有这些方法的实际示例,请参考我上面提到的链接,它展示了所有这些不同方法的示例。