Sposób na JPA i LazyInitializationException
Temat lazy loadingu (leniwego ładowania?) przewijał się ostatnio na polskich blogach javowych. Pisał o nim Mateusz Zięba, o wydajności takiego rozwiązania wspominał też w świetnym wpisie Sławek Sobótka. Czy można coś jeszcze dodać? Myślę, że niewiele, ale spróbujmy skupić się na jednej rzeczy, a mianowicie na zamkniętej sesji JPA i sposobie, jak sobie w takim wypadku radzić gdy mamy jeszcze w naszych zależnościach Spring Framework oraz działamy w środowisku webowym.
Problem jest dość częsty (przykładowy "stack trace" poniżej) i jako taki ma też rozwiązanie, opisane zresztą w artykule Open Session in View na stronach naszego ulubionego ORM-a. W skrócie całe zamieszanie związane jest z tym, że nie zawsze w naszej warstwie logiki biznesowej operujemy na obiektach, które potem chcemy wyświetlić, a zatem ORM bardzo słusznie nie pobiera ich z bazy danych. Jednak gdy dochodzi do wyświetlenia listy w widoku, zamiast np. listy ulubionych kolorów użytkownika dostajemy błąd numer 500, a w logach straszy nas LazyInitializationException (oczywiście w przypadku H., bo specyfikacja JPA o takiej sytuacji w ogóle nie wspomina). Sesja jest już bowiem zamknięta i nie wiadomo, skąd te kolory wziąć, a całej bazy przecież nie pociągnęliśmy wcześniej, prawda? :). Z przyczyn oczywistych zmiana fetch na FetchType.EAGER raczej nie wchodzi w grę.
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: net.miracki.Test.categories, no session or session was closed
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException (AbstractPersistentCollection.java:380)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected (AbstractPersistentCollection.java:372)
at org.hibernate.collection.AbstractPersistentCollection.readSize (AbstractPersistentCollection.java:119)
at org.hibernate.collection.PersistentSet.size (PersistentSet.java:162)
...
Problem jest, rozwiązanie dla Hibernate'a też. Niestety niewielki kłopot pojawia się, kiedy chcemy być standardowi i używamy Hibernate (albo czegokolwiek innego) poprzez właśnie JPA. Jak to zazwyczaj bywa, Spring Framework ma na takie zachowanie gotową i prostą receptę. W zasadzie wpis mógłby się kończyć na poniższym kawałku pliku web.xml:
JpaFilter
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
JpaFilter
/*
Dzięki temu filtrowi nie powinniśmy więcej spotkać wyjątku związanego z "lazy loadingiem". Powyższy filtr istnieje zresztą też w wersji dla zwykłego Hibernate (jako klasa OpenSessionInViewFilter).
Abstrahując już od głównego tematu, ORM-y to narzędzia, które przed użyciem wypada przynajmniej choć trochę poznać, a do tego tutorial jak zrobić "Hello World" nie wystarczy. Brałem udział w projekcie, który z dobrodziejstw opisanych filtrów nie korzystał, przez co niektóre metody w warstwie logiki biznesowej posiadały argumenty typu boolean o nazwach attachCategories, attachCountries itp. Nie wiem, czy byłbym jednak w stanie wymienić wszystkie zasady dobrego kodowania, które w ten sposób zostały złamane ;). Ale jeśli chodzi o ORM-y, to prawie na samej górze mojej prywatnej listy idiotycznych rozwiązań jest adnotacja @Transient dla obiektów połączona z mapowaniem kluczy obcych do Longów z @Column, bo "pociągniesz całą bazę, ORM-y to zło, ale niech już zostaną". Na całe szczęście takie podejście bardzo szybko minęło wraz z lekturą manuala...





