Dlaczego Hibernate Open Session in View uważa się za złą praktykę?

108

Jakich alternatywnych strategii używasz, aby uniknąć LazyLoadExceptions?

Rozumiem, że otwarta sesja w widoku ma problemy z:

  • Aplikacje warstwowe działające w różnych jvm
  • Transakcje są zatwierdzane dopiero na końcu i najprawdopodobniej chciałbyś wcześniej uzyskać wyniki.

Ale jeśli wiesz, że Twoja aplikacja działa na jednej maszynie wirtualnej, dlaczego nie złagodzić bólu, stosując strategię otwartej sesji w widoku?

HeDinges
źródło
12
Czy OSIV jest uważane za złą praktykę? Przez kogo?
Johannes Brodwall
4
I - jakie są dobre alternatywy?
David Rabinowitz
7
Ten spokój tekstu od twórców szwów: Jest kilka problemów z tą implementacją, z których najpoważniejszym jest to, że nigdy nie możemy być pewni, że transakcja zakończy się sukcesem, dopóki jej nie zatwierdzimy, ale do czasu, gdy transakcja „otwarta sesja w widoku” zostanie zatwierdzona, widok jest w pełni renderowany, a wyrenderowana odpowiedź mogła już zostać opróżniona do klienta. Jak możemy powiadomić użytkownika, że ​​jego transakcja się nie powiodła?
darpet
2
Zobacz ten wpis na blogu, aby poznać zalety i wady oraz moje własne doświadczenia z tym związane - blog.jhades.org/open-session-in-view-pattern-pros-and-cons
Angular University

Odpowiedzi:

46

Ponieważ wysyłanie potencjalnie niezainicjowanych serwerów proxy, zwłaszcza kolekcji, w warstwie widoku i wyzwalanie z niej ładowania hibernacji może być kłopotliwe zarówno z punktu widzenia wydajności, jak i zrozumienia.

Zrozumienie :

Używanie OSIV „zanieczyszcza” warstwę widoku problemami związanymi z warstwą dostępu do danych.

Warstwa widoku nie jest przygotowana do obsługi, HibernateExceptionco może się zdarzyć podczas leniwego ładowania, ale przypuszczalnie warstwa dostępu do danych jest.

Wydajność :

OSIV ma tendencję do ciągnięcia prawidłowego ładowania bytów pod dywan - zwykle nie zauważasz, że twoje kolekcje lub jednostki są leniwie inicjalizowane (być może N + 1). Więcej wygody, mniej kontroli.


Aktualizacja: zobacz Antypattern OpenSessionInView, aby uzyskać szerszą dyskusję na ten temat. Autor wymienia trzy ważne punkty:

  1. każda leniwa inicjalizacja dostanie zapytanie, co oznacza, że ​​każda jednostka będzie potrzebować N + 1 zapytań, gdzie N to liczba leniwych skojarzeń. Jeśli ekran przedstawia dane tabelaryczne, przeczytanie dziennika Hibernate stanowi wielką wskazówkę, że nie robisz tak, jak powinieneś
  2. To całkowicie pokonuje architekturę warstwową, ponieważ brudzisz paznokcie DB w warstwie prezentacji. To jest koncepcja koncepcyjna, więc mógłbym z tym żyć, ale jest tego konsekwencja
  3. na koniec, jeśli wystąpi wyjątek podczas pobierania sesji, nastąpi to podczas pisania strony: nie możesz przedstawić użytkownikowi czystej strony błędu, a jedyne, co możesz zrobić, to napisać komunikat o błędzie w treści
