[Java] Correcte koppeling tussen MVC en ORM?

Pagina: 1
Acties:

  • -FoX-
  • Registratie: Januari 2002
  • Niet online

-FoX-

Carpe Diem!

Topicstarter
Momenteel werk ik aan een webapplicatie (Struts/Tiles, Spring en Hibernate) en ben ik op zoek naar een goede best practice voor het afhandelen van de koppeling/loskoppeling tussen het ORM gedeelte en het Web (MVC) gedeelte.

Er zijn meerdere mogelijkheden om deze mismatch op te vullen. Momenteel gebruik ik volgende aanpak:
code:
1
2
3
4
5
6
7
8
9
10
11
<EDIT FORM>
1.) DAO --> BL --> Action--> View page
        - Haal object op: dao.findByPrimaryKey(id);
        - Bevolk de actionform met deze waarden
        - Toon ingevuld formulier op het scherm
<MODIFY FORM>
<SAVE>
2.) Action --> DAO --> Action --> BL --> DAO
        - Haal object opnieuw op, overeenstemmend met een ID/Handle
        - Set de nieuwe waarden op de VO
        - dao.saveOrUpdate(vo)
Ik zou verwachten dat de laatste saveOrUpdate(vo) niet meer nodig zou zijn, maar dit is wel het geval aangezien er anders niets gesaved wordt. Ik neem aan dat de sessie alweer gesloten wordt na het ophalen (2de keer).

Op zich is deze methode nog niet zo slecht, al zit je wel met een aantal issues zoals locking en transacties. Op welke manier weet je namelijk of de velden die je aanpast nog effectief kloppen en niet gewijzigd werden door een ander?

Ik neem aan dat er nog tal van andere mogelijkheden zijn. Ik lees ook net iets van een OpenSessionInViewFilter, maar weet er het fijne niet van. Is er iemand die hier al ervaring mee heeft? Eventuele pitfalls? Performance problemen??

Wat is jullie idee over de koppeling tussen MVC en het ORM gedeelte??
Op welke manier verzorgen jullie deze koppeling?

Verwijderd

Kijk eens naar een sample die bij de download van het springframework zit. Deze sample heet ImageDB. Maar er zitten ook nog andere leerzame samples bij.
Wel de spring-framework-1.2.7-with-dependencies.zip nemen, daar zitten de samples zeker bij.
Het is namelijk te veel om het hier allemaal uit te leggen.

  • -FoX-
  • Registratie: Januari 2002
  • Niet online

-FoX-

Carpe Diem!

Topicstarter
Ik kan allerhande examples wel gaan zitten uitpluizen en alle kennis zelf vergaren, gebaseerd op voorbeelden. Maar dit lijkt me niet de bedoeling. Het lijkt me nét handig om het bespreekbaar te maken en de ideeën van andere personen te zien (vooral de onderbouwingen).

In welke laag ga je de koppeling voorzien; Verder is er nog een overeenkomstige problematiek met transitive persistence en lazy loading. Hoe ga je hiermee om in je weblayer?

Ik heb mijn manier voorgesteld, ben er zelf niet tevreden over en zou graag de meningen van anderen hierover zien.

  • Antediluvian
  • Registratie: Maart 2002
  • Laatst online: 13-01 23:54
Volgens mij is het best om je MVC layer en ORM niet te koppelen.
Het enige dat wel instressant is, is het gebruik van een OpenSessionInView patern zodat je in je MVC gebruik kan maken van lazy loading. Zo is er toch een minimale koppeling tussen je front end en je ORM.
Dus:
request       open session           map url          call service      call dao      call db (orm)
 User ---------> Filter ---------> controller ---------> action ---------> ASL ---------> DAL

html         close session        render view         select view      return DO      create DO
User <--------- Filter <--------- controller <--------- action <--------- ASL <--------- DAL

ASL = Application Service Layer (met services of managers)
DAL = Data Acces Layer (met dao's [sup]Data Access Objects[/sup])
DOM = Domain Object Model (met do's [sup]Domain Objects[/sup])

  • matthijsln
  • Registratie: Augustus 2002
  • Nu online
-FoX- schreef op donderdag 16 maart 2006 @ 23:07:
Momenteel werk ik aan een webapplicatie (Struts/Tiles, Spring en Hibernate) en ben ik op zoek naar een goede best practice voor het afhandelen van de koppeling/loskoppeling tussen het ORM gedeelte en het Web (MVC) gedeelte.
Ik werk met ongeveer hetzelfde systeem maar dan zonder Spring, dus het kan zijn dat het in Spring wat anders gaat. Voor de view wordt JSTL gebruikt.
Er zijn meerdere mogelijkheden om deze mismatch op te vullen. Momenteel gebruik ik volgende aanpak:
code:
1
2
3
4
5
6
7
8
9
10
11
<EDIT FORM>
1.) DAO --> BL --> Action--> View page
        - Haal object op: dao.findByPrimaryKey(id);
        - Bevolk de actionform met deze waarden
        - Toon ingevuld formulier op het scherm
<MODIFY FORM>
<SAVE>
2.) Action --> DAO --> Action --> BL --> DAO
        - Haal object opnieuw op, overeenstemmend met een ID/Handle
        - Set de nieuwe waarden op de VO
        - dao.saveOrUpdate(vo)
