Jakie są różnice między różnymi metodami oszczędzania w Hibernacji?

199

Hibernacja ma kilka metod, które w taki czy inny sposób zabierają obiekt i umieszczają go w bazie danych. Jakie są między nimi różnice, kiedy z których korzystać, i dlaczego nie istnieje tylko jedna inteligentna metoda, która wie, kiedy użyć?

Metody, które do tej pory zidentyfikowałem to:

  • save()
  • update()
  • saveOrUpdate()
  • saveOrUpdateCopy()
  • merge()
  • persist()
Henrik Paul
źródło

Odpowiedzi:

117

Oto moje rozumienie metod. Głównie są one oparte na interfejsie API, ponieważ nie używam ich wszystkich w praktyce.

saveOrUpdate Połączenia zapisują lub aktualizują w zależności od niektórych kontroli. Np. Jeśli nie ma identyfikatora, wywoływane jest save. W przeciwnym razie wywoływana jest aktualizacja.

zapisz Utrzymuje byt. Przydzieli identyfikator, jeśli nie istnieje. Jeśli tak, to zasadniczo robi aktualizację. Zwraca wygenerowany identyfikator encji.

aktualizacja Próby utrwalenia jednostki przy użyciu istniejącego identyfikatora. Jeśli nie istnieje żaden identyfikator, uważam, że zgłoszony został wyjątek.

saveOrUpdateCopy To jest przestarzałe i nie powinno być dłużej używane. Zamiast tego jest ...

scalić Teraz moja wiedza zaczyna słabnąć. Ważną rzeczą jest tutaj różnica między istotami przejściowymi, odłączonymi i trwałymi. Aby uzyskać więcej informacji o stanach obiektów, spójrz tutaj . Dzięki funkcji Zapisz i aktualizuj masz do czynienia z obiektami trwałymi. Są powiązane z sesją, więc Hibernacja wie, co się zmieniło. Ale kiedy masz obiekt przejściowy, nie ma w tym żadnej sesji. W takich przypadkach należy użyć scalania do aktualizacji i utrwalania do zapisywania.

utrzymują Jak wspomniano powyżej, jest stosowany na przemijających przedmiotów. Nie zwraca wygenerowanego identyfikatora.

Lee Theobald
źródło
22
Chciałbym zaakceptować to jako odpowiedź, ale jedno jest nadal niejasne: skoro save () wraca do update (), jeśli taki element istnieje, czym różni się od saveOrUpdate () w praktyce?
Henrik Paul,
Gdzie jest określone, że zapisywanie będzie działać na odłączonych instancjach?
jrudolph
2
Jeśli opis scalania / utrzymywania jest ważny tylko na obiektach przejściowych, ma to sens i pasuje do tego, w jaki sposób używamy hibernacji. Należy również pamiętać, że scalanie często ma ograniczenia wydajności w porównaniu z aktualizacją, ponieważ wydaje się, że wykonuje dodatkowe pobieranie dla pewnego rodzaju kontroli integralności.
Martin Dale Lyness
1
Odpowiedź Jrudolpha poniżej jest dokładniejsza.
azerol
2
Biorąc pod uwagę hibernację, prawdopodobnie wie, w jakim stanie znajduje się obiekt, dlaczego musimy to robić ręcznie podczas pisania programu. Powinna być tylko jedna metoda zapisu.
masterxilo
116
╔══════════════╦═══════════════════════════════╦════════════════════════════════╗
    METHOD                TRANSIENT                      DETACHED            
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                     sets id if doesn't         sets new id even if object   
    save()         exist, persists to db,        already has it, persists    
                  returns attached object     to DB, returns attached object 
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                     sets id on object                    throws             
   persist()       persists object to DB            PersistenceException     
                                                                             
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                                                                             
   update()              Exception                persists and reattaches    
                                                                             
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                copy the state of object in      copy the state of obj in    
    merge()        DB, doesn't attach it,    ║      DB, doesn't attach it,    
                  returns attached object         returns attached object    
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                                                                             
saveOrUpdate()║           as save()                       as update()         
                                                                             
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝
Sergii Szewczyk
źródło
updateobiekt przejściowy jest w porządku, nie dostałem wyjątku.
GMsoF,
Co wiem, nie możemy utrzymać przemijalności w żaden sposób. Myślę, że różnica może być między odłączeniem a trwałością. Proszę mnie poprawić.
Ram
jest tu wiele błędów ... np. 1) ´save () ´ nie zwraca „dołączonego obiektu”, zwraca „id”; 2) perspersist () ´ nie ma gwarancji ustawienia „id”, ani „nie utrzymuje obiektu DB”; ...
Eugen Labun,
67
  • Zobacz forum Hibernacji, aby uzyskać wyjaśnienie subtelnych różnic między utrzymywaniem a zapisywaniem. Wygląda na to, że różnica polega na tym, kiedy instrukcja INSERT jest ostatecznie wykonywana. Ponieważ funkcja save zwraca identyfikator, instrukcja INSERT musi zostać wykonana natychmiast, niezależnie od stanu transakcji (co na ogół jest złą rzeczą). Persist nie wykona żadnych instrukcji poza bieżącą transakcją tylko w celu przypisania identyfikatora. Save / Persist oba działają na instancjach przejściowych , tj. Instancjach, które nie mają jeszcze przypisanego identyfikatora i jako takie nie są zapisywane w bazie danych.

  • Zaktualizuj i scal oba działają na odłączonych instancjach , tj. Instancjach, które mają odpowiedni wpis w bazie danych, ale które nie są obecnie dołączone do sesji ani nie są przez nią zarządzane. Różnica między nimi polega na tym, co dzieje się z instancją przekazywaną do funkcji. aktualizacja próbuje ponownie dołączyć instancję, co oznacza, że ​​nie może być teraz żadnej innej instancji trwałej jednostki dołączonej do sesji, w przeciwnym razie zostanie zgłoszony wyjątek. Scalanie , jednak wystarczy skopiować wszystkie wartości do trwałej instancji w sesji (która zostanie załadowana, jeśli nie jest aktualnie ładowana). Obiekt wejściowy nie ulega zmianie. Więc scal jest bardziej ogólne niż aktualizacja , ale może zużywać więcej zasobów.

