[NHibernate] niet-dirty objecten worden gesaved.

Pagina: 1
Acties:
  • 103 views sinds 30-01-2008
  • Reageer

  • whoami
  • Registratie: December 2000
  • Laatst online: 07-04 22:26
Ik ben een beetje met NHibernate aan het spelen. Ik heb bv een class Customer. Die Customer heeft Orders. Orders worden lazy geladen, en hebben als cascade optie 'save-update'.
So far so good; alles werkt zoals ik het wil, behalve het saven dus. :P

Ik heb een WinForms applicatie:
Die applicatie bestaat uit een overzichtscherm van Customers. Als ik daar een Customer kies, kan ik een detail-scherm openen voor die Customer. Op dat moment wordt de customer uit de databank gehaald:
code:
1
_customerObj = customerRepositoryObj.GetCustomer (id);

In de GetCustomer method van de repository, ga ik een ISession openen, de customer opladen, en de sessie opnieuw sluiten.
Op dit moment heb ik m'n Customer object in het geheugen zitten.

Stel nu dat ik een paar wijzigingen heb doorgevoerd aan attributen van de entiteit 'Customer', bv z'n naam. Echter, ik heb geen wijzigingen gedaan aan z'n Orders (ik heb deze enkel opgehaald).
Als ik dan op 'Save' klik, dan ga ik opnieuw een nieuwe ISession openen, de SaveOrUpdate method aanroepen en m'n customer-object als argument doorgeven.
Ook hier blijkt alles op het eerste zicht te werken zoals ik wil: de wijzigingen die ik doorgevoerd heb, worden ook in de DB gepersisteerd.
Echter, als ik even via Sql Profiler kijk wat er nu precies naar de DB gestuurd wordt, dan zie ik dat er ook UPDATE statements uitgevoerd worden voor m'n Order objecten die aan die Customer hangen, alhoewel ik deze niet gewijzigd heb.

Dat zal natuurlijk te maken hebben met het feit dat ik een andere ISession gebruik om m'n Customer op te slaan, en dat die ISession dus niet op de hoogte is van de 'state' van m'n objecten (dirty of niet). Dit is natuurlijk wel behoorlijk vervelend. Ik wil nl. niet dat er UPDATE statements uitgevoerd worden voor objecten die niet gewijzigd zijn.
Nu kan ik er natuurlijk wel voor zorgen dat de Save dezelfde ISession gaat gaan gebruiken als de Load, maar eigenlijk wil ik dit liever niet.
Dit zou nl. willen zeggen dat m'n ISession open blijft staan, zolang m'n CustomerDetailForm open staat.

M'n CustomerDetailForm zou dan bv deze code bevatten:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class CustomerDetailForm
{
    private ISession _sessionObj;

    private Customer _customerObj;

    protected override void Execute( int id )
    {
         _sessionObj = sessionFactoryObj.OpenSession();

        _customerObj = customerRepositoryObj.GetCustomer (_sessionObj, id);

        _sessionObj.Connection.Close();
    }

    protected override void OkButtonClicked()
    {

         _sessionObj.Reconnect();
         _sessionObj.BeginTransaction();

         customerRepositoryObj.SaveCustomer (_sessionObj, _customerObj);

        _sessionObj.Transaction.Commit();
        _sessionObj.Close();
    }
}

Voorbeeld code, ff niet op details letten zoals rollbacken etc... ;)

Dit zou naar mijn mening natuurlijk perfect moeten werken, maar een elegante oplossing vind ik het niet.
Dit houdt nl. in dat m'n Repositories altijd dezelfde ISession moeten gebruiken voor het manipuleren van een entiteit. Zo ook bij het lazy-loaden van de Orders zal m'n repository dus moeten weten dat een bepaalde ISession moet gebruikt worden.

Is er een andere oplossing mogelijk ?

[ Voor 4% gewijzigd door whoami op 01-03-2006 21:42 ]

https://fgheysels.github.io/


  • matthijsln
  • Registratie: Augustus 2002
  • Laatst online: 01-04 15:26