Ik zou verwachten dat de laatste saveOrUpdate(vo) niet meer nodig zou zijn, maar dit is wel het geval aangezien er anders niets gesaved wordt. Ik neem aan dat de sessie alweer gesloten wordt na het ophalen (2de keer).
Dit is ook niet nodig indien je in een Hibernate Transaction zit. De wijzigingen worden gesaved indien de Transaction wordt gecommit.
Op zich is deze methode nog niet zo slecht, al zit je wel met een aantal issues zoals locking en transacties. Op welke manier weet je namelijk of de velden die je aanpast nog effectief kloppen en niet gewijzigd werden door een ander?

Ik neem aan dat er nog tal van andere mogelijkheden zijn. Ik lees ook net iets van een OpenSessionInViewFilter, maar weet er het fijne niet van. Is er iemand die hier al ervaring mee heeft? Eventuele pitfalls? Performance problemen??

Wat is jullie idee over de koppeling tussen MVC en het ORM gedeelte??
Op welke manier verzorgen jullie deze koppeling?
Ik werk ongeveer op de manier die jij hierboven beschrijft.

Dit heeft inderdaad problemen die je hierboven beschrijft. Na het tonen van het form aan de user kan de database door een ander proces worden gewijzigd. Indien de user zijn form met stale data submit krijg je dan dus een probleem. Het hangt natuurlijk af van hoe groot dit probleem is (last save wins ok? is geen inconsistentie mogelijk? gaat user niet stijl achterover slaan?) of je er iets aan moet doen. Met Hibernate is hieraan wel iets te doen met versioning of timestamps (ik heb op dit moment nog niet zoiets geimplementeerd). Indien je data erg sensitive is voor concurrency issues zou ik hier zeker naar kijken. Kijk ook eens naar het Hibernate session per conversation pattern.

OpenSessionInView en performance problemen hangen eigenlijk een beetje met elkaar samen. Bij het beschikbaar maken van informatie voor de view moet je eerst even bedenken welke relaties allemaal nodig zijn en of deze wel of niet gefetched moeten worden via een join. Dit is belangrijk om het "N+1 selects" probleem te vermijden (je doet 1 select from Aap query, maar voor elk Aap record moet een Noot en Mies record worden gefechted).

Ik werk zelfs het liefst zonder een open session in de view, zodat je wordt gedwongen na te denken om de informatie redelijk efficient en zonder een explosie van queries beschikbaar te maken.

  • Antediluvian
  • Registratie: Maart 2002
  • Laatst online: 13-01 23:54
matthijsln schreef op zaterdag 18 maart 2006 @ 10:33:
Ik werk zelfs het liefst zonder een open session in de view, zodat je wordt gedwongen na te denken om de informatie redelijk efficient en zonder een explosie van queries beschikbaar te maken.
Waarom zou dit een explosie van queries veroorzaken. Ik zou juist het tegendeel beweren.

