Kiedy jako młody programista poznałem Hibernate byłem oczarowany. Idea przykrycia relacyjnej bazy danych zestawem obiektów wydawało mi się czymś mega innowacyjnym. W końcu programowaliśmy obiektowo nie? Były to czasy Spring’a 2.0, a ja zaczynałem jako młodszy programista pracę nad projektem dla jednej z firm udzielających pożyczek na niewielkie kwoty.
Architektura trójwarstwowa, SimpleFormController + Velocity na froncie oraz oczywiście obiekty DAO z zaprzęgniętym Hibernate. Masa obiektów encji z naćkanymi kolekcjami zagnieżdżonymi. Pamiętam słowa naszego ówczesnego architekta. „Tylko pamiętaj, fetchyType=Lazy, nie chcemy przecież zaciągać obiektów z bazy niepotrzebnie. Zostaną dociągnięte, gdy się do nich odwołasz”. Encje nierzadko leciały aż na widoki.
To Lazy nie jest jednak takie zarąbiste
Szybko okazało się, że świat nie jest taki kolorowy. Stało się to oczywiście za sprawą LazyInitializationException. Ale o co chodzi? – pomyślałem. A no o to, że te zagnieżdżone kolekcje, co to miały się magicznie zainicjować dopiero wtedy, gdy ich potrzebujemy, to jednak nie jest zawsze taki wspaniały pomysł. Istnieje coś takiego jak sesja Hibernate.
Niech pierwszy rzuci kamieniem ten, kto nie wpadł na pomysł aby pozbyć się tego problemu wykonując metodę size() na problematycznej kolekcji ;). Jednak surowy wzrok architekta i krótki wykład o problemie n+1, nie pozostawiły złudzeń. To był kiepski pomysł.
Na pewien czas świat stał się znowu piękny, gdy któryś ze starszych kolegów wspomniał o istnieniu OpenSessionInViewFilter :D.
Te zapytania pod spodem są jakieś pokraczne i jakoś ich dużo
Sielanka trwała do czasu, kiedy rozpoczęły się testy UAT. Aplikacja w wielu miejscach działała jakoś tak wolno. Okazało się, że Hibernate potrafi generować nie zawsze optymalne zapytania, a do tego potrafi generować je w ilości większej niż się zakładało. Wydajność aplikacji pozostawiała wiele do życzenia.
ORM jest super ale tylko jeśli wiesz jak on działa
Każda abstrakcja cieknie. A jeśli się nie wie co siedzi pod spodem, to cieknie jeszcze szybciej. To szczególnie dotyczy ORM’ów. Musisz dokładnie wiedzieć jak one działają, jeśli chcesz ich używać. Od samego początku musisz zwracać uwagę jakie zapytania zostaną finalnie wysłane do bazy danych. Jeśli myślisz, że stosując ORM’a zrzucasz z siebie ciężar projektowania optymalnych kwerend SQL, to muszę Cię rozczarować. Prędzej czy później takie podejście odbija się czkawką. Wydaje mi się, że najrozsądniej jest najpierw zaprojektować kwerendę SQL, a potem próbować zamodelować ją z użyciem klas ORMa. No chyba, że implementujemy prostego CRUDa.
Przy przetwarzaniu dużej ilości danych ORM się nie sprawdzi
Po raz pierwszy przekonałem się o tym, że ORM nie jest do wszystkiego, gdy musiałem zaimplementować import dużej ilości danych. Moja pierwsza implementacja oczywiście używała ORM’a. Dane ładowane były do jednej tabeli, żadnych relacji, struktura płaska. Co mogło pójść nie tak? Prawo Murphy’ego mówi „Jeśli coś może pójść nie tak, to na pewno tak się stanie”. Co więc okazało się problemem? Czas. Problemem był czas wykonania całej operacji. ORM generował za duży nakład czasowy. Nauczyłem się jednego. Operacje na dużych zbiorach danych trzeba implementować z użyciem native query. W Springu idealnie nadaje się do tego JdbcTemplate.
Istnieją alternatywy do ORM’a
Po raz pierwszy decyzję o nie używaniu ORM’a podjąłem, gdy zostałem zaangażowany w przepisanie systemu legacy. Był to system backend’owy, w którym znaczna część logiki biznesowej była zaszyta bardzo blisko, lub w samych kwerendach SQL. Zapytanie były sklejane ze String’ów i były tak złożone, że bardzo często nie mieściły się na ekranie monitora. Wiedzieliśmy już, że ORM się nie nada. Zaczęliśmy zatem rozglądać się za alternatywami. Wybór padł na QueryDLS. Framework sprawdził się w dużej części przypadków. Jednak złożoność zapytań okazała się w wielu przypadkach granicą nie do pokonania. Pojawianie się unii lub konieczność używania wewnątrz zapytań hint’ów specyficznych dla bazy Oracle spowodowały, że musieliśmy przygotowywać zapytania w czystym SQL’u. Postanowiliśmy używać JdbcTemplate i PreparedStatement. Po raz kolejny również wydajność okazała się znacznym problemem. QueryDSL był w wielu przypadkach zbyt wolny.
Konkluzja
W naszej branży mamy tendencję do podążania za modą. Próbujemy znajdować złote środki na rozwiązywanie wszystkich problemów. Lepiej jednak jest dobierać właściwe narzędzia do właściwych problemów. Jeśli uznasz, że nie potrzebujesz ORM’a i masz ku temu przesłanki, nie używaj go.