Ponieważ jest to bardzo częste pytanie, napisałem
ten artykuł , na którym opiera się ta odpowiedź.
Stany podmiotu
WZP definiuje następujące stany encji:
Nowy (przejściowy)
Nowo utworzony obiekt, który nigdy nie był powiązany z Hibernacją Session
(alias Persistence Context
) i nie jest odwzorowany na żaden wiersz tabeli bazy danych, jest uważany za będący w stanie Nowy (przejściowy).
Aby się utrwalić, musimy albo jawnie wywołać tę EntityManager#persist
metodę, albo skorzystać z mechanizmu trwałości przechodniego.
Trwały (zarządzany)
Trwała jednostka została powiązana z wierszem tabeli bazy danych i jest zarządzana przez aktualnie działający kontekst Persistence. Wszelkie zmiany dokonane w takiej jednostce zostaną wykryte i propagowane do bazy danych (podczas czasu opróżniania sesji).
Dzięki Hibernacji nie musimy już wykonywać instrukcji INSERT / UPDATE / DELETE. Hibernacja stosuje transakcyjny styl pracy z zapisem, a zmiany są synchronizowane w ostatniej odpowiedzialnej chwili, podczas bieżącego Session
czasu spłukiwania.
Oderwany
Po zamknięciu aktualnie działającego kontekstu trwałości wszystkie wcześniej zarządzane jednostki zostają odłączone. Kolejne zmiany nie będą już śledzone i nie nastąpi automatyczna synchronizacja bazy danych.
Przejścia stanu encji
Możesz zmienić stan encji przy użyciu różnych metod zdefiniowanych przez EntityManager
interfejs.
Aby lepiej zrozumieć przejścia stanu encji JPA, rozważ następujący diagram:
Korzystając z JPA, aby ponownie powiązać odłączony obiekt z aktywnym EntityManager
, możesz użyć operacji scalania .
Korzystając z natywnego interfejsu API Hibernacji, oprócz tego merge
można ponownie podłączyć odłączony byt do aktywnej sesji hibernacji przy użyciu metod aktualizacji, jak pokazano na poniższym diagramie:
Scalanie odłączonego bytu
Scalenie spowoduje skopiowanie stanu odłączonej jednostki (źródła) do instancji jednostki zarządzanej (miejsca docelowego).
Rozważmy, że utrwaliliśmy następującą Book
jednostkę, a teraz jednostka jest odłączona, ponieważ ta, EntityManager
która była używana do utrwalenia, została zamknięta:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
Gdy jednostka jest w stanie odłączonym, modyfikujemy ją w następujący sposób:
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
Teraz chcemy propagować zmiany w bazie danych, abyśmy mogli wywołać merge
metodę:
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
Hibernacja wykona następujące instrukcje 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
Jeśli łącząca się jednostka nie ma odpowiednika w bieżącym EntityManager
, migawka nowej jednostki zostanie pobrana z bazy danych.
Gdy istnieje zmieniony obiekt, JPA kopiuje stan odłączonego obiektu na ten, który jest obecnie zarządzany, a podczas kontekstuflush
trwałości zostanie wygenerowana AKTUALIZACJA, jeśli mechanizm brudnego sprawdzania wykryje, że zarządzany obiekt się zmienił.
Tak więc podczas używania merge
instancja odłączonego obiektu pozostanie odłączona nawet po operacji scalania.
Ponowne podłączanie odłączonego bytu
Hibernacja, ale nie JPA obsługuje ponowne podłączanie za pomocą tej update
metody.
Hibernacja Session
może skojarzyć tylko jeden obiekt encji dla danego wiersza bazy danych. Wynika to z faktu, że kontekst trwałości działa jako pamięć podręczna w pamięci (pamięć podręczna pierwszego poziomu) i tylko jedna wartość (jednostka) jest powiązana z danym kluczem (typ jednostki i identyfikator bazy danych).
Podmiot może zostać ponownie przyłączony tylko wtedy, gdy żaden inny obiekt JVM (pasujący do tego samego wiersza bazy danych) nie jest już powiązany z bieżącym Hibernacją Session
.
Biorąc pod uwagę, że utrwaliliśmy Book
encję i zmodyfikowaliśmy ją, gdy Book
encja była w stanie odłączonym:
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"
);
Możemy ponownie podłączyć odłączony byt w następujący sposób:
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
LOGGER.info("Updating the Book entity");
});
Hibernacja wykona następującą instrukcję SQL:
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
Ta update
metoda wymaga przejścia do unwrap
stanu EntityManager
hibernacji Session
.
W odróżnieniu od tego merge
, podana odłączona jednostka zostanie ponownie powiązana z bieżącym kontekstem trwałości, a podczas aktualizacji zaplanowano aktualizację, niezależnie od tego, czy jednostka zmodyfikowała się, czy nie.
Aby temu zapobiec, możesz użyć @SelectBeforeUpdate
adnotacji Hibernacja, która wyzwoli instrukcję SELECT, która pobrała stan załadowania, który jest następnie używany przez mechanizm sprawdzania nieczytelności.
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
Uwaga na wyjątek NonUniqueObjectException
Jednym z problemów, które mogą wystąpić, update
jest to, czy kontekst utrwalania zawiera już odwołanie do jednostki o tym samym identyfikatorze i tego samego typu, co w poniższym przykładzie:
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
);
}
Teraz, wykonując powyższy przypadek testowy, Hibernacja rzuci a, NonUniqueObjectException
ponieważ drugi EntityManager
zawiera już Book
encję o tym samym identyfikatorze, co ten, który przekazujemy update
, a kontekst trwałości nie może pomieścić dwóch reprezentacji tej samej encji.
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)
Wniosek
Ta merge
metoda jest preferowana, jeśli korzystasz z blokowania optymistycznego, ponieważ pozwala ona zapobiec utracie aktualizacji. Więcej informacji na ten temat można znaleźć w tym artykule .
Jest update
to przydatne w przypadku aktualizacji wsadowych, ponieważ może zapobiec dodatkowej instrukcji SELECT generowanej przez merge
operację, a tym samym skrócić czas wykonywania aktualizacji wsadowej.
refresh()
na odłączone podmioty? Przeglądając specyfikację 2.0, nie widzę żadnego uzasadnienia; tylko to nie jest dozwolone.