Java-Hibernate heeft hiervoor het "select-before-update" attribuut op het class element, maar in de NHibernate docs zie ik deze niet staan.

Het logisch dat hibernate of gelijk een update moet doen of eerst een select en daarna eventueel een update indien je een detached object gaat saven.

Wil je geen select of updates indien je geen wijzigingen in je object hebt zul je je objecten niet moeten detachen, bijvoorbeeld met het session-per-conversation model (ik weet niet of NHibernate dat ook ondersteunt): http://www.hibernate.org/42.html

  • Gert
  • Registratie: Juni 1999
  • Laatst online: 05-12-2025
10.5. Session disconnection

Misschien dat je daar wat aan hebt. Probleem is dat NHibernate gebasseerd is op zeer oude hibernate specs dus veel van de mooie features zitten er nog niet in.

  • Varienaja
  • Registratie: Februari 2001
  • Laatst online: 14-06-2025

Varienaja

Wie dit leest is gek.

Ik re-attach objecten aan de nieuwe sessie met:
code:
1
    session.lock(eenofanderobject,LockMode.READ);

Dat heb je ook per se nodig als je in een nieuwe sessie elementen van een Set opvraagt die nog niet zijn ingeladen.

Geldt wel voor Hibernate; ik gebruik Java.

Siditamentis astuentis pactum.


  • tijn
  • Registratie: Februari 2000
  • Laatst online: 22-03 21:36
whoami schreef op woensdag 01 maart 2006 @ 21:39:
Stel nu dat ik een paar wijzigingen heb doorgevoerd aan attributen van de entiteit 'Customer', bv z'n naam. Echter, ik heb geen wijzigingen gedaan aan z'n Orders (ik heb deze enkel opgehaald).
Als ik dan op 'Save' klik, dan ga ik opnieuw een nieuwe ISession openen, de SaveOrUpdate method aanroepen en m'n customer-object als argument doorgeven.
Ook hier blijkt alles op het eerste zicht te werken zoals ik wil: de wijzigingen die ik doorgevoerd heb, worden ook in de DB gepersisteerd.
Echter, als ik even via Sql Profiler kijk wat er nu precies naar de DB gestuurd wordt, dan zie ik dat er ook UPDATE statements uitgevoerd worden voor m'n Order objecten die aan die Customer hangen, alhoewel ik deze niet gewijzigd heb.
Misschien kun je in plaats van SaveOrUpdate(), SaveOrUpdateCopy() gebruiken?

Cuyahoga .NET website framework


Verwijderd

Ik wil ook met NHibernate gaan spelen, maar alleen nog de documentatie doorgelezen :D. Als ik de documentatie goed heb begrepen, zou de SessionFactory moeten weten wat de 'state' van een entity is. Creeër je deze misschien meedere keren in je applicatie?

  • whoami
  • Registratie: December 2000
  • Laatst online: 07-04 22:26
Nee, je Session weet wat de state van een entity is, en dat zorgt een beetje voor mijn probleem hier.
Een Session kan je meerdere keren binnen een applicatie creeëren; die is niet zo 'duur'.

Een SessionFactory maak je slechts 1x, deze is wel duur om te maken.

Thx, voor alle tips alvast. Ik zal ze eens bestuderen. :)

https://fgheysels.github.io/


  • zneek
  • Registratie: Augustus 2001
  • Laatst online: 08-02-2025
Heeft je probleem niet te maken met je cascading instellingen? Wat jij beschrijft is toch precies de bedoeling van cascade="save-update"?

Uit de Hibernate docs:
If a parent is passed to save(), update() or saveOrUpdate(), all children are passed to saveOrUpdate()
Die order objecten worden dus sowieso naar de saveOrUpdate gepassed. De essentie is dus puur die dirty check. Nu ik er zo over nadenk is mij dat ook wel eens opgevallen dat al die gerelateerde objecten mee-gesaved worden, ook al zijn ze niet gewijzigd. Weet je wel zeker dat dat dirty check mechanisme erin zit? Haal eens een customer op, wijzig niets, en pass hem dan naar de saveOrUpdate, in dezelfde sessie. Zie je dan een UPDATE statement langskomen? En als je hetzelfde doet, maar dan de saveOrUpdate in een 2de sessie?

