Struktura aplikacji web, hibernate lazy Initialization

Struktura aplikacji web, hibernate lazy Initialization
MA
  • Rejestracja:ponad 10 lat
  • Ostatnio:ponad 8 lat
  • Postów:35
0

Witam,

Mam zamiar zacząć pisać prostą aplikację webową opartą na Spring MVC i Hibernate. Mam kilka pytań odnośnie struktury modelu danych oraz Hibernate.

Co do struktury będzie ona dosyć standardowa:

  • encje jpa,
  • klasy DAO (@Repository),
  • serwisy (@Transactional, @Service),
  • controllery spring
  • widok - strony jsp.
  1. Zastanawiam się nad modelem danych przeznaczonym do komunikacji z widokiem. Aktualnie przychodzi mi do głowy albo przesyłanie obiektów encji (Czy to nie będzie mieszanie warstw aplikacji?) albo utworzenie specjalnych obiektów DTO i wrzucenie tam tylko potrzebnych danych (z tym, że trochę szkoda roboty na przepisywanie kodu) lub może utworzenie controllerow restowych. Jakie waszym zdaniem jest najlepsze rozwiązanie?

Teraz odnosnie Hibernate:

bazując na przykladowym modelu:

Kopiuj

@Entity
@Table(name = "apps")
public class App {
    @Id
    @GeneratedValue
    private int id;
    private String city;
    @OneToMany(mappedBy = "app", cascade = CascadeType.ALL)
    private Set<User> users = new HashSet();

    //settery, gettery
}

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue
    private int id;
    private String name;
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private Set<Phone> phones = new HashSet();
    @ManyToOne
    @JoinColumn(name = "app_id")
    private App app;
    
    //gettery, settery

}

@Entity
@Table(name = "phones")
public class Phone {

    @Id
    @GeneratedValue
    private int id;
    private String number;

    //settery, gettery
}

W celu uniknięcia LazyInitializationException wykonuję fetch joiny. I teraz tak (pewnie to jest głupie pytanie):

  1. Załóżmy, że chce pobrac sobie obiekt App o określonym identyfikatorze dołączając do niego zbiór Userow ALE WYKLUCZAJĄC WSZYSTKIE RELACJE w obiekcie User ( w tym przypadku Set<Phone>). Jak to najprościej zrobic?

Dlaczego o to pytam. Załóżmy że pobiorę obiekt App (dla powyższych warunkow) wyjdę nim poza warstwę serwisową, czyli poza sesje hibernate i niechcący pobiorę sobie Zbior Set<Phone> z obiektu User. Wtedy poleci LAzyInitializationException - tego chce uniknąć.

Ale może coś zle rozumiem?

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0
  1. DTO, na pewno nie Encje. Z przepychaniem encji mogą być różne dziwne problemy bo na przykład chcesz sobie przygotować w serwisie dane do wysłania do widoku i coś tam mieszasz a zapomnisz że masz transakcje na tym serwisie i nagle skasowałeś sobie pół bazy(tak tak, widziałem takie akcje ;) )
  2. Nie bardzo rozumiem co to znaczy "niechcący". Albo potrzebujesz tych danych i robisz fetch join, albo nie. Poza tym jak przepiszesz potrzebne dane do DTO to problem znika bo nie będzie tych danych do których mógłbyś się "przypadkiem" odwołac.

"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
MA
  • Rejestracja:ponad 10 lat
  • Ostatnio:ponad 8 lat
  • Postów:35
0
  1. Tak też myslalem ze stanie na DTO. W jaki sposób najlepiej tworzyć obiekty DTO, jest jakis sprawdzony pattern? Utworzyć sobie jakis interfejs który bedą implementowaly serwisy (Interfejs do mapowania dto -> encje, encje na dto) i zwracaly juz przemapowane obiekty na DTO do kontrolera? Czy może konstruktory w w klasach przyjmujące odpowiednio encje lub dto inicjalizujace obiekt? Czy tez tworzyć obiekty tylko w razie potrzeby?

