Spring to obecnie najpopularniejszy framework w świecie Javy. Pozostawia on daleko w tyle całą konkurencję. Dlatego jego dobra znajomość, to podstawa pracy każdego developera. W tym artykule opisuję najczęściej popełniane błędy dotyczące tego frameworka.
Nieodpowiednie używanie transakcji
Brak @Transactional
Transakcje to jedna z najważniejszych rzeczy w aplikacji (jeśli oczywiście używacie transakcyjnej bazy danych). Każde zapytanie odbywa się w jakiejś transakcji. Jeśli zapomnisz dodać adnotację @Transactional
na odpowiednich metodach, to każde zapytanie wykona się w osobnej transakcji, co może prowadzić do niespójności danych. W większości przypadków, w prostych aplikacjach nie ma z tym problemu, ale często dzieję się tak, że zapisywane są całe grupy obiektów, które odpowiadają rekordom w różnych tabelach, wtedy o wiele łatwiej doprowadzić do niespójności danych.
Niezrozumienie mechanizmu transakcji
Kolejnym problemem związanym z transakcjami jest niezrozumienie tego, jak one działają w springu. Spring używa AOP (Aspect Oriented Programming) do mechanizmu transakcji. To znaczy, że kod, który piszesz jest dodatkowo opakowywany kodem aspektu, który dodaje obsługę transakcji. Aspekt transakcyjny działa tylko na publicznych metodach klas, więc adnotacja @Transactional
nie daje żadnego efektu na metodach prywatnych.
Kolejną istotną kwestią jest wywoływanie metod w obrębie jednego serwisu. Wywołanie metody publicznej oznaczonej adnotacją @Transactional
w innej metodzie tego serwisu, też nie da żadnego efektu. Inaczej mówiąc: wywołanie metody transakcyjnej musi pochodzić z zewnątrz serwisu (lub innego beana).
Niepotrzebnie ustawione parametry propagation
@Transactional(propagation = Propagation.REQUIRED)
– często spotkałem się z niepotrzebnym ustawianiem parametru propagation
np. Propagation.REQUIRED
– jest to opcja domyślna i nie jest wymagana – lub ustawianie Propagation.REQUIRES_NEW
tam, gdzie nie była potrzeba nowa transakcja. Warto zapoznać się ze wszystkimi poziomami propagacji transakcji przed ich użyciem. Jeśli nie masz pewności co jak działa, zapytaj kogoś, czy dobrze rozumiesz te rzeczy.
Używanie transakcji wraz z nietransakcyjnym api
Jeśli musisz łączyć transakcyjne operacje z nietransakcyjnymi, to należy umieści je w odpowiedniej kolejności. Najpierw wywołujemy transakcyjne operacje, wtedy w wypadku błędu transakcja zostanie z rollbackowana. Na koniec wywołujemy nietransakcyjne operacje. W wypadku błędu tej operacji, też transakcja zostanie z rollbakowana. Jeśli wszystko pójdzie dobrze, transakcja zostanie zacommitowana. Gdy umieścimy operacje w odwrotnej kolejności, nietransakcyjna operacja może zakończyć się powodzeniem (np. jakaś akcja w zewnętrznym systemie zostanie wykonana), a operacje na bazie danych mogą spowodować błąd i transakcja zostanie cofnięta, ale operacji nietransakcyjnej nie da się już cofnąć.
Transakcje ReadOnly
Adnotacja @Transactional
posiada parametr readOnly
. Przy odpowiednim ustawieniu @Transactional(readOnly = true)
transakcja powinna być tylko do odczytu. Jednak nie jest to prawda. Jest to tylko podpowiedź dla programisty (i systemu transakcji), że ta transakcja powinna tylko odczytywać dane. Jak zauważył w komentarzach RSWRC w Springu 5.1 zostało to poprawione. Dzięki czemu możemy w ten sposób zaoszczędzić trochę pamięci i czasu procesora. Więcej informacji na githubie #21494.
Niezrozumienie parametrów noRollbackFor i rollbackFor
Domyślnie transakcje są rollbackowane tylko dla UncheckedException, można to zachowanie zmienić poprzez użycie parametrów noRollbackFor
i rollbackFor
. noRollbackFor
pozwala podać wyjątek (uchecked), który nie z rollbackuje transakcji. Natomiast rollbackFor
pozwala podać, który wyjątek (checked) spowoduje rollback transakcji.
Niepotrzebne użycie adnotacji @Autowired
Adnotacja @Autowired służy do wstrzykiwania zależności, ale spring daje kila możliwości na robienie tego. Pierwsza: wstrzykiwanie poprzez pola, druga poprzez konstruktory. O ile w przypadku pól adnotacja ta jest wymagana, to od Springa 4.3 adnotacja ta nie jest wymagana na konstruktorze. Wstrzykując przez konstruktor nie musisz jej w ogóle używać.
Wstrzykiwanie zależności przez konstruktor jest najbardziej naturalnym sposobem ich wstrzykiwania. Dzięki takiemu podejściu, w łatwy sposób możesz później używać beanów springowych w testach. Pozwala to zmniejszyć uzależnienie się od specyfiki frameworka.
Brak adnotacji @Valid
Jeśli korzystamy z mechanizmu Bean Validation do sprawdzania poprawności danych wejściowych, konieczne jest umieszczenie adnotacji @Valid
na parametrze metody kontrolera. Inaczej mechanizm walidacji nie zostanie uruchomiony. Wielu programistów często o tym zapomina.
@PostMapping public ResponseEntity<Long> createPerson(@RequestBody @Valid PersonDto personDto) { //... }
Niepotrzebne Użycie „implements Serializable”
Często wielu programistów dodaje implements Serializable
do obiektów, które zwracają w REST API (zwracających jsona). Nie jest to potrzebne. Jackson
(domyślna biblioteka serializująca obiekty do jsona w springu) nie wymaga tego. Serializable
jest wymagana tylko wtedy, gdy w kontenerze serwletów, korzystasz z sesji http i zapisujesz w niej obiekty.
Podsumowanie
Opisałem najczęstsze błędy, z jakimi się spotykam podczas pracy. Jeśli znasz jeszcze jakieś ciekawe i często powtarzające się błędy, to zachęcam do podzielenia się nimi w komentarzach 😉
Źródła:
Readonly transakcje mają znaczenie np. w połączeniu Spring (od 5.1) i Hibernate. Wtedy readonly jest propagowane do hibernate i encje są rzeczywiście readonly, wyłączony jest dirty-checking i nie jest trzymana kopia encji do dirtycheckingu co oszczędza CPU i RAM. Opis: https://github.com/spring-projects/spring-framework/issues/21494
Dzięki za komentarz. Dobrze wiedzieć, że zostało to poprawione 😉 Poprawię w artykule.
Krótko i w sedno, bardzo trafny artykuł. Ze swojej strony bardzo często spotykam też wrzucanie do kontekstu Springowego wszystkich obiektów jak popadnie w klasach konfiguracyjnych.
Dzięki za komentarz Paweł. Zastanawiałem się, czy dodać to w artykule, ale trochę trudno jest określić co można a czego nie powinno się dodawać do kontekstu, to zawsze jest kwestia indywidualna, dlatego tego nie ma.
Ciekawy artykuł. Chętnie będę czytał kolejne tego typu ciekawostki lub uwagi 🙂
Cześć,
Mała uwaga redakcyjna: zamień „nie zrozumienie” na niezrozumienie.
Dzięki za komentarz. Poprawione.