Ik gebruik overigens ook Java/Hibernate met openSessionInViewFilter, oftewel, 1 Hibernate session per HttpRequest. Vroeger werkten we ook op de manier zoals jij beschrijft, maar daar zijn we omwille van o.a.lazy loading van sets vanaf gestapt.

  • tijn
  • Registratie: Februari 2000
  • Laatst online: 22-03 21:36
zneek schreef op donderdag 02 maart 2006 @ 23:10:
Die order objecten worden dus sowieso naar de saveOrUpdate gepassed. De essentie is dus puur die dirty check. Nu ik er zo over nadenk is mij dat ook wel eens opgevallen dat al die gerelateerde objecten mee-gesaved worden, ook al zijn ze niet gewijzigd. Weet je wel zeker dat dat dirty check mechanisme erin zit?
Nou en of dat er in zit. Moet je voor de grap maar eens een object graph met 100000 objecten saven zonder dat er wat gewijzigd is. Hij dirty-checkt zich een ongeluk zonder dat er ook maar 1 update uitgevoerd wordt :).
Ik gebruik overigens ook Java/Hibernate met openSessionInViewFilter, oftewel, 1 Hibernate session per HttpRequest. Vroeger werkten we ook op de manier zoals jij beschrijft, maar daar zijn we omwille van o.a.lazy loading van sets vanaf gestapt.
Dat is een 'luxe' die je je bij webapplicaties kunt permitteren. Binnen WinForms (of andere fat client) programma's ligt dat toch wel wat anders omdat je objecten veel langer 'leven' en je toch je sessie's relatief kort wilt houden. Dan loop je vanzelf tegen de issues aan zoals in de TS gemeld.

Cuyahoga .NET website framework


  • EfBe
  • Registratie: Januari 2000
  • Niet online
zneek schreef op donderdag 02 maart 2006 @ 23:10:
Ik gebruik overigens ook Java/Hibernate met openSessionInViewFilter, oftewel, 1 Hibernate session per HttpRequest. Vroeger werkten we ook op de manier zoals jij beschrijft, maar daar zijn we omwille van o.a.lazy loading van sets vanaf gestapt.
1 session per request? Moet hij dan niet iedere keer de mappingfile parsen? Lijkt me een performance killer van jewelste in een systeem met 100+ entities.

Hibernate gebruikt shadow copy dirty checking, waarbij values vergeleken worden met een copy in de session en indien gewijzigd dan wordt de entity als dirty gemarkeerd. Ik weet niet hoe de proxy's van binnen werken (dus of ze triggers hebben voor field setters die dan accuut de entity dirty markeren) maar het lijkt me dat die info of een entity dirty is of niet in de session besloten moet worden en niet in een entity is opgeslagen.

[ Voor 31% gewijzigd door EfBe op 03-03-2006 09:42 ]

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • whoami
  • Registratie: December 2000
  • Laatst online: 07-04 22:26
De schema-mapping files worden volgens mij enkel geparsed bij het maken van je SessionFactory, en normaal gezien maak je die slechts 1x.

https://fgheysels.github.io/


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

EfBe schreef op vrijdag 03 maart 2006 @ 09:40:
[...]

1 session per request? Moet hij dan niet iedere keer de mappingfile parsen? Lijkt me een performance killer van jewelste in een systeem met 100+ entities.
Ik vind het een vreemde opmerking van je. Jij had ook wel kunnen bedenken dat je maar een keer hoeft te parsen en daarna de opgebouwde structuren kunt hergebruiken.

  • Not Pingu
  • Registratie: November 2001
  • Laatst online: 01-04 20:36

Not Pingu

Dumbass ex machina

