Sposób na JPA i LazyInitializationException

Wpis dodany 17 sierpnia 2009 o godz. 22:37:30 w kategorii Hibernate, Java, JPA, Spring, Techblog.

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...

Płynne interfejsy

Wpis dodany 11 sierpnia 2009 o godz. 21:05:24 w kategorii Java, JPA, Mockito, Techblog.

Krótki urlop (który upłynął pod znakiem słońca, koloru różowego i Panthenolu w sprayu) szybko się skończył, wypada więc w tym gorącym czasie (chociaż bardziej ze względu na kupno Spring Source przez VMware aniżeli przez aktualną pogodę) powrócić do świata Javy. Na tapetę wędrują zatem płynne interfejsy.

Mam nadzieję, że za tytułowe tłumaczenie "fluent interfaces" nikt mnie na stosie palił nie będzie, chodzi wszakże o całkiem fajny styl programowania, który dodatkowo ostatnio jakby staje się coraz popularniejszy. Określenia "fluent interface" użył chyba po raz pierwszy sam Martin Fowler, a obecnie dorobiło się ono nawet swojego wpisu w Wikipedii. Rzecz nie jest w żadnym stopniu nowa ani rewolucyjna, w dodatku od razu widać powiązanie z "method chaining". Na pewno każdy używał cout z C++, a zatem mniej lub bardziej świadomie korzystał też ze wspomnianej techniki, która teraz wraz ze spopularyzowaniem DSL-i przeżywa swoją drugą młodość, przynajmniej jeśli chodzi o świat Javy.

Odbiegając na chwilę lekko od tematu można czy nawet wypada wspomnieć o opisanych w Effective Java builderach (budowniczych). Przy klasie, która ma bardzo wiele atrybutów, bezpośrednie używanie konstruktorów z tymi parametrami nie jest najlepszym pomysłem. W pewnym momencie bowiem możemy stracić orientację, czy true na dwudziestej szóstej pozycji odnosi się do zablokowania użytkownika, czy do faktu posiadania uprawnień administracyjnych. Trzydzieści setterów jest natomiast obarczone ryzykiem, że gdzieś około piętnastej pozycji ktoś wywoła nam kod, który będzie potrzebował już w pełni stworzonego obiektu. Buildery takie są lekarstwem na wspomniane właśnie problemy, a wykorzystują one "method chaining". Do ich generowania pod Eclipse istnieje odpowiednia wtyczka, całkiem zresztą znośna w użyciu, choć do ideału jej nieco brakuje.

Ok, co z tymi płynnymi interfejsami i czemu warto tracić na coś takiego w ogóle czas? Otóż jest tak, że większość czasu spędzamy czytając kod, więc powinno nam zależeć, aby ten kod był po prostu czytelny. A teraz weźmy na przykład standardowe API Javy do obsługi dat - czy poniższy kod (wzięty zresztą z opisu biblioteki Time and Money Erica Evansa) jest przejrzysty i łatwo zrozumiały na pierwszy rzut oka?

Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(TimeZone.getTimeZone("Universal");
calendar.set(Calendar.DATE, 5);
calendar.set(Calendar.MONTH, 3 - 1);
calendar.set(Calendar.YEAR, 2004);
Date march5_2004 = calendar.getTime();

Na całe szczęście w przypadku dat możemy użyć chociażby Joda Time, natomiast płynne interfejsy znalazły swój przyczółek w bibliotekach do mockowania, w tym w mojej ulubionej, czyli w Mockito (w poniższym przykładzie w parze z projektem Fest). Czy ktoś odważy się powiedzieć, że taki test nie jest naprawdę piękny? ;)

    @Test
    public void shouldGenerateValidLoginFromEmailForUser() {
        // given
        String name = "test";
        User user = new User.Builder().mail(name + "@example.com").build();
        given(userDAO.update(user)).willReturn(user);

        // when
        userLogicBean.generateLoginAndSave(user);

        // then
        assertThat(user.getLogin()).hasSize(User.Length.LOGIN).startsWith(name);
    }

Ale mocki i asercje to nie jedyne miejsce dla płynnych interfejsów. Wykorzystano je projektując chociażby Google Collections (warto popatrzeć na przykład na klasę MapMaker), a biblioteka Lambdaj pozwala pozbyć się niewiele mówiących pętli połączonych z mnóstwem ifów na rzecz tego typu konstrukcji:

List<Person> oldFriends = filter(having(on(Person.class).getAge(), greaterThan(30)), meAndMyFriends);

Albo weźmy kolejny przykład - JPA. O ile JPA w pierwszej wersji było nieco wybrakowane, o tyle warto poczytać, co pisze o języku zapytań w JPA 2.0 Linda DeMichiel. Swoją drogą nawet Doctrine ORM w php było szybsze, jeśli chodzi o wykorzystanie "method chainingu" w zapytaniach.

Widać więc, że nawet świat Javy ewoluuje, starając się niwelować niedostatki samego języka bądź też niedoskonałego API. Oczywiście wszystkiego zrobić się nie da, problemem są metody zwracające void, nie można też wykryć w czasie wykonywania typu sparametryzowanej kolekcji. Są to jednak jedynie szczegóły, które można spróbować obejść (np. w Mockito dla metod zwracających void jest szereg metod do*, które zwykle pozwalają nam osiągnąć to, co chcemy).