Robert Munteanu
źródło
13
Ok, „zanieczyszcza” warstwę widoku z wyjątkiem hibernacji. Ale jeśli chodzi o wydajność, myślę, że problem jest podobny do problemu z dostępem do warstwy usług, która zwróci twoje dto. Jeśli napotkasz problem z wydajnością, powinieneś zoptymalizować ten konkretny problem za pomocą inteligentniejszego zapytania lub lżejszego dto. Jeśli musisz opracować zbyt wiele metod obsługi, aby obsłużyć możliwości, których możesz potrzebować w widoku, „zanieczyszczasz” również warstwę usług. Nie?
HeDinges
1
Jedna różnica polega na tym, że opóźnia zamknięcie sesji Hibernate. Będziesz czekać na wyrenderowanie / zapisanie / itp. Strony JSP, dzięki czemu obiekty będą dłużej przechowywane w pamięci. Może to stanowić problem, zwłaszcza jeśli musisz zapisywać dane przy zatwierdzaniu sesji.
Robert Munteanu
8
Nie ma sensu mówić, że OSIV szkodzi wydajności. Jakie są alternatywy oprócz używania DTO? W takim przypadku zawsze będziesz mieć niższą wydajność, ponieważ dane używane przez dowolny widok będą musiały zostać załadowane nawet w przypadku widoków, które ich nie potrzebują.
Johannes Brodwall
11
Myślę, że zanieczyszczenie działa w drugą stronę. Jeśli chcę chętnie załadować dane, warstwa logiczna (lub, co gorsza, warstwa dostępu do danych) musi wiedzieć, w jaki sposób obiekt będzie wyświetlany. Zmień widok, a skończysz ładowanie rzeczy, których nie potrzebujesz lub brakujące przedmioty, których potrzebujesz. Wyjątek hibernacji to błąd i tak samo zatruwający, jak każdy inny nieoczekiwany wyjątek. Ale wydajność jest problemem. Problemy z wydajnością i skalowalnością zmuszą Cię do zastanowienia się i popracowania nad warstwą dostępu do danych i prawdopodobnie wymuszą wcześniejsze zamknięcie sesji
Jens Schauder
1
@JensSchauder "Zmień widok, a skończysz ładowanie rzeczy, których nie potrzebujesz lub brakujące przedmioty, których potrzebujesz". To jest dokładnie to. Jeśli zmienisz widok, znacznie lepiej jest załadować rzeczy, których nie potrzebujesz (ponieważ jest bardziej prawdopodobne, że będziesz chętny do ich pobierania) lub odkryć brakujące obiekty, tak jak w przypadku wyjątku Lazy loading, niż pozwolić, aby widok się załadował to leniwie, ponieważ spowoduje to problem N + 1, a nawet nie będziesz wiedział, że to się dzieje. Więc IMO to lepsza warstwa usług (i Ty) wiesz, co jest wysyłane, niż widok ładujący się leniwie i nic o tym nie wiesz.
Jeshurun
40

Dłuższy opis można znaleźć w moim artykule Anty-Pattern Open Session In View . W przeciwnym razie, oto podsumowanie, dlaczego nie należy używać otwartej sesji w widoku.

Otwarta sesja w widoku ma złe podejście do pobierania danych. Zamiast pozwalać warstwie biznesowej decydować, jak najlepiej pobrać wszystkie skojarzenia, które są wymagane przez warstwę widoku, wymusza ona pozostawienie otwartego kontekstu trwałości, aby warstwa widoku mogła wyzwolić inicjalizację serwera proxy.

wprowadź opis obrazu tutaj

  • OpenSessionInViewFilterWywołuje openSessionmetodę bazowego SessionFactoryi pozyskuje nowe Session.
  • SessionJest zobowiązany do TransactionSynchronizationManager.
  • OpenSessionInViewFilterNazywa doFiltersię javax.servlet.FilterChainodwołania do obiektu, a wniosek jest dalej przetwarzany
  • DispatcherServletNazywa i IT kieruje żądania HTTP do instrumentu bazowego PostController.
  • W PostControllerwywołuje PostServiceuzyskać listę Postpodmiotów.
  • PostServiceOtwiera nową transakcję i HibernateTransactionManagerponownie wykorzystuje ten sam Session, który został otwarty przez OpenSessionInViewFilter.
  • PostDAOPobiera listę Postpodmiotów bez inicjowania żadnej leniwe stowarzyszenie.
  • Zatwierdza PostServicetransakcję bazową, ale Sessionnie jest zamknięta, ponieważ została otwarta na zewnątrz.
  • Do DispatcherServletrozpoczyna renderowania interfejsu użytkownika, który z kolei nawiguje leniwe skojarzenia i wyzwala ich inicjalizacji.
  • OpenSessionInViewFilterMożna zamknąć Session, a pod spodem połączenie z bazą danych jest zwolniony także.