whoami schreef op vrijdag 03 maart 2006 @ 09:49:
De schema-mapping files worden volgens mij enkel geparsed bij het maken van je SessionFactory, en normaal gezien maak je die slechts 1x.
Precies, daarbij wordt overal aangeraden om de SessionFactory als singleton te gebruiken en per request een session aan te maken. De sessionfactory constructor parsed de mapping files.

Certified smart block developer op de agile darkchain stack. PM voor info.


  • matthijsln
  • Registratie: Augustus 2002
  • Laatst online: 01-04 15:26
Gunp01nt schreef op vrijdag 03 maart 2006 @ 10:11:
[...]Precies, daarbij wordt overal aangeraden om de SessionFactory als singleton te gebruiken en per request een session aan te maken. De sessionfactory constructor parsed de mapping files.
Wat Hibernate dan trouwens ook doet is voor alle entiteiten DML queries (insert, update, delete) aanmaken. Dus als je je afvraagt waarom Hibernate van bij het saven van een entiteit alle kolommen update: dat is meestal sneller dan de SQL query elke keer opnieuw opbouwen. Je kan het per entiteit wel instellen met "dynamic-update" en "dynamic-insert" trouwens.

  • whoami
  • Registratie: December 2000
  • Laatst online: 07-04 22:26
SaveOrUpdateCopy doet idd enkel de gewijzigde rows updaten, maar dit zorgt er natuurlijk wel voor dat het gedetachte object , eerst in de sessie wordt ingeladen, wat dus een paar selects met zich meebrengt.
Nu is de vraag natuurlijk wat er in dit geval gebeurt als user A object 1 inlaad, en wijzigt, en in tussentijd user B datzelfde object ook ingeladen heeft wijzigt en saved voordat user A dat gedaan heeft.
Ik bedoel: zijn de wijzigingen van user B dan weg, of, wordt er een concurrency exception gegooid ?
(Waarschijnlijk moet ik hiervoor aan de slag met het version attribute oid van nhibernate).

Het verhaal van long sessions e.d. zal ik dit weekend eens doornemen.

https://fgheysels.github.io/


  • EfBe
  • Registratie: Januari 2000
  • Niet online
Alarmnummer schreef op vrijdag 03 maart 2006 @ 09:58:
[...]
Ik vind het een vreemde opmerking van je. Jij had ook wel kunnen bedenken dat je maar een keer hoeft te parsen en daarna de opgebouwde structuren kunt hergebruiken.
Ja duh, dat weet ik, maar als je dat in een session object doet dan ben je ze kwijt nadat de session is weggekegeld, vandaar. Ik wist niet dat er een sessionfactory was, en dus indien ik dat had geweten was het duidelijk geweest dat die dus de files parst en niet de session.
matthijsln schreef op vrijdag 03 maart 2006 @ 13:07:
[...]
Wat Hibernate dan trouwens ook doet is voor alle entiteiten DML queries (insert, update, delete) aanmaken. Dus als je je afvraagt waarom Hibernate van bij het saven van een entiteit alle kolommen update: dat is meestal sneller dan de SQL query elke keer opnieuw opbouwen. Je kan het per entiteit wel instellen met "dynamic-update" en "dynamic-insert" trouwens.
Ik weet niet of dit sneller is, ik denk eerlijk gezegd dat het trager is. De code die een gemiddelde query engine moet doorlopen om een fatsoenlijke query te bouwen is veelal straight forward en niet echt een bottleneck. Wat je wint is het feit dat je bv niet fields update die niet gewijzigd zijn, en dat kan qua data die naar de server gaat nogal wat schelen, EN de query zelf is trager in de executie.

Verder is het zo dat bv bij inserts je helemaal niet wilt dat de insert query al vaststaat. Immers, stel je hebt 4 nullable fields in je table, dan wil je toch echt dat het van de data afhangt die in de te inserten entity zit welke fields NULL worden. Wanneer je een insert query van te voren maakt, is deze dus niet bij machte daarin flexibel te zijn en zal nooit een NULL opleveren voor die fields, tenzij je extra logica toevoegt, maar dan moet je bv de query alweer opdelen in fragments en dan win je nog niet veel.