2."Poza tym jak przepiszesz potrzebne dane do DTO to problem znika" - tak w tym przypadku problem się rozwiązuje.

Shalom
W sumie nie wiem czy jest jakiś pattern na takie mapowanie ;)
MI
  • Rejestracja:około 12 lat
  • Ostatnio:ponad 4 lata
  • Postów:39
0
  1. A może mappery do przepisywania encja -> dto, dto -> encja? np. ModelMapper, Dozer
MA
  • Rejestracja:ponad 10 lat
  • Ostatnio:ponad 8 lat
  • Postów:35
0

myślałem o tym ale jednak nie chce angażować dodatkowych frameworkow (tym bardziej generatorów kodu, nie przepadam za nimi). Poniżej napisze na co się zdecydowałem może się komuś przyda.

frameworki: Spring MVC, Hibernate, Maven

Komunikacja z bazą: encje JPA, warstwa dao

Komunikacja controllera z modelem z biznesowym (baza) poprzez serwisy i tutaj ustaliłem że zrobie to w taki sposób:

  • Utworzę klasy DTO (posiadające tylko wymagane pola i gettery, obiekty będą readonly, plus odpowiednie konstruktory przyjmujące encje i inicjalizujące obiekty dto) odpowiednio dla każdej encji która będzie się komunikowała z widokiem (JSP).
  • Będę szedł raczej drogą aby większość relacji była 'lazy' i w razie potrzeby pobierac odpowiednie dane 'fetch joinem', zeby uniknąć milionów selektow do bazy.
  • Metody serwisowe pobierające dane, będą przyjmowały z dao odpowiednio kolekcje lub obiekty encji, następnie na poziomie serwisu będą się odbywały wszystkie operacje na nich, i zwracane dane do controllera będą już przemapowane na DTO. (Dzieki temu na poziomie widoku i controllera będą bezpieczne obiekty uniemożliwiające modyfikacje i przede wszystkim wykluczające LazyInitilizationException)
  • W przypadku tworzenia nowych obiektów po stronie widoku: Będą tworzone obiekty encyjne (w stanie transient, także też będą bezpieczne), i w tym przypadku metody serwisowe zapisujace dane będa przyjmowały encje a nie dto. (trochę to wymieszałem ale nic lepszego nie wywnioskowałem na tę chwilę, chcialem też, uniknąć mapowania w drugą strone, było by to dość kłopotliwe)

Czy to się sprawdzi? To się okażę :P

Jeśli macie jakieś uwagi i propozycje to piszcie, myślę ze to dość powszechny problem więc jeśli ktoś ma większe doświadczenie, miło będzie wysłuchać :) Może ktoś zaproponuje jakieś inne ciekawe i dobre podejscie.

Kundel Burek
  • Rejestracja:około 9 lat
  • Ostatnio:12 miesięcy
  • Postów:10
0

Jeśli chodzi o kwestię przepisywania encji na DTO, to IMHO jak na razie nie ma idealnego rozwiązania. Albo używasz zewnętrznych frameworków, które generalnie pod maską używają refleksji do kopiowania danych, albo tworzysz assemblery i robisz to ręcznie. Jest to bezpieczniejsze, bo widzisz explicite co jest kopiowane, ale co tu dużo mówić - czasochłonne i upierdliwe. Wybór należy do Ciebie. Od bidy, jak nie chcesz dodawać kolejnego framweworka, to możesz użyć BeanUtils.copyProperties ze Springa, co jednak i tak nie zwolni Cię pewnie z napisania assemblerów.

A co do implementacji, to tak na pierwszy rzut oka:

  • dobrze, że DTO będzie immutable.
  • IMHO DAO powinno enkapsulować obiekty zasobów (encje w tym wypadku) i powinno zwracać już DTO do serwisu. Serwis powinien operować na DTO w dół i w górę.
  • chyba bardziej eleganckim rozwiązaniem będzie jednak translacja z encji na DTO i z DTO na encje niż robienie hybrydy w zależności od odczytu i zapisu.
