Hibernate 有一些方法,它们以一种或另一种方式获取您的对象并将其放入数据库。它们之间有什么区别,什么时候使用哪个,为什么不只有一种智能方法知道什么时候使用什么?
到目前为止,我已经确定的方法是:
save()
update()
saveOrUpdate()
saveOrUpdateCopy()
merge()
persist()
Hibernate 有一些方法,它们以一种或另一种方式获取您的对象并将其放入数据库。它们之间有什么区别,什么时候使用哪个,为什么不只有一种智能方法知道什么时候使用什么?
到目前为止,我已经确定的方法是:
save()
update()
saveOrUpdate()
saveOrUpdateCopy()
merge()
persist()
这是我对这些方法的理解。这些主要是基于API,但我在实践中并没有使用所有这些。
saveOrUpdate 根据某些检查调用保存或更新。例如,如果不存在标识符,则调用 save。否则调用更新。
保存 实体。如果不存在,将分配一个标识符。如果是这样,它本质上是在进行更新。返回生成的实体 ID。
update 尝试使用现有标识符来持久化实体。如果不存在标识符,我相信会引发异常。
saveOrUpdateCopy 这已被弃用,不应再使用。取而代之的是...
合并 现在这是我的知识开始动摇的地方。这里重要的是瞬态、分离和持久实体之间的区别。有关对象状态的更多信息,请查看此处。使用保存和更新,您正在处理持久对象。它们与 Session 相关联,因此 Hibernate 知道发生了什么变化。但是当你有一个瞬态对象时,就不会涉及会话。在这些情况下,您需要使用合并进行更新并持久保存。
persist 如上所述,这用于瞬态对象。它不返回生成的 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() ║
║ ║ ║ ║
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝
请参阅Hibernate 论坛以了解持久和保存之间的细微差别。看起来差异在于最终执行 INSERT 语句的时间。由于save确实返回了标识符,因此无论事务的状态如何,都必须立即执行 INSERT 语句(这通常是一件坏事)。Persist不会仅仅为了分配标识符而在当前运行的事务之外执行任何语句。Save/Persist 都适用于瞬态实例,即尚未分配标识符且因此未保存在数据库中的实例。
更新和合并都适用于分离的实例,即在数据库中有相应条目但当前未附加到会话(或由会话管理)的实例。它们之间的区别在于传递给函数的实例会发生什么。update尝试重新附加实例,这意味着现在不能有其他持久实体实例附加到 Session,否则将引发异常。然而, merge只是将所有值复制到 Session 中的持久实例(如果当前未加载,则会加载该实例)。输入对象没有改变。所以合并比更新更通用,但可能会使用更多资源。
大多数时候,您应该更喜欢 JPA 方法,以及update
批处理任务。
JPA 或 Hibernate 实体可以处于以下四种状态之一:
从一种状态到另一种状态的转换是通过 EntityManager 或 Session 方法完成的。
例如,JPAEntityManager
提供了以下实体状态转换方法。
HibernateSession
实现了所有的 JPAEntityManager
方法并提供了一些额外的实体状态转换方法,如save
、saveOrUpdate
和update
.
要将实体的状态从 Transient (New) 更改为 Managed (Persisted),我们可以使用persist
JPA 提供的方法,该方法EntityManager
也由 Hibernate 继承Session
。
该
persist
方法触发PersistEvent
由DefaultPersistEventListener
Hibernate 事件侦听器处理的 a。
因此,在执行以下测试用例时:
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
结构中,其中键由实体类型及其标识符组成,值是实体引用。这就是为什么 JPAEntityManager
和 HibernateSession
被称为一级缓存的原因。
调用persist
时,实体只附加到当前运行的持久化上下文中,INSERT 可以推迟到flush
被调用。
唯一的例外是IDENTITY
立即触发 INSERT,因为这是获取实体标识符的唯一方法。出于这个原因,Hibernate 不能使用IDENTITY
生成器为实体批量插入。
特定于 Hibernate 的save
方法早于 JPA,并且自 Hibernate 项目开始时就可用。
该
save
方法触发SaveOrUpdateEvent
由DefaultSaveOrUpdateEventListener
Hibernate 事件侦听器处理的 a。因此,save
方法等价于update
andsaveOrUpdate
方法。
要查看该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 事件侦听器处理的 a。因此,update
方法等价于save
andsaveOrUpdate
方法。
要了解该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
避免不必要的更新现在,即使实体在分离状态下没有更改,也会始终执行更新。为了防止这种情况,您可以使用@SelectBeforeUpdate
Hibernate 注释,该注释将触发SELECT
获取的语句,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);
});
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 脏检查机制检测到实体未被修改,因此没有执行任何操作。
特定于 Hibernate 的saveOrUpdate
方法只是save
and的别名update
。
该
saveOrUpdate
方法触发SaveOrUpdateEvent
由DefaultSaveOrUpdateEventListener
Hibernate 事件侦听器处理的 a。因此,update
方法等价于save
andsaveOrUpdate
方法。
现在,您可以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
如果持久性上下文已经包含具有相同 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 将抛出 aNonUniqueObjectException
因为第二个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
,您需要使用merge
JPA 提供并由EntityManager
Hibernate 继承的方法Session
。
如果在持久性上下文中没有找到实体引用,则从数据库中merge
获取新的实体快照,并将分离实体的状态复制到merge
方法中。
该
merge
方法触发MergeEvent
由DefaultMergeEventListener
Hibernate 事件侦听器处理的 a。
要了解该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
merge
现在,虽然在复制分离的实体状态时您应该更喜欢使用 JPA,但SELECT
在执行批处理任务时,额外的可能会出现问题。
出于这个原因,update
当您确定没有实体引用已经附加到当前运行的持久性上下文并且分离的实体已被修改时,您应该更喜欢使用。
要持久化一个实体,您应该使用 JPApersist
方法。要复制分离的实体状态,merge
应该是首选。该update
方法仅对批处理任务有用。save
andsaveOrUpdate
只是别名,update
你根本不应该使用它们。
一些开发人员save
甚至在实体已经被托管时调用,但这是一个错误并触发冗余事件,因为对于托管实体,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();
请务必注意,合并返回对实例新更新版本的引用。它不会将项目重新附加到会话。如果您测试实例相等性(item == item3),您会发现它在这种情况下返回 false。从现在开始,您可能希望使用 item3。
还需要注意的是,Java Persistence API (JPA) 没有分离和重新附加对象的概念,而是使用 EntityManager.persist() 和 EntityManager.merge()。
一般来说,我发现在使用 Hibernate 时,saveOrUpdate() 通常足以满足我的需要。当我有可以引用相同类型对象的对象时,我通常只需要使用合并。最近,异常的原因是在验证引用不是递归的代码中。作为验证的一部分,我将相同的对象加载到我的会话中,导致错误。
你在哪里遇到过这个问题?合并对您有用还是需要其他解决方案?您更喜欢始终使用合并,还是更喜欢仅在特定情况下需要使用它
save()
实际上,休眠和方法之间的区别persist()
取决于我们使用的生成器类。
save()
如果我们的生成器类被分配,那么和persist(
) 方法之间没有区别。因为生成器'assigned'的意思是,作为一个程序员,我们需要给主键值以保存在数据库中[希望你知道这个生成器的概念]如果不是分配的生成器类,假设我们的生成器类名称是增量意味着hibernate 它自己会将主键 id 值分配到数据库中[除了分配的生成器,hibernate 仅用于处理主键 id 值记住],所以在这种情况下,如果我们调用save()
orpersist()
方法,那么它会将记录插入数据库正常但听到的是, save()
方法可以返回由休眠生成的主键 id 值,我们可以通过
long s = session.save(k);
在同样的情况下,persist()
永远不会将任何价值回馈给客户。
我找到了一个很好的例子,展示了所有休眠保存方法之间的差异:
http://www.journaldev.com/3481/hibernate-session-merge-vs-update-save-saveorupdate-persist-example
简而言之,根据上面的链接:
节省()
坚持()
保存或更新()
可以在有或没有事务的情况下使用,就像 save() 一样,如果它在没有事务的情况下使用,映射的实体将不会被保存,除非我们刷新会话。
根据提供的数据生成插入或更新查询。如果数据存在于数据库中,则执行更新查询。
更新()
合并()
对于所有这些的实际示例,请参阅我上面提到的链接,它显示了所有这些不同方法的示例。
请注意,如果您对分离的对象调用更新,无论您是否更改了对象,数据库中总会有更新完成。如果这不是您想要的,您应该使用 Session.lock() 和 LockMode.None。
仅当对象在当前会话范围之外(处于分离模式时)更改时,才应调用更新。
以下答案均不正确。所有这些方法看起来都很相似,但实际上做的事情却完全不同。很难给出简短的评论。最好提供有关这些方法的完整文档的链接:http: //docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html
以上答案都不完整。尽管 Leo Theobald 的答案看起来最接近。
基本点是hibernate如何处理实体的状态以及当状态发生变化时它如何处理它们。还必须查看有关刷新和提交的所有内容,每个人似乎都完全忽略了它们。
永远不要使用休眠的保存方法。忘记它甚至存在于 HIBERNATE 中!
坚持
正如大家所解释的,Persist 基本上将实体从“瞬态”状态转换为“托管”状态。此时,一个 slush 或一个提交可以创建一个插入语句。但实体仍将保持“托管”状态。这不会随着冲洗而改变。
此时,如果再次“坚持”,则不会有任何变化。如果我们尝试持久化一个持久化实体,将不会有更多的保存。
当我们试图驱逐实体时,乐趣就开始了。
驱逐是 Hibernate 的一个特殊功能,它将实体从“托管”转换为“分离”。我们不能在分离的实体上调用持久化。如果我们这样做,那么 Hibernate 会引发异常,并且整个事务会在提交时回滚。
合并与更新
这是 2 个有趣的函数,它们在以不同的方式处理时会做不同的事情。他们俩都试图将实体从“分离”状态转换为“托管”状态。但是做的不一样。
了解一个事实,即分离意味着一种“离线”状态。和管理意味着“在线”状态。
观察下面的代码:
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();
你什么时候做这个?你认为会发生什么?如果您说这会引发异常,那么您是正确的。这将引发异常,因为合并已对处于分离状态的实体对象起作用。但它不会改变对象的状态。
在幕后,合并将引发一个选择查询,并基本上返回一个处于附加状态的实体副本。观察下面的代码:
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();
上面的示例有效,因为合并将一个新实体带入了处于持久状态的上下文中。
当与更新一起应用时,同样可以正常工作,因为更新实际上并没有带来像合并这样的实体副本。
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 like merge 的 SQL 查询。
删除
在上面的例子中,我使用了 delete 而没有谈到 delete。删除基本上会将实体从托管状态转换为“已删除”状态。并且在刷新或提交时会发出删除命令来存储。
但是,可以使用 persist 方法将实体从“已删除”状态带回“托管”状态。
希望以上解释能澄清任何疑问。