Na pierwszy rzut oka może to nie wyglądać na straszną rzecz, ale gdy spojrzysz na to z perspektywy bazy danych, seria błędów staje się bardziej oczywista.

Warstwa usług otwiera i zamyka transakcję bazy danych, ale później nie ma żadnej jawnej transakcji. Z tego powodu każda dodatkowa instrukcja wydana z fazy renderowania interfejsu użytkownika jest wykonywana w trybie automatycznego zatwierdzania. Automatyczne zatwierdzanie wywiera presję na serwer bazy danych, ponieważ każda instrukcja musi opróżnić dziennik transakcji na dysk, powodując w ten sposób duży ruch we / wy po stronie bazy danych. Jedną z optymalizacji byłoby oznaczenie Connectionjako tylko do odczytu, co pozwoliłoby serwerowi bazy danych uniknąć zapisywania w dzienniku transakcji.

Nie ma już rozdzielania obaw, ponieważ instrukcje są generowane zarówno przez warstwę usług, jak i przez proces renderowania interfejsu użytkownika. Pisanie testów integracyjnych, które potwierdzają liczbę generowanych instrukcji, wymaga przejścia przez wszystkie warstwy (sieć, usługa, DAO), podczas gdy aplikacja jest wdrożona w kontenerze internetowym. Nawet w przypadku korzystania z bazy danych w pamięci (np. HSQLDB) i lekkiego serwera WWW (np. Jetty), te testy integracyjne będą wykonywane wolniej niż gdyby warstwy były rozdzielone, a wewnętrzne testy integracji korzystały z bazy danych, podczas gdy testy integracji front-endu w ogóle kpili z warstwy usług.

Warstwa interfejsu użytkownika jest ograniczona do nawigacji po asocjacjach, które z kolei mogą powodować problemy z zapytaniami N + 1. Chociaż Hibernate oferuje @BatchSizepobieranie skojarzeń w partiach i FetchMode.SUBSELECTporadzi sobie z tym scenariuszem, adnotacje wpływają na domyślny plan pobierania, więc są stosowane w każdym biznesowym przypadku użycia. Z tego powodu kwerenda warstwy dostępu do danych jest o wiele bardziej odpowiednia, ponieważ można ją dostosować do aktualnych wymagań dotyczących pobierania danych dla przypadków użycia.

Wreszcie, połączenie z bazą danych może być utrzymywane przez całą fazę renderowania interfejsu użytkownika (w zależności od trybu zwalniania połączenia), co wydłuża czas dzierżawy połączenia i ogranicza ogólną przepustowość transakcji z powodu przeciążenia puli połączeń bazy danych. Im dłużej połączenie jest utrzymywane, tym więcej innych współbieżnych żądań będzie czekać na połączenie z puli.

Tak więc, albo połączenie jest wstrzymane zbyt długo, albo uzyskujesz / zwalniasz wiele połączeń dla jednego żądania HTTP, wywierając w ten sposób presję na bazową pulę połączeń i ograniczając skalowalność.

Spring Boot

Niestety, otwarta sesja w widoku jest domyślnie włączona w Spring Boot .

Dlatego upewnij się, że w application.propertiespliku konfiguracyjnym znajduje się następujący wpis:

spring.jpa.open-in-view=false