SP
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 2 lata
  • Postów:127
0
Shalom napisał(a):
  1. DTO, na pewno nie Encje. Z przepychaniem encji mogą być różne dziwne problemy bo na przykład chcesz sobie przygotować w serwisie dane do wysłania do widoku i coś tam mieszasz a zapomnisz że masz transakcje na tym serwisie i nagle skasowałeś sobie pół bazy(tak tak, widziałem takie akcje ;) )
    (...)

Ale przecież zazwyczaj po odczycie danych do wyświetlenia zamyka się transakcję, więc nie można już nic namieszać w bazie. Poza tym od czego jest walidacja. Ja jestem zwolennikiem przepychania encji 8)

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
2

@student pro od kiedy sie niby zamyka transakcje po odczycie danych? o_O Transakcje ustawia się możliwie wysoko, żeby operacje w warstwie logiki był atomowe i zeby nie zabić wydajności otwieraniem i zamykaniem połączeń do bazy.
Gdyby ustawić transakcje na DAO to może byłoby tak jak piszesz, ale wtedy miałbyś problem z:

  1. Niespójnym stanem - polowa logiki jakiegoś serwisu się wykonała a potem był błąd i co teraz? Ta polowa operacji już została wykonana i zapisana w bazie i nie bardzo da sie to cofnąć...
  2. Pracą z niespójnymi danymi - połowa logiki jakiegoś serwisu się wykonała a teraz inny wątek już pracuje na tych danych, mimo że inne dane na których pracuje są jeszcze "stare".
  3. Wydajnością - co operacje marnujesz czas na narzut związany z otwieraniem i zamykaniem transakcji.

W efekcie w praktyce, w systemach gdzie istnieje jakaś logika (czyli nie w studenckich CRUDach gdzie masz tylko web-ui i warstwę DAO) transakcje ustawia się na metody serwisów. A taki serwis zajmuje się między innymi przygotowaniem danych do obsługi przez kontroler. W efekcie trzeba bardzo uważać jak cokolwiek robisz z encjami, bo na tym etapie transakcja nie jest zamknięta. W szczególności trzeba mieć na uwadze że zmiana obiektu encji propaguje sie jako zmiana w bazie, nawet jeśli nie wywolasz ręcznie żadnego update() czy saveOrUpdate().

Nie wiem co do tego mają walidatory, bo przecież w zaden sposób w niczym tu nie pomogą.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
SP
  • Rejestracja:ponad 9 lat
  • Ostatnio:ponad 2 lata
  • Postów:127
0

@Shalom: nie chodzi mi o zamykanie transakcji po każdym pojedynczym odczycie z bazy, tylko po wczytaniu danych do wyświetlenia.

W JSF:

Kopiuj
@ViewScoped
class Kontroler{
  @Getter MyEntity rootEntity;
  //to jest wykonywane automatycznie przed wyrenderowaniem strony (w JSF nazywa się to 'viewAction')
  void init(){
    // wczytajGrafEncji zaczyna i kończy transakcję
    rootEntity = serwis.wczytajGrafEncji( id_korzenia_grafu_encji_ktory_wyswietle );
    //od tego momentu rootEntity moge sobie edytowac, nie ma juz transakcji wiec nic mi sie nie zapisze
  }
  String akcjaUsera(){
     serwis.metodaBiznesowa( dane_z_formularza );
     // na koncu robie redirect-get
  }
}

Problem może wystąpić gdy chce zblokować encje, bo wtedy musiałbym chyba trzymać otwartą transakcję (wczytajGrafEncji nie zamknęła by transakcji), i wtedy rzeczywiście jest niebezpiecznie (tego przypadku jeszcze nie rozwiązałem :)

Shalom napisał(a):

