Refaktoryzacja Conditional JPQL do Criteria API w JPA 2.1

W projektach JEE, które wykorzystują mechanizmy ORM czasem zdarza się, że rozmiary encji są trochę większe niż byśmy tego chcieli. Przykładowo:

Oczywiście, można tutaj pomyśleć o korzystaniu z pól typu @Embeddable, ale są przypadki, w których nie mamy możliwości zmian w klasach encji. Jak mogłoby wyglądać repozytorium dla takiej encji? Zdefiniujmy najpierw abstrakcyjną klasę dla repozytoriów:

Jak widać, jest to dosyć typowe rozwiązanie abstrakcyjnego CRUDa. Na razie zero problemów. Zobaczmy jednak jak możnaby zdefiniować EJB repozytorium dla konkretnej (naszej) encji. Zakładamy, że chcemy mieć w pełni elastycznego findera, czyli możliwość wyboru parametrów, które posłużą do znalezienia odpowiedniego rekordu lub wielu rekordów. Na potrzeby projektu został także stworzony obiekt ScheduleResult zawierający status wyszukiwania (np. OK lub SCHEDULE_NOT_FOUND) oraz encję czy też listę encji (obiektów-rekordów). Podejście pierwsze:

Niestety, taka drabinka ifów to straszny potwór, nie tylko z wyglądu. Jak widać, nasza drabinka wymusza, aby wartość null niektórych z parametrów wpływała na wynik zapytania,
a niektórych nie. Wyobraźmy sobie teraz, że dostajemy nowe ograniczenia dla zapytania, lub musimy wygenerować zapytanie na podstawie trochę innej logiki niż powyższa (chodzi głównie o wymuszanie nullowanych kolumn). Będzie się to oczywiście sprowadzać do napisania kolejnej metody o bliźniaczo podobnej nazwie i super długim zestawie parametrów wejściowych. Oczywiście, zasada open-closed nie jest tutaj zachowana w najmniejszym nawet stopniu. Na sam początek spróbujmy się pozbyć super długiej listy parametrów metody:

Ok, poszło całkiem łatwo. Jednak główny problem nie został rozwiązany. Drabinka nadal istnieje (chociaż teraz tylko jedna), jednak zamiast bezpośrednio z parametru metody korzystamy z odpowiednich kluczy mapy. Przykładowo:

A zamiast dolnej drabinki mamy coś takiego:

Jak refaktoryzować? Na sam początek extract method oraz extract field:

Ok, teraz nasz finder na pewno będzie potrzebował o wiele mniej kodu żeby zadziałać. Jednak problem nie został zażegnany całkowicie. W tym momencie brniemy w warunkowane budowanie Stringu, co powinno być ostatecznością. Najlepszym sposobem na conditional query w JPA 2.1 jest Criteria API. Zobaczmy jak nasza metoda checkForEquality zmienia się. Wystarczy jeszcze tylko dorobić kilka pól do naszego repozytorium:

Teraz nasz finder będzie wyglądał mniej więcej tak:

Jak widać, zostało jeszcze kilka predykatów, które należałoby wrzucić do osobnych metod. Po takim zabiegu powstaje taka oto mniej więcej lista:

Całą listę, wraz z wcześniej utworzonymi polami, oczywiście przenosimy do AbstractCrudRepository. Dorzućmy do tego jeszcze dwie przydatne metody do pobierania odpowiednich typów zapytania (do pobierania rekordów oraz do liczenia rekordów):

Oraz inicjalizację pól, które dodaliśmy dla Criteria API:

I to by było na tyle. Myślę, że poszło całkiem sprawnie ;)

Just another developers blog.