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#persistmetodę, 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 Sessionczasu 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 EntityManagerinterfejs.
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 mergemoż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ą Bookjednostkę, a teraz jednostka jest odłączona, ponieważ ta, EntityManagerktó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ć mergemetodę:
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 mergeinstancja 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 updatemetody.
Hibernacja Sessionmoż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 Bookencję i zmodyfikowaliśmy ją, gdy Bookencja 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 updatemetoda wymaga przejścia do unwrapstanu EntityManagerhibernacji 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ć @SelectBeforeUpdateadnotacji 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ć, updatejest 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, NonUniqueObjectExceptionponieważ drugi EntityManagerzawiera już Bookencję 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 mergemetoda 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 updateto przydatne w przypadku aktualizacji wsadowych, ponieważ może zapobiec dodatkowej instrukcji SELECT generowanej przez mergeoperację, 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.