これは非常に一般的な質問であるため、
この回答に基づいてこの記事を作成しました。
エンティティの状態
JPAは以下のエンティティー状態を定義します。
新規(一時的)
Hibernate Session
(aka Persistence Context
)に関連付けられておらず、データベーステーブル行にマップされていない、新しく作成されたオブジェクトは、新規(一時的)状態と見なされます。
永続化するには、EntityManager#persist
メソッドを明示的に呼び出すか、推移的永続化メカニズムを利用する必要があります。
永続的(管理)
永続エンティティがデータベーステーブル行に関連付けられており、現在実行中の永続コンテキストによって管理されています。このようなエンティティに加えられた変更はすべて検出され、データベースに伝達されます(セッションのフラッシュ時)。
Hibernateを使用すると、INSERT / UPDATE / DELETEステートメントを実行する必要がなくなります。Hibernateはトランザクション後書き作業スタイルを採用しており、変更は現在のSession
フラッシュ時間中の最後の責任ある瞬間に同期されます。
戸建
現在実行中の永続コンテキストが閉じられると、以前に管理されていたすべてのエンティティが切り離されます。継続的な変更は追跡されなくなり、自動データベース同期は行われません。
エンティティの状態遷移
EntityManager
インターフェイスで定義されたさまざまなメソッドを使用してエンティティの状態を変更できます。
JPAエンティティの状態遷移をよりよく理解するには、次の図を検討してください。
JPAを使用する場合、分離されたエンティティをアクティブなに再度関連付けるにはEntityManager
、マージ操作を使用できます。
merge
次の図に示すように、ネイティブHibernate APIを使用する場合は、とは別に、更新メソッドを使用して、切り離されたエンティティをアクティブなHibernateセッションに再接続できます。
分離されたエンティティのマージ
マージにより、分離されたエンティティの状態(ソース)が管理対象エンティティのインスタンス(宛先)にコピーされます。
次のBook
エンティティを永続化したとします。エンティティの永続化EntityManager
に使用されたが閉じられたため、エンティティが切り離されました。
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
エンティティが分離された状態にある間、次のように変更します。
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
ここで、変更をデータベースに伝播するため、merge
メソッドを呼び出すことができます。
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
そして、Hibernateは次のSQLステートメントを実行します。
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
マージ中のエンティティに現在の同等のエンティティがない場合、EntityManager
新しいエンティティのスナップショットがデータベースからフェッチされます。
管理対象エンティティが存在すると、JPAは切り離されたエンティティの状態を現在管理されているエンティティにコピーし、永続コンテキストの実行flush
中に、管理対象エンティティが変更されたことをダーティチェックメカニズムが検出すると、UPDATEが生成されます。
そのため、を使用するmerge
と、デタッチされたオブジェクトインスタンスは、マージ操作後もデタッチされたままになります。
切り離されたエンティティの再接続
JPAはHibernateをサポートしていますが、update
メソッドによる再接続をサポートしていません。
Hibernate Session
は特定のデータベース行に1つのエンティティオブジェクトのみを関連付けることができます。これは、永続コンテキストがメモリ内キャッシュ(1次レベルキャッシュ)として機能し、1つの値(エンティティ)のみが特定のキー(エンティティタイプとデータベース識別子)に関連付けられているためです。
エンティティを再接続できるのは、現在のHibernateにすでに関連付けられている(同じデータベース行に一致する)他のJVMオブジェクトがない場合だけですSession
。
Book
エンティティを永続化し、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;
});
_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ステートメントを実行します。
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
update
この方法は、あなたを必要と休止状態に。unwrap
EntityManager
Session
とは異なりmerge
、提供された分離エンティティは現在の永続コンテキストに再関連付けされ、エンティティが変更されたかどうかに関係なく、フラッシュ中にUPDATEがスケジュールされます。
これを防ぐには、@SelectBeforeUpdate
ロードされた状態をフェッチするSELECTステートメントをトリガーするHibernateアノテーションを使用します。これは、ダーティチェックメカニズムによって使用されます。
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
NonUniqueObjectExceptionに注意してください
で発生する可能性のある問題の1つupdate
は、次の例のように、永続コンテキストに同じ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
これは、2番目に、渡したものと同じ識別子を持つエンティティがEntityManager
既に含まれており、永続コンテキストが同じエンティティの2つの表現を保持できないためです。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)
結論
merge
楽観的ロックを使用している場合は、更新の損失を防ぐことができるため、この方法をお勧めします。このトピックの詳細については、こちらの記事をご覧ください。
update
それはによって生成される、追加のSELECT文を防止できるようバッチ更新のために良いですmerge
ので、バッチ更新の実行時間を短縮、操作を。
refresh()
分離エンティティで許可されていない理由があるのでしょうか。2.0仕様を調べても、正当な理由はありません。許可されていないというだけです。