Spowoduje to wyłączenie OSIV, abyś mógł sobie poradzić LazyInitializationExceptionwe właściwy sposób .

Vlad Mihalcea
źródło
3
Korzystanie z otwartej sesji w widoku z automatycznym zatwierdzaniem jest możliwe, ale nie w sposób zamierzony przez programistów Hibernate. Więc chociaż Open Session in View ma swoje wady, automatyczne zatwierdzanie nie jest jednym z nich, ponieważ można go po prostu wyłączyć i nadal z niego korzystać.
stefan.m
Mówisz o tym, co dzieje się wewnątrz transakcji i to prawda. Ale faza renderowania warstwy sieciowej ma miejsce poza hibernacją, dlatego otrzymujesz tryb automatycznego zatwierdzania. Ma sens?
Vlad Mihalcea,
Myślę, że to wariant, który nie jest optymalny dla otwartej sesji w widoku. Sesja i transakcja powinny pozostać otwarte do momentu wyrenderowania widoku, wtedy nie ma potrzeby stosowania trybu automatycznego zatwierdzania.
stefan.m
2
Sesja pozostaje otwarta. Ale transakcja nie. Rozłożenie transakcji na cały proces również nie jest optymalne, ponieważ zwiększa jego długość, a blokady są utrzymywane dłużej niż to konieczne. Wyobraź sobie, co się stanie, jeśli widok zgłosi wyjątek RuntimeException. Czy transakcja zostanie wycofana z powodu niepowodzenia renderowania interfejsu użytkownika?
Vlad Mihalcea,
Bardzo dziękuję za bardzo szczegółową odpowiedź! Ostatecznie zmieniłbym tylko przewodnik, ponieważ użytkownicy rozruchu wiosennego prawdopodobnie nie będą używać jpa w ten sposób.
Skeeve
24
  • transakcje mogą być zatwierdzane w warstwie usług - transakcje nie są związane z OSIV. To Sessionpozostaje otwarte, a nie transakcja - trwająca.

  • jeśli twoje warstwy aplikacji są rozmieszczone na wielu komputerach, praktycznie nie możesz używać OSIV - musisz zainicjować wszystko, czego potrzebujesz, przed wysłaniem obiektu przez sieć.

  • OSIV to przyjemny i przejrzysty (tj. - żaden z twoich kodów nie jest świadomy tego, że tak się dzieje) sposób na wykorzystanie korzyści wydajnościowych wynikających z leniwego ładowania

Bozho
źródło
2
Jeśli chodzi o pierwszy punkt, to przynajmniej nie jest to prawdą w przypadku oryginalnego OSIV z wiki JBoss, obsługuje on również rozgraniczenie transakcji wokół żądania.
Pascal Thivent
@PascalThivent Która część sprawiła, że ​​tak myślisz?
Sanghyun Lee
13

Nie powiedziałbym, że otwarta sesja w widoku jest uważana za złą praktykę; co sprawia na tobie takie wrażenie?

Open-Session-In-View to proste podejście do obsługi sesji za pomocą Hibernate. Ponieważ jest prosty, czasami jest uproszczony. Jeśli potrzebujesz szczegółowej kontroli nad swoimi transakcjami, na przykład wielu transakcji w żądaniu, otwarta sesja w widoku nie zawsze jest dobrym rozwiązaniem.

Jak zauważyli inni, OSIV ma pewne kompromisy - jesteś znacznie bardziej podatny na problem N + 1, ponieważ mniej prawdopodobne jest, że zdasz sobie sprawę, jakie transakcje rozpoczynasz. Jednocześnie oznacza to, że nie musisz zmieniać warstwy usług, aby dostosować się do drobnych zmian w widoku.

Geoffrey Wiseman
źródło
5

