Samozarządzające się encje – persist vs. merge

Chcąc zaimplementować prostą wersję wzorca ActiveRecord należy uzbroić się w cierpliwość. Szczególnie jeżeli chce się to zrobić z wykorzystaniem JSR-330 i Guice.

Jednym z problemów przed jakimi staje osoba tworząca takie rozwiązanie jest decyzja czy encje powinny być zarządzane bardziej przez Guice czy bardziej przez JPA. Kluczowym elementem, który trzeba zrozumieć by dokonać wyboru jest różnica pomiędzy metodami persist i merge.

Metoda persist

Służy do utrwalania obiektów. Jej wywołanie powoduje dołączenie przekazanego obiektu do zbioru obiektów zarządzanych oraz wywołanie INSERT w momencie zakończenia transakcji (albo wywołania flush). Jeżeli istnieje już encja o takim samym identyfikatorze dostaniemy wyjątek.

Metoda merge

Służy do synchronizowania stanu obiektu w kontekście utrwalania. W przeciwieństwie do poprzedniej metody encja przekazana w argumencie nie jest dołączana do zbioru obiektów zarządzanych. Jest ona kopiowana, kopia trafia pod zarząd EntityManagera i jest zwracana. W momencie zakończenia transakcji wywoływany jest INSERT albo UPDATE w zależności czy istnieje rekord o danym identyfikatorze w bazie danych.

Gdzie leży pies pogrzebany

Problem leży nie tyle co w samym wywołaniu metody, ale w tym, który obiekt jest zarządzany.

Przypadek prosty

Wywołujemy persist(this). Nasz obiekt staje się obiektem zarządzanym. Jeżeli był utworzony przez injector to jesteśmy w dobrej sytuacji. Guice i JPA nie pogryzły się o to kto ma zarządzać danym obiektem.

Przypadek trochę bardziej skomplikowany

Wywołujemy merge(this) powodujące INSERT. W tym przypadku nasz obiekt nie będzie zarządzany przez EntityManagera. Kopia wykonana w wyniku wywołania metody nie będzie miała wstrzykniętych zależności. Możemy to zignorować i dalej działać na naszym obiekcie. Kolejne wywołanie…

Przypadek jeszcze bardziej skomplikowany

Wywołujemy merge(this) powodujące UPDATE. W tym przypadku można postąpić jak wyżej czyli zignorować utworzony obiekt. Kosztuje to oczywiście dodatkowy SELECT na każde kolejne wywołanie merge(this).

Przypadek najbardziej bardziej skomplikowany

Coś na co zwrócił uwagę Wojtek Ebertowski w komentarzu do poprzedniego wpisu. Jeżeli wywołamy find i pozwolimy silnikowi JPA utworzyć obiekt to nie będzie miał on ustawionych pól wstrzykiwanych przez DI. Podobnie w druga stronę można co prawda utworzyć obiekt za pomocą Assisted Injection ustawiając identyfikator za pomocą danych od użytkownika albo tworząc go za pomocą konstruktora kopiującego. W takim przypadku obiekt wypada z pod pieczy JPA.

Alternatywy

Jak widać nie można w prosty sposób zbudować obiektu, który jednocześnie będzie zarządzany przez JPA i będzie miał wstrzyknięte pola. Jedynym przypadkiem kiedy to działa jest sytuacja kiedy tworzony jest nowy unikalny obiekt.

Jednym z rozwiązań może być przepisywanie pól. W takim przypadku wszystkie pola, które są wstrzykiwane powinny zostać oznaczone jako transient i następnie ręcznie ustawiane w obiekcie zarządzanym przez JPA. Źródłem będzie obiekt zarządzany przez Guice.
Innym rozwiązaniem jest ignorowanie obiektów zarządzanych przez JPA lecz ceną jest dodatkowy SELECT na każdą operację.
Jeszcze innym rozwiązaniem jest rezygnacja z Assisted Injection na rzecz samodzielnego tworzenia providera opartego o JPA. W tym przypadku problemem może okazać się duża ilość kodu (jeden provider na każdą klasę encji).
Ostatnim rozwiązaniem, którego nie używałem nigdy w życiu jest zabawa z podmianą obiektów locie. Coś na wzór JRebel czy OSGi.

2 myśli na temat “Samozarządzające się encje – persist vs. merge

  1. A nie ma w JPA czegoś a’la PostCreationVisitor? Moglibyśmy ustawić swój, który po każdorazowym stworzeniu obiektu przez JPA zajrzałby robiąc
    injector.requestInjection(newlyCreatedEntity);

  2. Są listenery. Jest też krótka pamięć Kozła… który zapomniał, że ma odpowiednie narzędzie w łapkach.

Napisz odpowiedź

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax