Jak to zaprojektować projekt dostępu do danych?

0

Hej,

Słuchajcie potrzebuję zaprojektować kawałek aplikacji i przyznam szczerze, że trochę się już w tym pogubiłem i dlatego zdecydowałem się zapytać Was.

Opis:

Tworzę projekt Javowy, który będzie odpowiedzialny za kontakt z bazą danych (standard). Oczywiście zostanie to zrealizowane za pomocą JPA (implementacja w Hibernate), a dostarczycielem api do DAO czy transakcji będzie Spring framework.
Przewiduję, że będzie potrzebna mi jakaś generyczna klasa DAO do CRUD operacji. Oprócz tego zapewne będą mi potrzebne jeszcze inne klasy(bardziej specyficzne), za pomocą których będę odwoływał się do bazy danych (może podklasy generycznego DAO?). Będą tam zawarte jakieś szczególne zapytania czy to sql, jpql czy przez CriteriaBuilder. Przewiduję także, że będzie mi potrzebny deklaratywna obsługa transakcji (całość dostarczona ze Springa).

I tu pojawiają mi się pytania:

Jak to zrobić zgodnie ze sztuką?
Czy powinienem w ogóle tworzyć specyficzne DAO dla różnych operacji, które będą dziedziczyć po DAO generycznym? Czy tak to się robi?
Jak napisać te DAO opakowane deklaratywnym obsługą transakcji, aby móc łatwo i bezinwazyjnie przekazywać je innym beanom z kontenera Springa (tam się pojawić może problem z interfejsami Proxy i Advice)?
Czy może zrobić to w całkiem inny sposób?
Aha czy tak właściwie jak z umieszczam w kontenerze Springa obiekt DAO, powołując go jako "zwykłago" beana to czy to dobrze?
Jak zapewnić, aby w przyszłości nie musieć zmagać się z problemami konkurencji? Czy to już zrobi za mnie od początku do końca Spring AOP i API do transakcji?
Czy macie może jakieś propozycje przykładowych projektów, których robi się to tak jak powinno się robić?
Może jakieś linki?
Czy może rozszerzyć opis jak coś jest niezrozumiałe lub niekompletne?

Dzięki

1
  1. Tak, ale bądź normalny i nie pisz tego generic dao samodzielnie. W necie jest 1000 przykładów jak to zrobić.
  2. Nie ma problemów z @Transactional o ile:
  • wszędzie (!) wstrzykujesz interfejsy a nie konkretne klasy
  • nie robisz krzyżowych referencji pomiędzy serwisami
  1. Nie rozumiem co to "zwykły" bean. Dao pewnie będzie jakimś @Service
  2. Tak, transakcje same rozwiążą ten problem.
1

Aplikacja powinna mieć dwie warstwy:
-dostępu do danych (DAO) - każde DAO odpowiedzialne za obsługę konkretnej encji. Powinny to być beany zaanotowane poprzez @Repository
-serwisowa - z logiką biznesową. Zaanotowane poprzez @Service

Należy pamiętać o tym, że:
-beany @Service mogą mieć wstrzyknięte inne beany @Service oraz @Repository. Nigdy jednak DAO nie powinna się komunikować z innym DAO lub serwisem. Czyli zawsze serwis wywołuj metodę DAO (lub innego serwisu), a nie odwrotnie. Dlatego DAO nie powinno mieć nigdy wstrzyknięte żadnego innego DAO lub serwisu. DAO powinno być maksymalnie "głupie". Cała logika powinna być w warstwie serwisowej.
-adnotacji @Transactional należy używać tylko na poziomie serwisu, nie DAO. Jeżeli metoda serwisowa wywołuje wiele metod DAO (jednego lub wielu DAO), to transakcji powinna trwać na całe wywołanie metody serwisowej.

W moim projekcie używam klasy generycznego abstrakcyjnego dao (proste metody do CRUD), po której dziedziczą klasy DAO do każdej encji (do których dokładam niestandardowe metody).
Generyczne DAO implementuje interfejs, po którym dziedziczą interfejsy implementowane przez konkretne DAO.
Do serwisów zawsze wstrzyknięte są interfejsy DAO, a nie implementacji.
Rozwiązuje to problem przy mockowaniu w testach jednostkowych.

0

dzięki krzysiek..trochę mi Twoja wypowiedź odwróciła myślenie, zwłaszcza ta myśl:

"-adnotacji @Transactional należy używać tylko na poziomie serwisu, nie DAO. Jeżeli metoda serwisowa wywołuje wiele metod DAO (jednego lub wielu DAO), to transakcji powinna trwać na całe wywołanie metody serwisowej."

dzięki

1

@emilklim z tymi transakcjami i tym jak są "wysoko" to trochę zależy od operacji, które są wykonywane :) Zdarzyło mi się kiedyś poprawiać błąd w kodzie gdzie transakcja była na poziomie serwisu, który miał zaimplementowanie takie "aktywne czekanie". Pobierał z bazy informacje czy jest jakaś encja "available" i jak była to ją sobie zajmował (ustawiając jej false). Problem był taki że "true" ustawiało się tam poprzez wywołanie innej metody. I ogólnie generowało to trochę problemów związanych z deadlockiem na transakcji, bo "aktywnie czekająca" metoda, jako że miała zarówno read jak i write miała exclusive locka na te encje i metoda która miała ustawić true nie mogła wykonać operacji na bazie bo czekała na zakończenie tamtej transakcji ;] Tak więc: wszystko z głową ;) Transakcje powinny być na takim poziomie, gdzie ma to jakiś sens - obejmujemy transakcją taki zestaw operacji, który faktycznie powinien być wykonany "na raz".

Poza tym z wyciąganiem transakcji możliwie wysoko wiąże się problem n+1 selectów. Chodzi o to że jeśli masz jakieś pola lazy w klasie encyjnej to one nie są automatycznie wyciągane z bazy. Jeśli transakcja jest nisko (np. na poziomie dao) to po wyciągnięciu takich obiektów i przekazaniu ich do warstwy biznesowej, próba odwołania do takiego pola spowoduje LazyInitializationException. Jeśli transakcja jest wysoko i takie odwołanie nastąpi jeszcze "wewnątrz" transakcji to wygenerowane zostaną zapytania do wyciągnięcia tego brakującego pola. Jeśli to było 1:n albo n:m to wygeneruje ci się w związku z tym n zapytań do bazy ;]
Oczywiście tutaj problemem jest niepoprawne zapytanie, a nie sam fakt transakcji możliwie wysoko, żebyśmy mieli jasność :) Chodzi mi tylko o to, że warto patrzeć w logi zapytań, bo jeśli transakcje są na poziomie serwisów to łatwo przeoczyć n+1 selectów bo nie ma żadnych wyjątków i nic sie nie "sypie", tylko wolno działa.

1 użytkowników online, w tym zalogowanych: 0, gości: 1