jrudolph
źródło
instrukcja insert wciąż się nie zdarza, jeśli masz własny generator identyfikatorów
kommradHomer
Tak więc w przypadku odłączonego obiektu scalanie spowoduje wybranie, a aktualizacja nie?
Gab
1
save() - If an INSERT has to be executed to get the identifier, then this INSERT happens immediately, no matter if you are inside or outside of a transaction. This is problematic in a long-running conversation with an extended Session/persistence context.Czy możesz mi powiedzieć, w jaki sposób wkładka może się zdarzyć poza sesją i dlaczego jest tak źle?
Erran Morad
Oświadczenie: Dawno nie korzystałem z hibernacji. IMO ma następujący problem: podpis i umowa save () wymagają, aby save zwrócił identyfikator nowego obiektu. W zależności od wybranej strategii generowania identyfikatorów identyfikator jest generowany przez DB po edycji wartości INSERT. W związku z tym w takich przypadkach nie można teraz zwrócić identyfikatora bez wygenerowania go i aby go wygenerować, należy uruchomić INSERT teraz . Ponieważ transakcja długo działa nie jest prowadzony w tej chwili , ale tylko na zobowiązać, że jedynym sposobem, aby wykonać INSERTteraz, aby go uruchomić poza TX.
jrudolph
12

Ten link wyjaśnia w dobry sposób:

http://www.stevideter.com/2008/12/07/saveorupdate-versus-merge-in-hibernate/

Wszyscy mamy problemy, z którymi spotykamy się tak rzadko, że kiedy je ponownie widzimy, wiemy, że je rozwiązaliśmy, ale nie pamiętamy, jak to zrobić.

Wyjątek NonUniqueObjectException zgłaszany podczas używania Session.saveOrUpdate () w Hibernacji jest jednym z moich. Dodam nową funkcjonalność do złożonej aplikacji. Wszystkie moje testy jednostkowe działają dobrze. Następnie, testując interfejs użytkownika, próbując zapisać obiekt, zaczynam otrzymywać wyjątek z komunikatem „inny obiekt o tej samej wartości identyfikatora był już powiązany z sesją”. Oto przykładowy kod z Java Persistence with Hibernate.

            Session session = sessionFactory1.openSession();
            Transaction tx = session.beginTransaction();
            Item item = (Item) session.get(Item.class, new Long(1234));
            tx.commit();
            session.close(); // end of first session, item is detached

            item.getId(); // The database identity is "1234"
            item.setDescription("my new description");
            Session session2 = sessionFactory.openSession();
            Transaction tx2 = session2.beginTransaction();
            Item item2 = (Item) session2.get(Item.class, new Long(1234));
            session2.update(item); // Throws NonUniqueObjectException
            tx2.commit();
            session2.close();