Dankzij OpenSessionInView kan je gebruik maken van Lazy Loading en zo enkel de data van de db opvragen die je werkelijk nodig hebt.
Als je hiervan geen gebruik maakt moet je:
  • of alles ophalen uit de DB in de DAL (massa's queries)
  • of perfect medelen welke attributen & collecties die je nodig hebt in de view (view gaat dicteren hoe de ASL en DAL iets moeten doen)
  • of de controller moet verschillende call's doen tot hij alles heeft wat hijzelft en de view nodig heeft (controller moet perfect weten wat de view zal weergeven)
Geen enkel van deze senario's lijken mij wenselijk.

  • -FoX-
  • Registratie: Januari 2002
  • Niet online

-FoX-

Carpe Diem!

Topicstarter
matthijsln schreef op zaterdag 18 maart 2006 @ 10:33:
Ik werk met ongeveer hetzelfde systeem maar dan zonder Spring, dus het kan zijn dat het in Spring wat anders gaat. Voor de view wordt JSTL gebruikt.
Op zich heeft Spring hier niet veel mee te maken, al voorzien ze wel een interface voor de hibernate acces. Maar dit doet hier niet echt ter zake.
Dit is ook niet nodig indien je in een Hibernate Transaction zit. De wijzigingen worden gesaved indien de Transaction wordt gecommit.
Ik heb het idee dat de Hibernate Transaction na de 2de fetch ook onmiddellijk weer gesloten wordt. Dus een expliciete binding aan de sessie is wel nodig. Op welke manier kan je het anders doen?
Dit heeft inderdaad problemen die je hierboven beschrijft. Na het tonen van het form aan de user kan de database door een ander proces worden gewijzigd. Indien de user zijn form met stale data submit krijg je dan dus een probleem. Het hangt natuurlijk af van hoe groot dit probleem is (last save wins ok? is geen inconsistentie mogelijk? gaat user niet stijl achterover slaan?) of je er iets aan moet doen. Met Hibernate is hieraan wel iets te doen met versioning of timestamps (ik heb op dit moment nog niet zoiets geimplementeerd). Indien je data erg sensitive is voor concurrency issues zou ik hier zeker naar kijken. Kijk ook eens naar het Hibernate session per conversation pattern.
Hibernate versioning systeem doet hier eigenlijk weinig aan. Aangezien je de laatste versie bij de 2de fetch terugkrijgt. (Tenzij er tussen de 2de fetch en de effectieve save nog een andere versie gecommit wordt). Dus hier zie ik wel een probleem in. In mijn systeem is dit zeker niet wenselijk!
Hebben mensen hier ervaring mee, hoe wordt dit meestal aangepakt of wat is de best practice?
OpenSessionInView en performance problemen hangen eigenlijk een beetje met elkaar samen. Bij het beschikbaar maken van informatie voor de view moet je eerst even bedenken welke relaties allemaal nodig zijn en of deze wel of niet gefetched moeten worden via een join. Dit is belangrijk om het "N+1 selects" probleem te vermijden (je doet 1 select from Aap query, maar voor elk Aap record moet een Noot en Mies record worden gefechted).

Ik werk zelfs het liefst zonder een open session in de view, zodat je wordt gedwongen na te denken om de informatie redelijk efficient en zonder een explosie van queries beschikbaar te maken.
OpenSessionInView is dus alleen maar wenselijk voor lazy loading problematiek?

  • matthijsln
  • Registratie: Augustus 2002
  • Nu online
Antediluvian schreef op zaterdag 18 maart 2006 @ 12:53:
[...]
Waarom zou dit een explosie van queries veroorzaken. Ik zou juist het tegendeel beweren.
N+1 select probleem?
Dankzij OpenSessionInView kan je gebruik maken van Lazy Loading en zo enkel de data van de db opvragen die je werkelijk nodig hebt.
Als je hiervan geen gebruik maakt moet je:
  • of alles ophalen uit de DB in de DAL (massa's queries)
  • of perfect medelen welke attributen & collecties die je nodig hebt in de view (view gaat dicteren hoe de ASL en DAL iets moeten doen)
  • of de controller moet verschillende call's doen tot hij alles heeft wat hijzelft en de view nodig heeft (controller moet perfect weten wat de view zal weergeven)
Geen enkel van deze senario's lijken mij wenselijk.
Het anderste uiterste lijkt mij anders ook niet wenselijk. ORM is niet de holy grail, soms heb je gewoon kennis nodig van welke informatie je nodig gaat hebben en hoe je die efficient uit de database krijgt. Anders ga je echt pagina's krijgen die honderden queries gaan genereren, of een rapportagetool die de hele database gaat fetchen (been there).

  • matthijsln
  • Registratie: Augustus 2002
  • Nu online
-FoX- schreef op zaterdag 18 maart 2006 @ 14:02:
[...]
Ik heb het idee dat de Hibernate Transaction na de 2de fetch ook onmiddellijk weer gesloten wordt. Dus een expliciete binding aan de sessie is wel nodig. Op welke manier kan je het anders doen?
:? Ik volg je niet helemaal... Een Hibernate sessie wordt gesloten wanneer je die zelf sluit of de transaction eindigt (je kan na eindigen van de transaction wel weer een nieuwe transaction starten voor de session).
Hibernate versioning systeem doet hier eigenlijk weinig aan. Aangezien je de laatste versie bij de 2de fetch terugkrijgt. (Tenzij er tussen de 2de fetch en de effectieve save nog een andere versie gecommit wordt). Dus hier zie ik wel een probleem in. In mijn systeem is dit zeker niet wenselijk!
Hibernate versioning doet hier toch wel wat aan. In de documentatie staan hiervoor een boel mogelijkheden beschreven: http://www.hibernate.org/...en/html/transactions.html
OpenSessionInView is dus alleen maar wenselijk voor lazy loading problematiek?
Je hebt een open sessie nodig om entities uit de database te laden. Gebruik je in je view dus een relatie die nog niet geladen is kan deze automatisch worden geladen wanneer de session nog open is. Het kan handig zijn maar ook wat problemen opleveren, zoals het N+1 selects probleem en andere performance problemen (een transaction open hebben tijdens het langdurig renderen van een pagina is ook niet bevorderlijk voor performance indien andere transactions worden geblocked).

  • -FoX-
  • Registratie: Januari 2002
  • Niet online

-FoX-

Carpe Diem!

Topicstarter
matthijsln schreef op zondag 19 maart 2006 @ 00:09:
:? Ik volg je niet helemaal... Een Hibernate sessie wordt gesloten wanneer je die zelf sluit of de transaction eindigt (je kan na eindigen van de transaction wel weer een nieuwe transaction starten voor de session).
De transactie is onmiddellijk ook weer afgelopen, na het her-ophalen van het object dat we gewijzigd hebben. Dus als ik deze populeer met de nieuwe waarden, dien ik op deze weer expliciet een update() aan te roepen.
Hibernate versioning doet hier toch wel wat aan. In de documentatie staan hiervoor een boel mogelijkheden beschreven: http://www.hibernate.org/...en/html/transactions.html
Op welke manier dan? De versioning informatie wordt toch gewoon overschreven bij de 2de call?
VB:
code:
1
2
3
4
5
6
7
8
1. dao.findByPrimaryKey(id);                 --> entiteit met id=14 en version=3
2. copyProperties(entity, actionform);
3. display form to user
4. --> 2de concurrent user doet een dao.findByPrimaryKey(id); --> entity id=14 version=3
5. --> aanpassing
6. --> save
7. aanpassing
8. save --> findByPrimaryKey(id); --> entiteit id=14, version=4

Bij stap 8 heeft de entiteit versie-4, aangezien deze matcht met de laatste versie in de database zal deze record gewoon overschreven worden (de laatste wint dus). Dit is niet de bedoeling van het versioning-systeem en dit probleem komt ook alleen maar voor omdat we na de aanpassing, het object terug uit de database gaan ophalen.
Je hebt een open sessie nodig om entities uit de database te laden. Gebruik je in je view dus een relatie die nog niet geladen is kan deze automatisch worden geladen wanneer de session nog open is. Het kan handig zijn maar ook wat problemen opleveren, zoals het N+1 selects probleem en andere performance problemen (een transaction open hebben tijdens het langdurig renderen van een pagina is ook niet bevorderlijk voor performance indien andere transactions worden geblocked).
Akkoord, maar ik denk ook niet dat het altijd nodig is om al je relaties volledig naar waarheid te modelleren. Als je enkel een bepaald object in een bepaalde view nodig hebt. Kan het toch evengoed met een aangepaste mapping, zonder associaties?
Misschien niet direct het juiste voorbeeld, maar je begrijpt wel waar ik naartoe wil.


Een tweede mogelijke 'workaround' is om het entiteit object telkens in de sessie te plaatsen. Op die manier kan je met dezelfde entiteit blijven werken. Maar dit gaat de sessie misschien te zwaar vervuilen?

  • matthijsln
  • Registratie: Augustus 2002
  • Nu online
-FoX- schreef op zondag 19 maart 2006 @ 10:52:
De transactie is onmiddellijk ook weer afgelopen, na het her-ophalen van het object dat we gewijzigd hebben. Dus als ik deze populeer met de nieuwe waarden, dien ik op deze weer expliciet een update() aan te roepen.
Die transaction moet je dan ook niet meteen sluiten, waarom doe je dat? Daarmee krijg je alleen nog maar meer concurrency issues.
Op welke manier dan? De versioning informatie wordt toch gewoon overschreven bij de 2de call?
Ik snap je punt, maar de oplossing staat toch echt in de Hibernate documentatie:

http://www.hibernate.org/...actions-optimistic-manual
Een tweede mogelijke 'workaround' is om het entiteit object telkens in de sessie te plaatsen. Op die manier kan je met dezelfde entiteit blijven werken. Maar dit gaat de sessie misschien te zwaar vervuilen?
Deze manier is inderdaad een mogelijkheid, beschreven in:
http://www.hibernate.org/...ns-optimistic-longsession

Deze variant heeft dus automatic version checking intt de vorige link waarbij dat handmatig is. Je krijgt dus een StaleObjectStateException voor je kiezen indien een andere user tussendoor heeft gesaved.

  • -FoX-
  • Registratie: Januari 2002
  • Niet online

-FoX-

Carpe Diem!

Topicstarter
Nu hebben we wel een aantal mogelijkheden besproken. Maar welke aanpak verkies jij dan?

Misschien kan je ook een aantal guidelines posten.. die je probeert te volgen
Pagina: 1