[ Voor 56% gewijzigd door EfBe op 04-03-2006 11:14 ]

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • matthijsln
  • Registratie: Augustus 2002
  • Laatst online: 01-04 15:26
EfBe schreef op zaterdag 04 maart 2006 @ 11:09:
[...]
Ik weet niet of dit sneller is, ik denk eerlijk gezegd dat het trager is. De code die een gemiddelde query engine moet doorlopen om een fatsoenlijke query te bouwen is veelal straight forward en niet echt een bottleneck. Wat je wint is het feit dat je bv niet fields update die niet gewijzigd zijn, en dat kan qua data die naar de server gaat nogal wat schelen, EN de query zelf is trager in de executie.

Verder is het zo dat bv bij inserts je helemaal niet wilt dat de insert query al vaststaat. Immers, stel je hebt 4 nullable fields in je table, dan wil je toch echt dat het van de data afhangt die in de te inserten entity zit welke fields NULL worden. Wanneer je een insert query van te voren maakt, is deze dus niet bij machte daarin flexibel te zijn en zal nooit een NULL opleveren voor die fields, tenzij je extra logica toevoegt, maar dan moet je bv de query alweer opdelen in fragments en dan win je nog niet veel.
Hibernate bind voor een null waarde voor een kolom gewoon null aan de betreffende query parameter. Daarnaast kan de query gecached worden waardoor alleen maar de parameters over de lijn moeten. Bij een insert lijkt me deze stategie altijd sneller. Voor wat update queries betreft denk dat het van de grootte en aantal kolommen van de tabel afhangt wat sneller is. Of de executie van de query trager is weet ik niet, misschien bij column-level locking (niet echt standaard geloof ik).

  • whoami
  • Registratie: December 2000
  • Laatst online: 07-04 22:26
Ik ben wat verder aan het kijken naar long sessions binnen NHibernate, en dat zorgt natuurlijk wel voor een oplossing van m'n probleem.
Echter, dit zorgt er wel voor dat ik in m'n presentatie laag meer met de infrastructuur moet bezig zijn.

Bv, op een detail-form heb ik deze method:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private ISession _session;
private Customer _cust;

public void Execute( int id )
{

    _session = sessionFactoryObj.OpenSession();

    _cust = customerRepositoryObj (_session, id);

    _session.Disconnect();

    // ... databindings...
    this.Show();
}


Hier zie je dus dat ik een session open, deze doorgeef aan m'n customer-repository die de customer ophaalt, en daarna de onderliggende connectie met de databank sluit.
De session zelf blijft 'geldig' gedurende de hele 'looptijd' van m'n form.
Als er op 'OK' geklikt wordt, heb ik danbv deze code:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void OkButton_Click()
{
    if( _session.IsConnected == false )
    {
         _session.Reconnect();
    }

    _session.BeginTransaction();

    try
    {
         customerRepositoryObj.SaveCustomer (_sessoin, _cust);
         _session.CommitTransaction();
    }
    catch
    {
         _session.RollbackTransaction();
    }
    finally
    {
        _session.Close();
    }
}

Zoals je ziet, zit er heel wat 'hibernate-infrastructuur' code in die presentatie laag, en dat vind ik nogal vervelend. Misschien kan ik er een wrapper rondbouwen...

Wat me ook opvalt, is dat ik geen enkel voorbeeld op het internet ben tegengekomen, waar de transactie-afhandeling buiten de repositories/DAO's ligt. Ikzelf vind het geen goed idee om de transaction-handling binnen de DAO/repository te leggen, aangezien die de context van die transactie niet weet... Het kan nl. zijn dat ik bv 2 customers in één en dezelfde transactie wil opslaan, dan kan die 'starttransaction' en committransaction niet binnen je DAO brengen.