Aby zrozumieć przyczynę tego wyjątku, ważne jest zrozumienie odłączonych obiektów i tego, co dzieje się po wywołaniu metody saveOrUpdate () (lub po prostu update ()) na odłączonym obiekcie.

Kiedy zamykamy pojedynczą sesję hibernacji, trwałe obiekty, z którymi pracujemy, są odłączane. Oznacza to, że dane są nadal w pamięci aplikacji, ale Hibernacja nie jest już odpowiedzialna za śledzenie zmian w obiektach.

Jeśli następnie zmodyfikujemy nasz odłączony obiekt i chcemy go zaktualizować, musimy ponownie dołączyć obiekt. Podczas tego procesu ponownego dołączania Hibernacja sprawdzi, czy istnieją inne kopie tego samego obiektu. Jeśli znajdzie jakiś, musi nam powiedzieć, że nie wie już, czym jest „prawdziwa” kopia. Być może wprowadzono inne zmiany w tych innych kopiach, które, jak się spodziewamy, zostaną zapisane, ale Hibernate nie wie o nich, ponieważ wtedy nie zarządzał nimi.

Zamiast zapisywać potencjalnie złe dane, Hibernacja informuje nas o problemie za pośrednictwem wyjątku NonUniqueObjectException.

Co więc mamy zrobić? W Hibernacji 3 mamy merge () (w Hibernacji 2 użyj saveOrUpdateCopy ()). Ta metoda zmusi Hibernację do skopiowania wszelkich zmian z innych odłączonych instancji do instancji, którą chcesz zapisać, a tym samym scali wszystkie zmiany w pamięci przed zapisaniem.

        Session session = sessionFactory1.openSession();
        Transaction tx = session.beginTransaction();
        Item item = (Item) session.get(Item.class, new Long(1234));
        tx.commit();
        session.close(); // end of first session, item is detached

        item.getId(); // The database identity is "1234"
        item.setDescription("my new description");
        Session session2 = sessionFactory.openSession();
        Transaction tx2 = session2.beginTransaction();
        Item item2 = (Item) session2.get(Item.class, new Long(1234));
        Item item3 = session2.merge(item); // Success!
        tx2.commit();
        session2.close();

Należy zauważyć, że funkcja scalania zwraca odwołanie do nowo zaktualizowanej wersji instancji. To nie jest ponowne podłączanie przedmiotu do sesji. Jeśli przetestujesz na przykład równość (item == item3), przekonasz się, że w tym przypadku zwraca false. Prawdopodobnie będziesz chciał pracować z item3 od tego momentu.

Należy również zauważyć, że interfejs API Java Persistence (JPA) nie ma pojęcia odłączonych i ponownie podłączonych obiektów, i korzysta z EntityManager.persist () i EntityManager.merge ().

Ogólnie stwierdziłem, że przy użyciu Hibernacji metoda saveOrUpdate () jest zwykle wystarczająca dla moich potrzeb. Zwykle muszę używać scalania tylko wtedy, gdy mam obiekty, które mogą mieć odwołania do obiektów tego samego typu. Ostatnio przyczyną wyjątku był kod potwierdzający, że odwołanie nie było rekurencyjne. W ramach sprawdzania poprawności ładowałem ten sam obiekt do mojej sesji, powodując błąd.

Gdzie napotkałeś ten problem? Czy scalenie działało dla Ciebie, czy potrzebujesz innego rozwiązania? Czy wolisz zawsze używać scalania, czy wolisz używać go tylko w razie potrzeby w określonych przypadkach

Hakuna Matata
źródło
Link do artykułu na temat webarchive, ponieważ oryginał nie jest dostępny: web.archive.org/web/20160521091122/http://www.stevideter.com:80/…
Eugen
5

W rzeczywistości różnica między hibernacją save()a persist()metodami zależy od używanej klasy generatora.

