środa, 9 listopada 2011

Hibernate L1 cache a cachowanie zapytań.

Niby wszyscy wiedzą jak działa działa L1 cache w Hibernate. W ramach jednej sesji hibernata, jeśli wczytamy sobie jakiś obiekt za pomocą np. funkcji .get(), to każde kolejne wywołanie tej funkcji nie odpytuje już bezpośrednio bazy danych, tylko zwraca ten obiekt właśnie z sesji. Pytanie, czy jesteśmy świadomi wszystkich konsekwencji takiego podejścia?

Proponuje zagadkę.
W bazie jest 2 użytkowników z tego samego miasta: Andrzej i Wojtek. Metoda getAllUsersFromCity() tworzy odpowiedni obiekt Criteria i zwraca listę użytkowników z danego miasta. Przyjmijmy, że kod funkcji testCache() wykonywany jest w ramach jednej sesji hibernata:

public void testCache(){
    printUsers();
    //w tym momecie ktoś inny edytuje jednego z użytkowników,
    //w zupełnie innej sesji lub bezpośrednio na bazie,
    //zmienia nazwe z Andrzej na Bartek
    printUsers();
}

public void printUsers(){
    List<User> users = dao.getAllUsersFromCity("city");
    foreach(User user : users){
        System.out.println(user.getName());
    }
}

Co zostanie wyświetlone na ekranie?
a)AndrzejWojtekAndrzejWojtek
b)AndrzejWojtekBartekWojtek

4 komentarze:

  1. To normalne, że jak pobierzesz rekord za pomocą get/load to następne wywołanie zwraca rekord z cache bez znaczenia czy zmienił się on w bazie czy nie....
    Jeżeli chodzi o cache'owanie zapytań to wyobraź sobie, że to działa "idealnie" czyli po zmianie Andrzej na Bartek metoda getAllUsersFromCity zwraca AndrzejWojtekBartekWojtek co też jest do dupy bo może się okazać ze w tej samej sesji cześć algorytmu podejmie decyzje na podstawie AndrzejWojtekAndrzejWojtek a druga cześć na podstawie AndrzejWojtekBartekWojtek
    Podsumowując wołaj metode getAllUsersFromCity raz i zadbaj o concurrency

    OdpowiedzUsuń
  2. Mirku, czy sprawdzałeś to doświadczalnie? Bo ja wczoraj poświęciłem na to sporo czasu i do wczoraj wydawało mi się, że drugie wywołanie metody powinno zwrócić, tak jak napisałeś, już świeże dane (czyli BartekWojtek) - ale tak nie jest... W obrębie tej samej sesji dostaje za każdym razem, AndrzejWojtek i AndrzejWojtek.

    Jest jeszcze jeden aspekt, który może mięć na to wpływ (choć szczerze wątpię). Testy przeprowadzałem na aplikacji, która wykorzystuje Envers'a. I szperając po necie natknąłem się na taki wpis:

    http://www.java-forums.org/database/45267-jtable-refresh-problem-hibernate-session.html

    - drugi komentarz.

    Jakoś nie chce mi się wierzyć, żeby Envers miał tu aż taki wpływ, ale tego przyznam szczerze nie zbadałem.

    OdpowiedzUsuń
  3. Zależy jakie masz zapytanie ja nigdy nie wyciągam zapytaniem obiektów tylko projekcje...jeśli wyciągniesz obiekty to "sprytny" hibernate stwierdzi ze wynikiem zapytania są obiekty o id 1,2,3 które ma już w L1 więc zamiast danych które zwróciło zapytanie da Ci obiekty z cache...dla mnie dziwne jest ze pomimo pola VERSION i jego zmiany hibernate nadal ma to gdzieś ...
    Z wielką siłą (hibernate) wiąże się wielka odpowiedzialność :)

    OdpowiedzUsuń
  4. Dla @Version nie sprawdzałem, ale skoro mówisz, że i tak nic to nie zmienia... Mimo wszystko takie podejście ma jakiś sens. Załóżmy, że user Andrzej jest już w sesji. Z poziomu bazy zmieniamy na Bartek. Robimy select wszystkich userów. I gdyby ten select miał zwrócić świeże dane, to nagle w tej samej sesji Andrzej stałby się Bartkiem, co chyba byłoby bardziej problematyczne...

    Trzeba po prostu być świadomym jak działa L1.

    Projekcje są rozwiązaniem, ale nie uniwersalnym. Poza tym wygodniej pobiera się listę danych obiektów.

    OdpowiedzUsuń