Jeśli używasz kontenera Inversion of Control (IoC), takiego jak Spring, możesz poczytać o zakresie fasoli . Zasadniczo mówię Springowi, aby dał mi Sessionobiekt Hibernate, którego cykl życia obejmuje całe żądanie (tj. Jest tworzony i niszczony na początku i na końcu żądania HTTP). Nie muszę się martwić o LazyLoadExceptions ani o zamknięcie sesji, ponieważ kontener IoC zarządza tym za mnie.

Jak wspomniano, będziesz musiał pomyśleć o problemach z wydajnością N + 1 SELECT. Zawsze możesz później skonfigurować swoją jednostkę Hibernacji, aby wykonywała szybkie ładowanie sprzężenia w miejscach, w których wydajność jest problemem.

Rozwiązanie dotyczące zakresu fasoli nie jest specyficzne dla wiosny. Wiem, że PicoContainer oferuje te same możliwości i jestem pewien, że inne dojrzałe kontenery IoC oferują coś podobnego.

0sumgain
źródło
1
Czy masz wskaźnik do faktycznej implementacji sesji Hibernate, które są udostępniane w widoku za pośrednictwem komponentów bean o zakresie żądań?
Marvo
4

Z własnego doświadczenia wynika, że ​​OSIV nie jest taki zły. Jedyne ustalenie jakie poczyniłem to użycie dwóch różnych transakcji: - pierwsza, otwarta w „warstwie usługowej”, gdzie mam „logikę biznesową” - druga otwarta tuż przed renderowaniem widoku

Davide
źródło
3

Właśnie napisałem post na temat wskazówek, kiedy używać otwartej sesji na moim blogu. Sprawdź, jeśli jesteś zainteresowany.

http://heapdump.wordpress.com/2010/04/04/should-i-use-open-session-in-view/

Chris Upton
źródło
1
Zgodnie z ogólną zasadą SO, jeśli udzielasz odpowiedzi, najlepiej zrobić coś więcej niż tylko linkować w innym miejscu. Być może podaj jedno lub dwa zdania lub wymienione elementy, które będą streszczone. Łączenie jest w porządku, ale chcesz zapewnić trochę dodatkowej wartości. W przeciwnym razie możesz po prostu skomentować i umieścić tam łącze.
DWright,
link w tej odpowiedzi jest wart przeczytania, zawiera dobre wskazówki, kiedy używać OSIV, a kiedy nie
ams
1

Jestem b. Zardzewiały na Hibernate ... ale myślę, że jest możliwe, aby mieć wiele transakcji w jednej sesji Hibernate. Zatem granice transakcji nie muszą być takie same, jak zdarzenia rozpoczęcia / zakończenia sesji.

OSIV, imo, jest przede wszystkim przydatne, ponieważ możemy uniknąć pisania kodu uruchamiającego „kontekst trwałości” (inaczej sesję) za każdym razem, gdy żądanie wymaga dostępu do bazy danych.

W warstwie usług prawdopodobnie będziesz musiał wywoływać metody, które mają inne potrzeby transakcyjne, takie jak „Wymagane, Nowe wymagane itp.”. Jedyną rzeczą, jakiej potrzebują te metody, jest to, że ktoś (np. Filtr OSIV) uruchomił kontekst trwałości, więc jedyną rzeczą, o którą muszą się martwić, jest - „hej, daj mi sesję hibernacji dla tego wątku… Muszę coś zrobić Rzeczy DB ”.

rjk2008
źródło
1

To nie pomoże zbyt wiele, ale możesz sprawdzić mój temat tutaj: * Hibernate Cache1 OutOfMemory with OpenSessionInView

Mam pewne problemy z OutOfMemory z powodu OpenSessionInView i wielu załadowanych jednostek, ponieważ pozostają w pamięci podręcznej Hibernate na poziomie 1 i nie są zbierane jako śmieci (ładuję wiele jednostek z 500 elementami na stronę, ale wszystkie jednostki pozostają w pamięci podręcznej)

Sebastien Lorber
źródło