Jeśli nasza klasa generatora jest przypisana, nie ma różnicy między save()i persist() metodami. Ponieważ generator „przypisany” oznacza, że ​​jako programista musimy podać wartość klucza podstawowego, aby zapisać ją w bazie danych [Mam nadzieję, że znasz tę koncepcję generatorów] W przypadku innej niż przypisana klasa generatora, załóżmy, że nasza nazwa klasy generatora to Przyrost hibernuj to samo przypisze wartość identyfikatora klucza podstawowego do bazy danych w prawo [inny niż przypisany generator, hibernacja służy tylko do zapamiętania wartości identyfikatora klucza podstawowego zapamiętaj], więc w tym przypadku, jeśli wywołamy save()lub persist()metodę, to wstawi rekord do baza danych normalnie Ale słyszę, że save()metoda może zwrócić tę wartość identyfikatora klucza podstawowego, która jest generowana przez hibernację i możemy to zobaczyć przez

long s = session.save(k);

W tym samym przypadku persist()klient nigdy nie zwróci żadnej wartości.

Hari Krishna
źródło
5

Znalazłem dobry przykład pokazujący różnice między wszystkimi metodami hibernacji:

http://www.journaldev.com/3481/hibernate-session-merge-vs-update-save-saveorupdate-persist-example

W skrócie, zgodnie z powyższym linkiem:

zapisać()

  • Możemy wywołać tę metodę poza transakcją. Jeśli użyjemy tego bez transakcji i będziemy mieć kaskadę między jednostkami, tylko podstawowy podmiot zostanie zapisany, chyba że opróżnimy sesję.
  • Jeśli więc istnieją inne obiekty zmapowane z obiektu głównego, zostaną one zapisane w momencie dokonywania transakcji lub w momencie opróżnienia sesji.

trwać()

  • Jest podobny do korzystania z save () w transakcji, więc jest bezpieczny i dba o wszystkie obiekty kaskadowe.

saveOrUpdate ()

  • Może być używany z transakcją lub bez, i podobnie jak save (), jeśli jest używany bez transakcji, zmapowane jednostki nie zostaną zapisane, chyba że opróżnimy sesję.

  • Wyniki wstawiania lub aktualizowania zapytań na podstawie dostarczonych danych. Jeśli dane są obecne w bazie danych, wykonywane jest zapytanie o aktualizację.

aktualizacja()

  • Aktualizacja hibernacji powinna być używana tam, gdzie wiemy, że aktualizujemy tylko informacje o encji. Ta operacja dodaje obiekt encji do trwałego kontekstu, a dalsze zmiany są śledzone i zapisywane po zatwierdzeniu transakcji.
  • Dlatego nawet po wywołaniu aktualizacji, jeśli ustawimy jakieś wartości w encji, zostaną one zaktualizowane po zatwierdzeniu transakcji.

łączyć()

  • Hibernacji można użyć do zaktualizowania istniejących wartości, jednak ta metoda tworzy kopię z obiektu obiektu przesłanego i zwraca ją. Zwrócony obiekt jest częścią trwałego kontekstu i jest śledzony pod kątem wszelkich zmian, przekazany obiekt nie jest śledzony. Jest to główna różnica w merge () od wszystkich innych metod.

Również dla praktycznych przykładów ich wszystkich, proszę odwołać się do linku, o którym wspomniałem powyżej, zawiera przykłady wszystkich tych różnych metod.

Z umysłu
źródło
3

Jak wyjaśniłem w tym artykule , powinieneś preferować metody JPA przez większość czasu i updatezadania przetwarzania wsadowego.

WZP lub Hibernacja może znajdować się w jednym z następujących czterech stanów:

  • Przejściowy (nowy)
  • Zarządzane (trwałe)
  • Oderwany
  • Usunięte (usunięte)

Przejście z jednego stanu do drugiego odbywa się za pomocą metod EntityManager lub Session.

Na przykład WZP EntityManagerzapewnia następujące metody zmiany stanu encji.

wprowadź opis zdjęcia tutaj

Hibernacji Sessionrealizuje wszystkie JPA EntityManagermetody i zawiera kilka dodatkowych metod, takich jak zmiana stanu podmiotu save, saveOrUpdatei update.

wprowadź opis zdjęcia tutaj

Trwać

Aby zmienić stan bytu z Przejściowy (Nowy) na Zarządzany (Utrwalony), możemy użyć persistmetody oferowanej przez JPA, EntityManagerktóra jest również dziedziczona przez Hibernację Session.

