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.