https://fgheysels.github.io/


  • tijn
  • Registratie: Februari 2000
  • Laatst online: 22-03 21:36
whoami schreef op zondag 05 maart 2006 @ 15:58:
Zoals je ziet, zit er heel wat 'hibernate-infrastructuur' code in die presentatie laag, en dat vind ik nogal vervelend. Misschien kan ik er een wrapper rondbouwen...
Zou ik zeker doen. Maak service classes die de transacties regelen etc. Deze hebben dus een referentie naar NHibernate en de repositories.
Wat me ook opvalt, is dat ik geen enkel voorbeeld op het internet ben tegengekomen, waar de transactie-afhandeling buiten de repositories/DAO's ligt. Ikzelf vind het geen goed idee om de transaction-handling binnen de DAO/repository te leggen, aangezien die de context van die transactie niet weet... Het kan nl. zijn dat ik bv 2 customers in één en dezelfde transactie wil opslaan, dan kan die 'starttransaction' en committransaction niet binnen je DAO brengen.
Helemaal mee eens en volgens mij is er ook niet veel mis mee om een ISession in de constructor mee te geven.

Cuyahoga .NET website framework


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

whoami schreef op zondag 05 maart 2006 @ 15:58:
Ik ben wat verder aan het kijken naar long sessions binnen NHibernate, en dat zorgt natuurlijk wel voor een oplossing van m'n probleem.
Echter, dit zorgt er wel voor dat ik in m'n presentatie laag meer met de infrastructuur moet bezig zijn.
Correct. Totale abstractie van je datalaag is een illusie.
Zoals je ziet, zit er heel wat 'hibernate-infrastructuur' code in die presentatie laag, en dat vind ik nogal vervelend. Misschien kan ik er een wrapper rondbouwen...
In Spring los ik het op via een aspect (AOP) er om heen te plakken. Op die manier kan je transparant dit soort functionaliteit er om heen plakken zonder dat het erg opdringerig in je code is.
Wat me ook opvalt, is dat ik geen enkel voorbeeld op het internet ben tegengekomen, waar de transactie-afhandeling buiten de repositories/DAO's ligt. Ikzelf vind het geen goed idee om de transaction-handling binnen de DAO/repository te leggen, aangezien die de context van die transactie niet weet...
Exact. Je datalaag hoort alleen gebruik te maken van de sessie/transactie die er al is. In principe hoort hij geen transacties aan te sturen (alhoewel je soms wel eens voor het gemak een transactionele methode erin zet, maar dat is uitzondering).
Het kan nl. zijn dat ik bv 2 customers in één en dezelfde transactie wil opslaan, dan kan die 'starttransaction' en committransaction niet binnen je DAO brengen.
Transacties instellen is 'taak' van de business layer. Er ligt eigelijk een hele dunne laag over je business layer heen die verantwoordelijk is voor transactie coordinatie en vaak ook security.

[ Voor 6% gewijzigd door Alarmnummer op 06-03-2006 08:52 ]


Verwijderd

Alarmnummer schreef op maandag 06 maart 2006 @ 08:43:
[...]
Transacties instellen is 'taak' van de business layer. Er ligt eigelijk een hele dunne laag over je business layer heen die verantwoordelijk is voor transactie coordinatie en vaak ook security.
Is dat wat ik nog wel eens "service-layer" genoemd zie worden?

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Verwijderd schreef op maandag 06 maart 2006 @ 13:48:
[...]


Is dat wat ik nog wel eens "service-layer" genoemd zie worden?
In het boek van fowler heet dit idd de service layer. Ik begin ook een beetje gestoord te raken van de naamgeving.

Verwijderd

Alarmnummer schreef op maandag 06 maart 2006 @ 13:49:
[...]


In het boek van fowler heet dit idd de service layer. Ik begin ook een beetje gestoord te raken van de naamgeving.
Haha, en ik ben dan nog een nieuwkomer op het gebied van OOAD. Kun je je voorstellen hoe 't er voor mij uit ziet ;)
Pagina: 1