persistSposób wyzwala PersistEventktóre są przetwarzane przez DefaultPersistEventListenerdetektor zdarzeń hibernacji.

Dlatego podczas wykonywania następującego przypadku testowego:

doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    LOGGER.info(
        "Persisting the Book entity with the id: {}", 
        book.getId()
    );
});

Hibernacja generuje następujące instrukcje SQL:

CALL NEXT VALUE FOR hibernate_sequence

-- Persisting the Book entity with the id: 1

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

Zauważ, że idprzypisuje się to przed dołączeniem Bookbytu do bieżącego kontekstu trwałości. Jest to konieczne, ponieważ jednostki zarządzane są przechowywane w Mapstrukturze, w której klucz jest tworzony przez typ jednostki i jej identyfikator, a wartością jest odwołanie do jednostki. To jest powód, dla którego JPA EntityManageri HibernacjaSession są znane jako pamięć podręczna pierwszego poziomu.

Podczas połączenia persistjednostka jest dołączona tylko do aktualnie działającego kontekstu trwałości, a WSTAW można odłożyć do momentuflush zostanie wywołane.

Jedynym wyjątkiem jest generator TOŻSAMOŚCI, który natychmiast uruchamia WSTAW, ponieważ jest to jedyny sposób, w jaki może uzyskać identyfikator jednostki. Z tego powodu Hibernacja nie może wsadowo wstawiać wstawek dla encji korzystających z generatora TOŻSAMOŚCI. Więcej informacji na ten temat można znaleźć w tym artykule .

Zapisać

saveMetoda specyficzna dla Hibernacji poprzedza JPA i jest dostępna od początku projektu Hibernacja.

saveSposób wyzwala SaveOrUpdateEventktóre są przetwarzane przez DefaultSaveOrUpdateEventListenerdetektor zdarzeń hibernacji. Dlatego savemetoda jest równoważna z metodami updatei saveOrUpdate.

Aby zobaczyć, jak savedziała metoda, rozważ następujący przypadek testowy:

doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);

    Long id = (Long) session.save(book);

    LOGGER.info(
        "Saving the Book entity with the id: {}", 
        id
    );
});

Po uruchomieniu powyższego przypadku testowego Hibernacja generuje następujące instrukcje SQL:

CALL NEXT VALUE FOR hibernate_sequence

-- Saving the Book entity with the id: 1

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

Jak widać, wynik jest identyczny z persistwywołaniem metody. Jednak w przeciwieństwie persistdosave metody zwraca identyfikator jednostki.

Aby uzyskać więcej informacji, sprawdź ten artykuł .

Aktualizacja

updateMetoda specyficzna dla hibernacji ma na celu ominięcie mechanizmu sprawdzania brudności i wymuszenie aktualizacji encji w czasie opróżniania.

updateSposób wyzwala SaveOrUpdateEventktóre są przetwarzane przez DefaultSaveOrUpdateEventListenerdetektor zdarzeń hibernacji. Dlatego updatemetoda jest równoważna z metodami savei saveOrUpdate.

Aby zobaczyć, jak updatedziała metoda, rozważ następujący przykład, który utrzymuje Bookjednostkę w jednej transakcji, a następnie modyfikuje ją, gdy jednostka jest w stanie odłączonym, i wymusza aktualizację SQL za pomocą updatewywołania metody.

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

LOGGER.info("Modifying the Book entity");

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);

    session.update(_book);

    LOGGER.info("Updating the Book entity");
});

Podczas wykonywania powyższego przypadku testowego Hibernacja generuje następujące instrukcje SQL:

CALL NEXT VALUE FOR hibernate_sequence

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

-- Modifying the Book entity
-- Updating the Book entity

UPDATE 
    book 
SET 
    author = 'Vlad Mihalcea', 
    isbn = '978-9730228236', 
    title = 'High-Performance Java Persistence, 2nd edition'
WHERE 
    id = 1

Zauważ, że UPDATEoperacja jest wykonywana podczas opróżniania kontekstu trwałego, tuż przed zatwierdzeniem, i dlatego Updating the Book entitywiadomość jest najpierw logowana.

Używanie w @SelectBeforeUpdatecelu uniknięcia niepotrzebnych aktualizacji

