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)?
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)?
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.
MyEntity e = new MyEntity();
// scenario 1
// tran starts
em.persist(e);
e.setSomeField(someValue);
// tran ends, and the row for someField is updated in the database
// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue);
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)
// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue);
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)
Scenariusz 1 i 3 są w przybliżeniu równoważne, ale istnieją sytuacje, w których warto użyć scenariusza 2.
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:
{
AnyEntity newEntity;
AnyEntity nonAttachedEntity;
AnyEntity attachedEntity;
// Create a new entity and persist it
newEntity = new AnyEntity();
em.persist(newEntity);
// Save 1 to the database at next flush
newEntity.setValue(1);
// Create a new entity with the same Id than the persisted one.
AnyEntity nonAttachedEntity = new AnyEntity();
nonAttachedEntity.setId(newEntity.getId());
// Save 2 to the database at next flush instead of 1!!!
nonAttachedEntity.setValue(2);
attachedEntity = em.merge(nonAttachedEntity);
// This condition returns true
// merge has found the already attached object (newEntity) and returns it.
if(attachedEntity==newEntity) {
System.out.print("They are the same object!");
}
// Set 3 to value
attachedEntity.setValue(3);
// Really, now both are the same object. Prints 3
System.out.println(newEntity.getValue());
// Modify the un attached object has no effect to the entity manager
// nor to the other objects
nonAttachedEntity.setValue(42);
}
W ten sposób istnieje tylko 1 dołączony obiekt dla dowolnego rejestru w menedżerze encji.
merge () dla encji o identyfikatorze przypomina coś:
AnyEntity myMerge(AnyEntity entityToSave) {
AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
if(attached==null) {
attached = new AnyEntity();
em.persist(attached);
}
BeanUtils.copyProperties(attached, entityToSave);
return attached;
}
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.
em.persist(x)
z x = 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
(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.
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.
Zauważyłem, że kiedy korzystałem em.merge
, otrzymałem SELECT
instrukcję dla każdego INSERT
, 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ę wtedy em.persist(myEntityObject)
i dostałem tylko INSERT
oświadczenia.
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 JPA SELECT
(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 przypadku persist()
pomógł mi.
Specyfikacja JPA mówi o tym, co następuje persist()
.
Jeśli X jest obiektem odłączonym,
EntityExistsException
może zostać wyrzucony, gdy wywoływana jest operacja utrwalania,EntityExistsException
lub innyPersistenceException
może zostać wyrzucony w czasie opróżniania lub zatwierdzania.
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.
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:
Zwracanie zarządzanej instancji innej niż pierwotna jednostka jest krytyczną częścią procesu scalania. Jeśli instancja encji o tym samym identyfikatorze już istnieje w kontekście trwałości, dostawca zastąpi swój stan stanem encji, która jest scalana, ale istniejąca już wersja zarządzana musi zostać zwrócona klientowi, aby można ją było używany. Jeśli dostawca nie zaktualizował instancji pracownika w kontekście trwałości, wszelkie odwołania do tej instancji staną się niespójne w przypadku połączenia nowego stanu.
Gdy merge () jest wywoływana na nowej encji, zachowuje się podobnie jak operacja persist (). Dodaje jednostkę do kontekstu trwałości, ale zamiast dodawać oryginalną instancję encji, tworzy nową kopię i zamiast tego zarządza tą instancją. Kopia utworzona przez operację merge () jest utrwalana tak, jakby wywołano na niej metodę persist ().
W przypadku relacji operacja scalania () spróbuje zaktualizować zarządzaną jednostkę, aby wskazywała na zarządzane wersje jednostek, do których odwołuje się odłączona jednostka. Jeśli jednostka ma związek z obiektem, który nie ma trwałej tożsamości, wynik operacji scalania jest niezdefiniowany. Niektórzy dostawcy mogą zezwolić, aby kopia zarządzana wskazywała na nietrwały obiekt, podczas gdy inni mogą natychmiast zgłosić wyjątek. W takich przypadkach operacja scalania () może być opcjonalnie kaskadowana, aby zapobiec wystąpieniu wyjątku. W dalszej części tej sekcji omówimy kaskadowanie operacji scalania (). Jeśli scalany obiekt wskazuje na usunięty obiekt, zostanie zgłoszony wyjątek IllegalArgumentException.
Relacje leniwe ładowanie są szczególnym przypadkiem w operacji scalania. Jeśli relacja leniwego ładowania nie została uruchomiona na encji przed jej odłączeniem, relacja ta zostanie zignorowana po scaleniu encji. Jeśli relacja została uruchomiona podczas zarządzania, a następnie ustawiona na null, gdy jednostka została odłączona, zarządzana wersja jednostki również będzie wyczyścić relację podczas scalania. ”
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ź.
Jest jeszcze kilka różnic między merge
i persist
(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:
//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);
//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);
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 an IllegalArgumentException
.
D3 Jeśli zdecydujesz się zająć ręcznie swoimi identyfikatorami (np. Za pomocą identyfikatorów UUID), wówczas merge
operacja wywoła kolejne SELECT
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
.
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 wrzuci LazyLoadingException
, modyfikacja przykładu 2:
Dla mnie zadziałało:
// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"
//access e from jsp and it will work dandy!!
Znalazłem to wyjaśnienie z oświecenia dokumentów Hibernacja, ponieważ zawierają one przypadek użycia:
Użycie i semantyka funkcji merge () wydaje się być myląca dla nowych użytkowników. Po pierwsze, dopóki nie próbujesz użyć stanu obiektu załadowanego w jednym menedżerze encji w innym nowym menedżerze encji, nie powinieneś w ogóle używać merge () . Niektóre całe aplikacje nigdy nie użyją tej metody.
Zwykle funkcja merge () jest używana w następującym scenariuszu:
- Aplikacja ładuje obiekt do pierwszego menedżera encji
- obiekt jest przekazywany do warstwy prezentacji
- wprowadzono pewne modyfikacje do obiektu
- obiekt jest przekazywany z powrotem do warstwy logiki biznesowej
- aplikacja utrzymuje te modyfikacje, wywołując funkcję merge () w menedżerze drugiej jednostki
Oto dokładna semantyka scalania ():
- jeśli istnieje instancja zarządzana z tym samym identyfikatorem aktualnie powiązanym z kontekstem trwałości, skopiuj stan danego obiektu do instancji zarządzanej
- jeśli nie ma obecnie instancji zarządzanej powiązanej z kontekstem trwałości, spróbuj załadować ją z bazy danych lub utwórz nową instancję zarządzaną
- instancja zarządzana jest zwracana
- dana instancja nie zostaje skojarzona z kontekstem trwałości, pozostaje odłączona i zwykle jest odrzucana
Od: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
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
i Cascade.PERSIST
które będą traktowane zgodnie z zastosowaną metodą.
Specyfikacja jest twoim przyjacielem;)
JPA jest bez wątpienia doskonałym uproszczeniem w dziedzinie aplikacji korporacyjnych zbudowanych na platformie Java. Jako programista, który musiał poradzić sobie z zawiłościami starej fasoli encji w J2EE, postrzegam włączenie JPA do specyfikacji Java EE jako duży krok naprzód. Jednak zagłębiając się w szczegóły JPA, znajduję rzeczy, które nie są takie łatwe. W tym artykule zajmuję się porównaniem metod łączenia i utrwalania EntityManager, których zachodzenie na siebie może powodować zamieszanie nie tylko dla początkujących. Ponadto proponuję uogólnienie, w którym obie metody są traktowane jako szczególne przypadki bardziej ogólnej metody.
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ć
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.
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.addSpittle(spittle3); // <--persist
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Scenariusz Y:
To uratuje Spittera, uratuje 2 Spittles Ale nie będą odnosić się do tego samego Spittera!
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.save(spittle3); // <--merge!!
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Kolejna obserwacja:
merge()
będzie dbał o automatycznie wygenerowany identyfikator (testowany na IDENTITY
i SEQUENCE
), gdy rekord o takim identyfikatorze już istnieje w twojej tabeli. W takim przypadku merge()
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żywaj merge()
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:
Przyczyna: org.hibernate.PersistentObjectException: odłączony byt przekazany do trwałego działania
hibernate-jpa javadoc ma podpowiedź:
Zgłasza : javax.persistence.EntityExistsException - jeśli jednostka już istnieje. (Jeśli jednostka już istnieje, może zostać wygenerowany wyjątek EntityExistsException podczas wywoływania operacji trwałej lub wyjątek EntityExistsException lub inny wyjątek PersistenceException może zostać zgłoszony w momencie opróżnienia lub zatwierdzenia.)
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:
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).