EntityManager.merge()
może wstawiać nowe obiekty i aktualizować istniejące.
Dlaczego miałby chcieć używać persist()
(który może tworzyć tylko nowe obiekty)?
jpa
merge
entitymanager
persist
java-persistence-api
Aaron Digulla
źródło
źródło
Odpowiedzi:
Tak czy inaczej doda encję do PersistenceContext, różnica polega na tym, co zrobisz z encją później.
Persist pobiera instancję encji, dodaje ją do kontekstu i zarządza tą instancją (tzn. Przyszłe aktualizacje encji będą śledzone).
Opcja Scal zwraca instancję zarządzaną, z którą stan został scalony. Zwraca coś, co istnieje w PersistenceContext lub tworzy nową instancję twojej encji. W każdym razie skopiuje stan z podanego obiektu i zwróci kopię zarządzaną. Przekazywana instancja nie będzie zarządzana (wszelkie wprowadzone zmiany nie będą częścią transakcji - chyba że ponownie wywołasz scalanie). Możesz poprzez użycie zwróconej instancji (zarządzanej).
Może pomoże przykładowy kod.
Scenariusz 1 i 3 są w przybliżeniu równoważne, ale istnieją sytuacje, w których warto użyć scenariusza 2.
źródło
merge
pełna kopia obiektu przed zarządzaniem ma wpływ na wydajność?@GeneratedId
mogę to zdobyć w scenariuszu 2?Utrwalanie i łączenie służą dwóm różnym celom (wcale nie są alternatywami).
(edytowane w celu rozszerzenia informacji o różnicach)
trwać:
łączyć:
wydajność persist ():
semantyka persist ():
Przykład:
W ten sposób istnieje tylko 1 dołączony obiekt dla dowolnego rejestru w menedżerze encji.
merge () dla encji o identyfikatorze przypomina coś:
Chociaż połączenie z MySQL Merge () może być tak samo wydajne jak persist () przy użyciu wywołania INSERT z opcją ON DUPLICATE KEY UPDATE, JPA to programowanie na bardzo wysokim poziomie i nie można zakładać, że tak będzie wszędzie.
źródło
em.persist(x)
zx = em.merge(x)
?merge()
może również rzucićEntityExistsException
RuntimeException
, ale nie jest wspomniany w Javadoc.Jeśli używasz przypisanego generatora, użycie funkcji scalania zamiast opcji utrwalania może spowodować nadmiarową instrukcję SQL , a tym samym wpłynąć na wydajność.
Ponadto, nazywając seryjnej dla podmiotów zarządzanych jest również błędem, ponieważ podmioty zarządzane są automatycznie zarządzane przez Hibernate i ich stan jest zsynchronizowany z rekordu bazy danych przez mechanizm kontroli brudny po spłukiwania Context Persistence .
Aby zrozumieć, jak to wszystko działa, najpierw powinieneś wiedzieć, że Hibernacja zmienia sposób myślenia programisty z instrukcji SQL na przejścia stanów encji .
Gdy jednostka jest aktywnie zarządzana przez Hibernację, wszystkie zmiany będą automatycznie propagowane do bazy danych.
Hibernacja monitoruje aktualnie podłączone podmioty. Ale aby jednostka mogła być zarządzana, musi być we właściwym stanie encji.
Aby lepiej zrozumieć przejścia stanu JPA, możesz zwizualizować następujący diagram:
Lub jeśli używasz interfejsu API Hibernacji:
Jak ilustrują powyższe diagramy, jednostka może znajdować się w jednym z czterech następujących stanów:
Nowy (przejściowy)
Nowo utworzony obiekt, który nigdy nie był powiązany z Hibernacją
Session
(aliasPersistence 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.
Aby powiązać odłączony byt z aktywną sesją hibernacji, możesz wybrać jedną z następujących opcji:
Ponowne podłączenie
Hibernacja (ale nie JPA 2.1) obsługuje ponowne podłączanie za pomocą metody aktualizacji Session #. Sesja hibernacji 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żącą sesją hibernacji.
Scalanie
Scalenie spowoduje skopiowanie stanu odłączonej jednostki (źródła) do instancji jednostki zarządzanej (miejsca docelowego). Jeśli łącząca się jednostka nie ma odpowiednika w bieżącej sesji, zostanie ona pobrana z bazy danych. Instancja odłączonego obiektu pozostanie odłączona nawet po operacji scalania.
Oddalony
Chociaż JPA wymaga, aby można było usuwać tylko zarządzane jednostki, Hibernacja może również usuwać odłączone jednostki (ale tylko poprzez wywołanie metody Session # delete). Usunięty obiekt jest zaplanowany tylko do usunięcia, a rzeczywista instrukcja DELETE bazy danych zostanie wykonana podczas czasu opróżniania sesji.
źródło
Zauważyłem, że kiedy korzystałem
em.merge
, otrzymałemSELECT
instrukcję dla każdegoINSERT
, nawet gdy nie było dla mnie pola generowanego przez JPA - pole klucza podstawowego to UUID, który sam ustawiłem. Przełączyłem się wtedyem.persist(myEntityObject)
i dostałem tylkoINSERT
oświadczenia.źródło
merge()
. Miałem bazę danych PostgreSQL ze skomplikowanym widokiem : widok agregował dane z kilku tabel (tabele miały identyczną strukturę, ale różne nazwy). Więc JPA próbowało to zrobićmerge()
, ale tak naprawdę najpierw JPASELECT
(baza danych z powodu ustawień widoku mogła zwrócić kilka rekordów z tym samym kluczem podstawowym z różnych tabel!), A następnie JPA (Hibernacja była implementacją) nie powiodło się: istnieje kilka rekordów z tym samym kluczem (org.hibernate.HibernateException: More than one row with the given identifier was found
). W moim przypadkupersist()
pomógł mi.Specyfikacja JPA mówi o tym, co następuje
persist()
.Dlatego użycie
persist()
byłoby odpowiednie, gdy obiekt nie powinien być obiektem odłączonym. Być może wolisz, aby kod wyrzucił,PersistenceException
więc szybko się nie powiedzie.Chociaż specyfikacja jest niejasna ,
persist()
może ustawić@GeneratedValue
@Id
dla obiektu.merge()
musi jednak mieć obiekt z@Id
już wygenerowanym.źródło
merge()
jednak musi mieć obiekt z@Id
już wygenerowanym . ”. Ilekroć EntityManager nie znajdzie wartości dla pola identyfikatora obiektu, jest on utrwalany (wstawiany) do bazy danych.Trochę więcej szczegółów na temat scalania, które pomogą ci użyć scalania ponad:
Wszystkie powyższe informacje pochodzą z „Pro JPA 2 Mastering the Java ™ Persistence API” autorstwa Mike'a Keitha i Merricka Schnicariola. Rozdział 6. Odłączanie i łączenie sekcji. Ta książka jest właściwie drugą książką poświęconą WZP autorom. Ta nowa książka zawiera wiele nowych informacji niż poprzednia. Naprawdę poleciłem przeczytać tę książkę dla tych, którzy będą poważnie zaangażowani w JPA. Przykro mi, że anonimowo opublikowałem pierwszą odpowiedź.
źródło
Jest jeszcze kilka różnic między
merge
ipersist
(wymienię ponownie te już tutaj opublikowane):D1
merge
nie sprawia, że przekazywana jednostka jest zarządzana, ale zwraca inną zarządzaną instancję.persist
z drugiej strony sprawi, że przekazywany podmiot będzie zarządzany:D2 Jeśli usuniesz encję, a następnie zdecydujesz się zachować encję z powrotem, możesz to zrobić tylko za pomocą persist (), ponieważ
merge
wyrzuci anIllegalArgumentException
.D3 Jeśli zdecydujesz się zająć ręcznie swoimi identyfikatorami (np. Za pomocą identyfikatorów UUID), wówczas
merge
operacja wywoła kolejneSELECT
zapytania w celu wyszukania istniejących encji o tym identyfikatorze, chociażpersist
może nie potrzebować tych zapytań.D4 Są przypadki, gdy po prostu nie ufasz kodowi, który wywołuje Twój kod, i aby upewnić się, że żadne dane nie są aktualizowane, ale raczej wstawione, musisz użyć
persist
.źródło
Otrzymywałem wyjątki leniwe ładowanie mojej encji, ponieważ próbowałem uzyskać dostęp do leniwie ładowanej kolekcji, która była w sesji.
To, co bym zrobił, to oddzielne żądanie, pobierz encję z sesji, a następnie spróbuj uzyskać dostęp do kolekcji na mojej stronie jsp, która była problematyczna.
Aby temu zaradzić, zaktualizowałem ten sam byt w moim kontrolerze i przekazałem go do mojego pliku jsp, chociaż wyobrażam sobie, że kiedy ponownie zapisałem w sesji, będzie on również dostępny
SessionScope
i nie wrzuciLazyLoadingException
, modyfikacja przykładu 2:Dla mnie zadziałało:
źródło
Znalazłem to wyjaśnienie z oświecenia dokumentów Hibernacja, ponieważ zawierają one przypadek użycia:
Od: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
źródło
Przeglądając odpowiedzi brakuje pewnych szczegółów dotyczących `Kaskady 'i generowania identyfikatorów. Zobacz pytanie
Warto również wspomnieć, że możesz mieć osobne
Cascade
adnotacje do łączenia i utrwalania:Cascade.MERGE
iCascade.PERSIST
które będą traktowane zgodnie z zastosowaną metodą.Specyfikacja jest twoim przyjacielem;)
źródło
Utrzymujące się podmioty
W przeciwieństwie do metody scalania metoda persist jest dość prosta i intuicyjna. Najczęstszy scenariusz użycia metody persist można podsumować w następujący sposób:
„Nowo utworzona instancja klasy encji jest przekazywana do metody persist. Po powrocie tej metody encja jest zarządzana i planowana do wstawienia do bazy danych. Może się to zdarzyć w momencie lub przed zatwierdzeniem transakcji lub po wywołaniu metody flush. Jeśli jednostka odwołuje się do innej jednostki poprzez relację oznaczoną strategią kaskadową PERSIST, procedura ta ma również zastosowanie do niej. ”
Specyfikacja jest bardziej szczegółowa, jednak ich zapamiętanie nie jest kluczowe, ponieważ dotyczą one mniej lub bardziej egzotycznych sytuacji.
Scalanie podmiotów
W porównaniu do trwałego opis zachowania scalania nie jest tak prosty. Nie ma głównego scenariusza, jak ma to miejsce w przypadku persist, a programista musi zapamiętać wszystkie scenariusze, aby napisać poprawny kod. Wydaje mi się, że projektanci WZP chcieli mieć metodę, której podstawową troską byłaby obsługa odłączonych encji (w przeciwieństwie do metody persist, która dotyczy przede wszystkim nowo tworzonych encji). Głównym zadaniem metody scalania jest przeniesienie stanu z niezarządzana jednostka (przekazana jako argument) do swojego zarządzanego odpowiednika w kontekście trwałości. To zadanie dzieli się jednak na kilka scenariuszy, które pogarszają zrozumiałość zachowania całej metody.
Zamiast powtarzania akapitów ze specyfikacji JPA przygotowałem schemat blokowy, który schematycznie przedstawia zachowanie metody scalania:
Kiedy więc powinienem użyć opcji keep, a kiedy scalenia?
trwać
łączyć
źródło
Scenariusz X:
Table: Spitter (One), Table: Spittles (Many) (Spittles jest właścicielem relacji z FK: spitter_id)
Ten scenariusz skutkuje zapisaniem: Spittera i obu Spittlesów, jakby były własnością Same Spittera.
Scenariusz Y:
To uratuje Spittera, uratuje 2 Spittles Ale nie będą odnosić się do tego samego Spittera!
źródło
Kolejna obserwacja:
merge()
będzie dbał o automatycznie wygenerowany identyfikator (testowany naIDENTITY
iSEQUENCE
), gdy rekord o takim identyfikatorze już istnieje w twojej tabeli. W takim przypadkumerge()
spróbuje zaktualizować rekord. Jeśli jednak identyfikator jest nieobecny lub nie pasuje do żadnych istniejących rekordów,merge()
całkowicie go zignoruje i poprosi db o przydzielenie nowego. Czasami jest to źródłem wielu błędów. Nie używajmerge()
do wymuszania identyfikatora dla nowego rekordu.persist()
z drugiej strony nigdy nie pozwoli ci nawet przekazać mu identyfikatora. Natychmiast zawiedzie. W moim przypadku jest to:hibernate-jpa javadoc ma podpowiedź:
źródło
persist()
nie narzeka, że ma identyfikator, narzeka tylko wtedy, gdy coś o tym samym identyfikatorze jest już w bazie danych.Być może przyszedłeś tutaj po porady, kiedy używać trwałości, a kiedy używać scalania . Myślę, że to zależy od sytuacji: jak prawdopodobne jest, że musisz utworzyć nowy rekord i jak trudno jest odzyskać utrwalone dane.
Załóżmy, że możesz użyć naturalnego klucza / identyfikatora.
Dane muszą zostać utrwalone, ale raz na jakiś czas istnieje rekord i wymagana jest aktualizacja. W takim przypadku możesz spróbować utrwalić, a jeśli zgłosi wyjątek EntityExistsException, poszukaj go i połącz dane:
spróbuj {entityManager.persist (byt)}
catch (wyjątek EntityExistsException) {/ * pobierz i scal * /}
Utrwalone dane muszą zostać zaktualizowane, ale od czasu do czasu nie ma jeszcze danych. W takim przypadku możesz to sprawdzić i wykonać utrwalenie, jeśli brakuje elementu:
encja = entityManager.find (klucz);
if (encja == null) {entityManager.persist (encja); }
else {/ * merge * /}
Jeśli nie masz naturalnego klucza / identyfikatora, trudniej będzie ci ustalić, czy istota istnieje, czy nie, i jak to sprawdzić.
Połączenia można również wykonać na dwa sposoby:
źródło
persist (encja) powinna być używana z całkowicie nowymi encjami, aby dodać je do DB (jeśli encja już istnieje w DB, będzie rzut EntityExistsException).
należy użyć scalania (bytu), aby przywrócić byt do kontekstu trwałości, jeśli byt został odłączony i został zmieniony.
Prawdopodobnie trwa to generowanie instrukcji SQL INSERT i scalanie instrukcji SQL UPDATE (ale nie jestem pewien).
źródło