Teraz UPDATE zawsze będzie wykonywane, nawet jeśli jednostka nie została zmieniona w stanie odłączonym. Aby temu zapobiec, możesz użyć @SelectBeforeUpdateadnotacji Hibernacja, która uruchomi SELECTpobrane polecenieloaded state która zostanie następnie wykorzystana przez mechanizm sprawdzania nieczystości.

Jeśli więc dodamy adnotację do Bookjednostki za pomocą @SelectBeforeUpdateadnotacji:

@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {

    //Code omitted for brevity
}

I wykonaj następujący przypadek testowy:

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);

    session.update(_book);
});

Hibernacja wykonuje następujące instrukcje SQL:

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

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

Zauważ, że tym razem nie jest UPDATEwykonywane, ponieważ mechanizm hibernacji „brudnego sprawdzania” wykrył, że jednostka nie została zmodyfikowana.

SaveOrUpdate

saveOrUpdateMetoda specyficzna dla hibernacji jest tylko aliasem dla savei update.

saveOrUpdateSposób wyzwala SaveOrUpdateEventktóre są przetwarzane przez DefaultSaveOrUpdateEventListenerdetektor zdarzeń hibernacji. Dlatego updatemetoda jest równoważna z metodami savei saveOrUpdate.

Teraz możesz użyć, saveOrUpdategdy chcesz utrwalić byt lub wymusić element, UPDATEjak pokazano 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");

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(_book);
});

Uważaj na NonUniqueObjectException

Jednym z problemów, który może wystąpić z save, updatei saveOrUpdatejest, jeśli kontekst trwałości zawiera już odwołanie do jednostki o tym samym identyfikatorze i tego samego typu, jak 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 przechowywać 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)

Łączyć

Aby tego uniknąć NonUniqueObjectException, musisz użyć mergemetody oferowanej przez JPA EntityManageri dziedziczonej również przez Hibernację Session.

Jak wyjaśniono w tym artykule , mergepobiera nową migawkę encji z bazy danych, jeśli w kontekście trwałości nie znaleziono odwołania do encji, i kopiuje stan odłączonej encji przekazanej do mergemetody.

mergeSposób wyzwala MergeEventktóre są przetwarzane przez DefaultMergeEventListenerdetektor zdarzeń hibernacji.

Aby zobaczyć, jak mergedziała metoda, rozważ następujący przykład, który utrzymuje Bookbyt w jednej transakcji, a następnie modyfikuje go, gdy jest on w stanie odłączonym, i przekazuje odłączony byt mergew podsekwencji Kontekst trwałości.

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

LOGGER.info("Modifying the Book entity");

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

doInJPA(entityManager -> {
    Book book = entityManager.merge(_book);

    LOGGER.info("Merging the Book entity");

    assertFalse(book == _book);
});

Podczas uruchamiania powyższego przypadku testowego Hibernacja wykonała następujące instrukcje SQL:

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

-- Modifying the Book entity

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

Zauważ, że zwrócone przez encję odwołanie do encji mergejest inne niż odłączone, które przekazaliśmy do mergemetody.

Teraz, chociaż powinieneś preferować używanie JPA mergepodczas kopiowania stanu odłączonej jednostki, dodatkowe SELECTmogą być problematyczne podczas wykonywania zadania przetwarzania wsadowego.

Z tego powodu powinieneś preferować używanie, updategdy masz pewność, że żadne odwołanie do encji nie jest już dołączone do aktualnie działającego kontekstu trwałości i że odłączony byt został zmodyfikowany.

Więcej informacji na ten temat można znaleźć w tym artykule .

Wniosek

Aby utrwalić jednostkę, należy użyć persistmetody JPA . Aby skopiować stan odłączonej jednostki, mergepowinno być preferowane. Ta updatemetoda jest przydatna tylko do zadań przetwarzania wsadowego. saveI saveOrUpdateto tylko aliasy updatei nie należy ich używać prawdopodobnie w ogóle.

Niektórzy programiści dzwonią savenawet wtedy, gdy jednostka jest już zarządzana, ale jest to błąd i wyzwala zdarzenie nadmiarowe, ponieważ dla jednostek zarządzanych UPDATE jest automatycznie obsługiwana w czasie opróżniania kontekstu Persistence.

Aby uzyskać więcej informacji, sprawdź ten artykuł .

