W trakcie pisania kawałka kodu wykorzystującego JPA natrafiłem na następującą sytuację. Z innej warstwy aplikacji otrzymywałem nowy obiekt, którego jeden z atrybutów był obiektem zapisanym już wcześniej w bazie danych. Jednak był on w stanie detached i należałoby go włączyć do persistence context przed zapisaniem głównego obiektu.
class A { // nowy obiekt, bez ustawionego id @Id int id; @ManyToOne B b; // obiekt już zapisany w bazie danych, detached }
Rozwiązałem to w ten sposób, że relacji do encji B ustawiłem atrybut cascade na CascadeType.MERGE (np. @ManyToOne(cascade = CascadeType.MERGE)) i użyłem metody merge() do zapisania nowego obiektu. Zadziałało, ale zacząłem się zastanawiać, jaka właściwie jest różnice pomiędzy persist() a merge().
Zacznijmy od dokumentacji. Poniższe punkty stanowią tłumaczenie specyfikacji JPA:
3.2.1 Zapisywanie encji
Nowa instancja encji zostaje zapisana w bazie danych i staje się zarządzaną poprzez jawne lub kaskadowe wywołanie na niej operacji persist().
- Jeśli X jest nową encją, staje się zarządzaną. Zapis do bazy danych następuje przed lub w momencie zatwierdzenia transakcji albo w wyniku opróżnienia bufora operacji.
- Jeśli X jest encją, która znajduje się już w bazie danych i jest zarządzana, to nie bierze udziału w zapisywaniu. Jednak operacja ta jest wywoływana kaskadowo na wszystkich encjach będących w relacji do X, jeśli relacje te są opisane adnotacjami
cascade = CascadeType.PERSISTalbocascade = CascadeType.ALL. Ustawienia te można też umieścić w odpowiednim elemencie deskryptora. - Jeśli encja X została usunięta, staje się zarządzaną.
- Jeśli X jest encją w stanie detached (ang. odłączona), wyjątek
EntityExistsExceptionmoże zostać zgłoszony po wywołaniu metodypersist()albo w momencie zatwierdzenia transakcji lub opróżnienia bufora operacji może zostać zgłoszony wyjątekEntityExistsExceptionlub innyPersistenceException. - Dla wszystkich encji Y będących w relacji do X, jeśli relacje są opisane adnotacjami z atrybutem
cascade = CascadeType.PERSISTalbocascade = CascadeType.ALL, to operacja zapisywania odnosi się również do Y.
3.4.2.1 Scalanie stanu odłączonej encji
Operacja scalania propaguje stan odłączonej encji do instancji zarządzanej przez Entity Manager.
- Jeśli X jest odłączoną encją, to jej stan jest kopiowany do już istniejącej i zarządzanej instancji X’ o tej samej tożsamości albo tworzona jest jej nowa, zarządzana kopia X’.
- Jeśli X jest nową instancją, to nowa, zarządzana instancja X’ zostanie utworzona a stan encji X jest kopiowany do X’.
- Jeśli X została usunięta, wyjątek
IllegalArgumentExceptionzostanie zgłoszony albo transakcja zakończy się niepowodzeniem. - Jeśli X jest już zarządzana, to operacja
merge()jest ignorowana. Jest ona jednak wywoływana kaskadowo na wszystkich encjach będących w relacji do X, jeśli relacje te są opisane adnotacjami z atrybutemcascade = CascadeType.MERGEalbocascade = CascadeType.ALL. - Dla wszystkich encji Y będących w relacji do X i relacje te są opisane adnotacją z atrybutami
cascade = CascadeType.MERGEalbocascade = CascadeType.ALL, Y jest scalana rekurencyjnie do Y’. Dla wszystkich relacji X z Y, jest tworzona relacja pomiędzy odpowiadającymi X’ i Y’ (jeśli X jest już zarządzana, to X jest tym samym obiektem co X’). - Jeśli X jest scalona do X’, z relacją do encji Y a relacja nie jest opisana adnotacją z atrybutem
cascade = CascadeType.MERGEalbocascade = CascadeType.ALL, to trawersowanie tej samej relacji z encji X’ da w wyniku referencję do zarządzanego obiektu Y’ o tej samej tożsamości co Y.
Tyle teorii…
Co z niej wynika, jeśli chodzi o pisanie kodu?
E e = new E(); em.persist(e); e.setField("newValue"); // ta zmiana zostanie zapisana w bazie danych przy zatwierdzeniu transakcji, // gdyż w tym momencie encja 'e' jest już zarządzana
E e = new E(); em.merge(e); e.setField("newValue"); // ta zmiana nie zostanie zapisana w bazie danych, // gdyż encja 'e' nie jest zarządzana
E e = new E(); E ep = em.merge(e); ep.setField("newValue"); // ta zmiana zostanie zapisana w bazie danych przy zatwierdzeniu transakcji, // gdyż encja 'ep' jest zarządzana i tożsama z e
E e = getEntityFromWebTier(E.class); // encja 'e' jest 'detached' em.persist(e); // wyjątek
Jeśli znacie inne przykłady na różnice pomiędzy persist() a merge(), zachęcam do podzielenia się nimi w komentarzach.
Comments
Leave a comment Trackback