[英]What are the differences between Hibernate Session methods saveOrUpdate() and merge()?
[英]What are the differences between the different saving methods in Hibernate?
Hibernate 有一些方法,它們以一種或另一種方式獲取您的對象並將其放入數據庫。 它們之間有什么區別,何時使用哪個,為什么沒有一種智能方法知道何時使用什么?
到目前為止,我確定的方法是:
save()
update()
saveOrUpdate()
saveOrUpdateCopy()
merge()
persist()
以上是我對方法的理解。 盡管我在實踐中並沒有使用所有這些,但主要是基於API 。
saveOrUpdate根據某些檢查調用 save 或 update。 例如,如果不存在標識符,則調用 save。 否則調用更新。
save持久化一個實體。 如果不存在,將分配一個標識符。 如果有,它本質上是在進行更新。 返回生成的實體 ID。
更新嘗試使用現有標識符持久化實體。 如果不存在標識符,我相信會拋出異常。
saveOrUpdateCopy這已被棄用,不應再使用。 反而有...
合並現在這是我的知識開始動搖的地方。 這里重要的是瞬態、分離和持久實體之間的區別。 有關對象狀態的更多信息, 請查看此處。 使用保存和更新,您正在處理持久對象。 它們與會話相關聯,因此 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() ║
║ ║ ║ ║
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝
請參閱Hibernate 論壇以獲取有關 persist 和 save 之間細微差別的說明。 看起來區別在於 INSERT 語句最終執行的時間。 由於save確實返回標識符,因此無論事務的狀態如何(這通常是一件壞事)都必須立即執行 INSERT 語句。 Persist不會在當前運行的事務之外執行任何語句,只是為了分配標識符。 Save/Persist 都適用於瞬態實例,即尚未分配標識符的實例,因此不會保存在數據庫中。
更新和合並都適用於分離的實例,即在數據庫中有相應條目但當前未附加到(或由其管理)會話的實例。 它們之間的區別在於傳遞給函數的實例會發生什么。 update嘗試重新附加實例,這意味着現在不能有其他持久實體的實例附加到會話,否則會拋出異常。 但是, merge只是將所有值復制到 Session 中的持久實例(如果當前未加載,則將加載該實例)。 輸入對象沒有改變。 因此merge比update更通用,但可能會使用更多資源。
大多數時候您應該更喜歡 JPA 方法,以及批處理任務的update
。
JPA 或 Hibernate 實體可以處於以下四種狀態之一:
從一種狀態到另一種狀態的轉換是通過 EntityManager 或 Session 方法完成的。
例如,JPA EntityManager
提供以下實體狀態轉換方法。
Hibernate Session
實現了所有 JPA EntityManager
方法並提供了一些額外的實體狀態轉換方法,如save
、 saveOrUpdate
和update
。
要將實體的狀態從 Transient (New) 更改為 Managed (Persisted),我們可以使用 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
)
請注意,在將Book
實體附加到當前持久性上下文之前分配了id
。 這是必需的,因為托管實體存儲在Map
結構中,其中鍵由實體類型及其標識符構成,值是實體引用。 這就是 JPA EntityManager
和 Hibernate Session
被稱為一級緩存的原因。
調用persist
,實體僅附加到當前運行的 Persistence Context,並且可以推遲 INSERT 直到調用flush
。
唯一的例外是IDENTITY
,它立即觸發 INSERT,因為這是獲取實體標識符的唯一方法。 因此,Hibernate 無法使用IDENTITY
生成器批量插入實體。
特定於 Hibernate 的save
方法早於 JPA,並且它自 Hibernate 項目開始就可用。
save
方法觸發由DefaultSaveOrUpdateEventListener
Hibernate 事件偵聽器處理的SaveOrUpdateEvent
。 因此,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
方法調用相同。 但是,與persist
不同, save
方法返回實體標識符。
特定於 Hibernate 的update
方法旨在繞過臟檢查機制並在刷新時強制實體更新。
update
方法觸發由DefaultSaveOrUpdateEventListener
Hibernate 事件偵聽器處理的SaveOrUpdateEvent
。 因此,update
方法等效於save
和saveOrUpdate
方法。
要了解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
是在 Persistence Context 刷新期間執行的,就在提交之前,這就是為什么首先記錄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 臟檢查機制已檢測到實體未被修改。
Hibernate 特定的saveOrUpdate
方法只是save
和update
的別名。
saveOrUpdate
方法觸發由DefaultSaveOrUpdateEventListener
Hibernate 事件偵聽器處理的SaveOrUpdateEvent
。 因此,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
標識符相同,並且 Persistence Context 不能保存同一實體的兩個表示。
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 Session
繼承的merge
方法。
如果在 Persistence Context 中沒有找到實體引用, 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
方法。 要復制分離的實體狀態,應首選merge
。 update
方法僅對批處理任務有用。 save
和saveOrUpdate
只是update
別名,您可能根本不應該使用它們。
即使實體已經被管理,一些開發人員也會調用save
,但這是一個錯誤並觸發一個冗余事件,因為對於被管理的實體,更新是在 Persistence 上下文刷新時間自動處理的。
這個鏈接很好地解釋了:
http://www.stevider.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 返回對實例新更新版本的引用。 它不會將項目重新附加到會話。 如果您測試實例相等性 (item == item3),您會發現在這種情況下它返回 false。 從現在開始,您可能希望使用 item3。
同樣重要的是要注意 Java Persistence API (JPA) 沒有分離和重新附加對象的概念,而是使用 EntityManager.persist() 和 EntityManager.merge()。
我發現一般來說,在使用 Hibernate 時,saveOrUpdate() 通常足以滿足我的需要。 我通常只在對象可以引用相同類型的對象時才需要使用合並。 最近,異常的原因在於驗證引用不是遞歸的代碼中。 作為驗證的一部分,我將同一個對象加載到我的會話中,從而導致了錯誤。
你在哪里遇到過這個問題? 合並對您有用還是您需要其他解決方案? 您更喜歡始終使用合並,還是更喜歡僅在特定情況下需要時使用它
實際上,hibernate save()
和persist()
方法之間的區別取決於我們使用的生成器類。
如果我們的生成器類被賦值,那么save()
和persist(
) 方法之間沒有區別。 因為generator 'assigned' 的意思是,作為程序員,我們需要給主鍵值以保存在數據庫中的權利[希望你知道這個generators概念] 如果不是分配的generator class,假設我們的generator class name is Increment 意味着休眠它自己會將主鍵id值分配到數據庫中[除了分配的生成器,休眠僅用於記住主鍵id值],所以在這種情況下,如果我們調用save()
或persist()
方法然后它會正常地將記錄插入到數據庫中 但是聽說, save()
方法可以返回由 hibernate 生成的主鍵 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。
僅當對象在當前會話范圍之外(處於分離模式時)發生更改時,才應調用 update。
以下答案都不對。 所有這些方法看起來都一樣,但實際上做的事情完全不同。 很難給出簡短的評論。 最好提供有關這些方法的完整文檔的鏈接: http : //docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html
以上答案都不完整。 盡管 Leo Theobald 的回答看起來最接近。
基本點是 hibernate 如何處理實體的狀態以及當狀態發生變化時它如何處理它們。 關於刷新和提交的一切都必須被看到,每個人似乎都完全忽略了這一點。
切勿使用 Hibernate 的保存方法。 忘記它甚至存在於休眠狀態!
堅持
正如每個人所解釋的,Persist 基本上是將實體從“瞬態”狀態轉換為“托管”狀態。 此時,slush 或 commit 可以創建插入語句。 但實體仍將保持“受管”狀態。 這不會隨着沖洗而改變。
此時,如果您再次“堅持”,則不會有任何變化。 如果我們嘗試持久化一個持久化實體,就不會再有任何保存了。
當我們試圖驅逐實體時,樂趣就開始了。
驅逐是 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();
你什么時候這樣做? 你認為會發生什么? 如果你說這會引發異常,那么你是對的。 這將引發異常,因為合並已經處理了處於分離狀態的實體對象。 但它不會改變對象的狀態。
在幕后,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();
上述示例之所以有效,是因為合並已將一個新實體帶入處於持久狀態的上下文中。
當與更新一起應用時,同樣的工作正常,因為更新實際上並沒有帶來像合並這樣的實體副本。
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();
同時在調試跟蹤中我們可以看到更新沒有像合並一樣引發選擇的 SQL 查詢。
刪除
在上面的例子中,我使用了 delete 而沒有談到 delete。 刪除基本上會將實體從托管狀態轉換為“已刪除”狀態。 並且在刷新或提交時會發出刪除命令來存儲。
但是,可以使用persist 方法將實體從“已刪除”狀態帶回“托管”狀態。
希望以上的解釋能澄清大家的疑惑。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.