Vlad Mihalcea
źródło
2

Należy pamiętać, że jeśli wywołasz aktualizację na odłączonym obiekcie, zawsze nastąpi aktualizacja w bazie danych, niezależnie od tego, czy zmieniłeś obiekt, czy nie. Jeśli nie jest to, czego chcesz, powinieneś użyć Session.lock () z LockMode.None.

Powinieneś wywołać aktualizację tylko wtedy, gdy obiekt został zmieniony poza zakresem bieżącej sesji (w trybie odłączonym).

Bernardn
źródło
0

Żadna z powyższych odpowiedzi nie jest kompletna. Chociaż odpowiedź Leo Theobalda wygląda na najbliższą.

Podstawową kwestią jest to, jak hibernacja radzi sobie ze stanami bytów i jak sobie z nimi radzi, gdy następuje zmiana stanu. Wszystko musi być postrzegane również w odniesieniu do flushów i zatwierdzeń, które wszyscy wydają się całkowicie ignorować.

NIGDY NIE UŻYWAJ ZAPISANEJ METODY HIBERNATU. ZAPOMNIJ, ŻE JESZCZE ISTNIEJE W HIBERNATIE!

Trwać

Jak wszyscy wyjaśnili, Persist zasadniczo zmienia byt ze stanu „przejściowego” w stan „zarządzanego”. W tym momencie slush lub zatwierdzenie może utworzyć instrukcję insert. Ale jednostka nadal pozostanie w stanie „Zarządzanym”. To się nie zmienia wraz z kolorami.

W tym momencie, jeśli „uporczywie” ponownie, nie będzie żadnych zmian. I nie będzie już żadnych zapisów, jeśli spróbujemy utrzymać uparty byt.

Zabawa zaczyna się, gdy próbujemy eksmitować istotę.

Eksmisja to specjalna funkcja Hibernacji, która zmieni byt z „Zarządzanego” na „Odłączony”. Nie możemy wywoływać trwałej odłączonej jednostki. Jeśli to zrobimy, Hibernacja zgłosi wyjątek, a cała transakcja zostanie wycofana po zatwierdzeniu.

Scal vs Aktualizacja

Są to 2 ciekawe funkcje, które wykonują różne czynności, gdy są obsługiwane na różne sposoby. Obaj próbują zmienić byt ze stanu „Odłączony” do stanu „Zarządzany”. Ale robi to inaczej.

Zrozum, że Odłączony oznacza rodzaj „offline”. i zarządzany oznacza stan „Online”.

Obserwuj poniższy kod:

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();

    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    ses1.merge(entity);

    ses1.delete(entity);

    tx1.commit();

Kiedy to robisz? Jak myślisz, co się stanie? Jeśli powiedziałeś, że spowoduje to wyjątek, masz rację. Spowoduje to wyjątek, ponieważ scalanie działało na obiekcie encji, który jest stanem odłączonym. Ale to nie zmienia stanu obiektu.

Za sceną scalenie wywoła wybrane zapytanie i zasadniczo zwraca kopię encji, która jest w stanie dołączonym. Obserwuj poniższy kod:

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();
    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    HibEntity copied = (HibEntity)ses1.merge(entity);
    ses1.delete(copied);

    tx1.commit();

Powyższa próbka działa, ponieważ scalanie wprowadziło nowy byt w kontekście, który jest w stanie trwałym.

Po zastosowaniu z aktualizacją to samo działa dobrze, ponieważ aktualizacja nie przynosi kopii encji takiej jak scalanie.

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();

    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    ses1.update(entity);

    ses1.delete(entity);

    tx1.commit();

Jednocześnie w śledzeniu debugowania widzimy, że aktualizacja nie wywołała zapytania SQL typu select like merge.

usunąć

W powyższym przykładzie użyłem delete bez mówienia o delete. Usuń zasadniczo spowoduje przejście jednostki ze stanu zarządzanego do stanu „usuniętego”. A po opróżnieniu lub zatwierdzeniu wyda polecenie usuwania do zapisania.

Możliwe jest jednak przywrócenie jednostki do stanu „zarządzanego” ze stanu „usuniętego” za pomocą metody persist.

Mam nadzieję, że powyższe wyjaśnienie wyjaśniło wszelkie wątpliwości.

Piwo imbirowe
źródło