Nie wiem co do tego mają walidatory, bo przecież w zaden sposób w niczym tu nie pomogą.

Chodziło automatyczną walidację encji (BeanValidation) przed ich zapisaniem (PreUpdate), powinna pilnować poprawności danych

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

No dobra ale teraz to pokazałes tylko trywialny przykład kiedy tylko coś czytasz z bazy, a przecież w prawdziwym życiu logika biznesowa nie jest taka prosta i ten model sie zupełnie nie sprawdzi. Już jeden głupi zapis do bazy albo konieczność atomowego wczytania danych z kilku niepowiązanych encji sprawia że twój model nadaje się do kosza...

Walidator to sobie może sprawdzić czy format danych jest poprawny. Nie może sprawdzić spójności logicznej danych, szczególnie nie bez blokowania całej bazy a przecież zapis tak zwalidowanych danych tez musi być atomowy razem z całym sprawdzeniem, bo inaczej masz kalsyczny błąd check-then-act. Zwalidowałeś i było ok a w trakcie zapisu już może nie być ok bo coś się zmieniło w międzyczasie.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
MA
  • Rejestracja:ponad 10 lat
  • Ostatnio:ponad 8 lat
  • Postów:35
0

@Kundel Burek
Wydaje mi się ze enkapsulowanie encji na poziomie dao jest trochę bez sensu. W takim razie po co nam transakcyjność na serwisie? Chyba serwis daje nam tą zaletę, że możemy w nim zaimplementować logike związaną z modelem w ramach transkacji i bezpiecznie operować na obiektach. Ale założmy, że zwrocimy dto do serwisu, jeżeli będą one immutable, przy każdej drobnej zmianie wymagane będzie tworzenie nowego obiektu i wykonywanie mapowania w obydwie strony, chyba złożoność operacji w takich przypadkach niepotrzebnie wzrośnie ale mogę się mylić...Z kolei "hybryda" którą sobie wymysliłem też mi się nie bardzo podoba trochę pomieszanie z poplątaniem

Jeszcze co do DTO, nie chce ich mapować 1:1 z encjami, bo w większości przypadków wszystkie pola encji nie będą mi potrzebne w widoku (pewnie nigdy), chce tam umieszac potrzebne dane, to też trochę zawsze zmniejszy obciążenie przesyłanych danych. Dlatego jeżeli bym zrobił translacje obiektow w obydwie strony znowu pojawia się porąbana logika odwzorowania niepełnego dto na encje... Natomiast w przypadku operowania nawet encyjnym obiektem w widoku ale nowo utworzonym byłoby to bezpieczne i zapis byłby sprawny

Przypomina mi to wybory w naszym kraju, mniejsze zło będzie górą.

edytowany 1x, ostatnio: mateuszq
Kundel Burek
  • Rejestracja:około 9 lat
  • Ostatnio:12 miesięcy
  • Postów:10
0

@mateuszq

Osobiście jestem zwolennikiem enkapsulowania szczegółów implementacyjnych persystencji w DAO. IMHO DAO powinno przyjmować POJO i zwracać POJO. Dzięki temu, gdy zmienisz np. sposób persystencji z bazki na cokolwiek innego, nie będziesz potrzebować zmieniać niczego w serwisach, jedynie w DAO. Serwisy powinny skupić się na logice biznesowej, a DAO na obsłudze persystencji. Natomiast transakcyjność nie ma tutaj nic do rzeczy - jeśli serwis jest transakcyjny, to nie ważne gdzie będziesz dokonywać translacji z encji na DTO, transakcja przeprowadzona będzie tak samo.

Tym bardziej myślę, że należy odseparować kontrolery widoku od encji. Zmieniając jakąkolwiek tabelkę, będziesz musiał przeorać serwisy i widok. Jeśli będzie Cię męczyć kopiowanie propertisów ręcznie, użyj choćby tooli ze Springa czy Apache Commons.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)