Toplink Object-Relational Mapping: Begrip voor de cache

Object-Relational Mapping oftewel ORM is een oplossing van het ‘object relational impedance mismatch’ probleem. Door ORM te gebruiken slaan we een brug tussen Object Oriented Programming en het Relational Model van de database. Als je nog meer wilt weten over ORM, lees dan “Mapping Objects to Relational Databases: O/R Mapping in detail�?.

Tegenwoordig bestaat er een variëteit aan ORM tools waar Java developers uit kunnen kiezen. Deze tools maken het ons mogelijk ons relational model te ontkoppelen en een sterk object model te bouwen. Voorbeelden van deze tools zijn natuurlijk Hibernate, Toplink en JDO. De keuze van de juiste tool voor een specifieke situatie, kan op zich al erg moeilijk zijn. Het is voor nu genoeg om te weten dat alle eerder genoemde tools het werk kunnen doen.

Zoals al eerder genoemd is Toplink zo’n tool en deze kan de klus zeker klaren. Echter, zoals bij iedere tool, als je het verkeerd gebruikt kun je je borst nat maken. Eén ding dat iedere ORM tool moet implementeren is een cache, zonder een cache zal de performance van de tool erg slecht zijn, het heeft een cache nodig om de tijd te minimaliseren dat het spendeert in de database (het aantal DML statements). De meeste leveranciers implementeren een session cache. Een session cache bevindt zich in de application layer. Het houdt informatie over je objecten en de daarop betrekking hebbende tegenhangers in de database vast in het geheugen gedurende de sessie. Normaal gesproken gaat iedere CRUD actie automatisch door de session cache voordat de database wordt gecontroleerd. Het begrijpen van hoe de session cache van Toplink werkt is cruciaal voor het slagen van je project. Het verkeerd begrijpen kan leiden tot gedeeltelijke/verkeerde writes en een moeilijk te onderhouden applicatie. We zullen de meest voorkomende vergissingen bij het gebruik van de Toplink session cache verder bekijken.

Laten we het three-tier Toplink model bekijken. Wat typerend is voor Toplink is de Server Session (of Database Session in een two-thier model). Deze session zorgt voor de server side communication tussen client en server, geeft toegang tot gedeelde bronnen zoals een gedeelde object cache en connection pooling.

Fai 1

Een klant gebruikt een client session om samen met de server session de verbinding client/server te maken. Let op: zowel de client session als de server session bevinden zich op de server. Eén server (één JVM) bevat maar één server session en kan meerdere client sessions bevatten. Een client session wordt runtime verkregen indien nodig en deelt normaal gesproken de session cache van zijn parent server session. Dit maakt efficiënte read operations mogelijk voor meerdere client sessions, client sessions kunnen voordeel halen uit hergebruik van al teruggevonden objects uit de shared cache. In de meeste applicaties worden read operations vaker uitgevoerd dan write operations, daarom is een shared cache een goede oplossing voor het leveren van een betere performance voor deze applicaties.

Een client session levert alleen read access aan de database. Een client session kan een object niet veranderen of verwijderen, om write access te verkrijgen gebruikt Toplink ‘Unit of Work’. Een Unit of Work werkt als een exclusive transactional object space en zorgt ervoor dat alle veranderingen die worden toevertrouwd aan de database ook in de session cache voor komen. Een klant moet een Unit of Work verkrijgen uit de client session voordat het enige modificaties kan maken.

Eenvoudig gezegd werkt een Unit of Work als volgt. Je registreert Objects in de Unit of Work. De Unit of Work zal je daarna een kloon van het Object geven vanuit de shared cache. Je voert modificaties uit aan het Object en vertrouwd deze toe aan de Unit of Work. De Unit of Work vergelijkt jouw gekloonde versie met de versie in de shared cache en calculeert een change set en voert de modificaties uit in de database.

Een voorbeeld, een persoon met zijn/haar adres. We definiëren het volgende erg eenvoudige object model.

Eén persoon kan meerdere adressen hebben:

fai2

Ervan uitgaande dat we de setup van de mapping en de database correct hebben uitgevoerd, kunnen we beginnen met de coded. Eerst halen we een client session op.

SessionManager sessionManager = SessionManager.getManager(); 
Server server = (Server) sessionManager.getSession(new XMLSessionConfigLoader(), "session", Thread.currentThread() .getContextClassLoader(), true, true); // Oh it's the Server Session
Session session = server.acquireClientSession(); // Acquire a Client Session

Nu kunnen we de database afzoeken. Laten we de person table afzoeken naar id.

Person laoTzu = new Person(new Long(1)); // This is Lao Tzu, it's present in the person table
Person sessionCacheObject = (Person) session.readObject(laoTzu); // Read it from the session cache
System.out.println(sessionCacheObject.getFirstname()); // Lao 
System.out.println(sessionCacheObject.getLastname()); // Tzu

So far so good. Het print wat we verwachtten, maar wat werkelijk gebeurde was het volgende: de client session zocht in de shared cache naar het object en omdat het de eerste keer was dat we naar dit object zochten wordt het niet gevonden. De database wordt bevraagd, het object wordt gevonden en daarop aansluitend wordt de shared cache gevuld met het object. Als we nu opnieuw zoeken naar het object, zal het worden opgehaald uit de shared cache. We zullen nu het object aanpassen.

UnitOfWork uow = session.acquireUnitOfWork();
Person uowObject = (Person) uow.registerObject(laoTzu);
uowObject.setFirstname("John");
uowObject.setLastname("Doe");
uow.commit();

Zoals je je herinnert moeten we de Unit of Work nog aan de client session vragen. Vervolgens registreren we de objecten die we willen veranderen. In dit geval veranderen we “Lao Tzu�? in “John Doe�?. We commit’en, een change set wordt gecalculeerd, de query wordt samengevoegd en de naam wordt veranderd naar “John Doe�?. Tot nu toe hebben we hele normale acties uitgevoerd, alles werkt zoals verwacht, geen onvoorziene verrassingen.

Is je het gebruik van de variabele namen uowObject en sessionCacheObject opgevallen in de voorbeelden opgevallen? Ik heb ze daar neergezet met een reden. Kijk maar,

Person laoTzu = new Person(new Long(1)); // This is Lao Tzu again, it's present in the person table
Person person = (Person) session.readObject(laoTzu); // Read it from the session cache
System.out.println(person.getFirstname()); // Lao 
System.out.println(person.getLastname()); // Tzu
UnitOfWork uow = session.acquireUnitOfWork();
Person uowObject = (Person) uow.registerObject(person);
person.setFirstname("John"); // Oops wrong object
person.setLastname("Doe"); // Oops wrong object
uow.commit();

In dit voorbeeld gebeurt er in de database niets, er wordt niets weggeschreven naar de database. Het volgende is fout gegaan: de kloon die door de Unit of Word werd niet veranderd, maar het session cached object uit de shared cache. We hebben ons niet gerealiseerd wat voor object het was. Laten we ervan uitgaan dat een andere client session actief is en deze client ook een mutatie probeert door te voeren. Deze gebruikt de correcte manier om het object te modificeren, namelijk door de kloon van de Unit of Work aan te passen.

Person person = (Person) 
session.readObject(new Person(new Long(1))); // Read it from the session cache
UnitOfWork uow = session.acquireUnitOfWork();
Person uowObject = (Person) uow.registerObject(person);
uowObject.setFirstname("John"); // yes modify the clone
uowObject.setLastname("Doe"); // yes modify the clone
uow.commit();

Op zich zelf is deze code juist en zal het werken. Maar bij commit gebeurt er niets. Dat is vreemd, had je dat verwacht? Waarom is dit gebeurd? Het is ‘jammer’ dat eerder het person object is veranderd uit de shared cache, hierdoor kan de Unit of Work namelijk niet de change set meer correct calculeren. Normaal gesproken wordt het object uit de shared cache met de kloon vergeleken, en omdat deze op dat moment hetzelfde zijn, worden er geen veranderingen gedetecteerd. De database denkt nog steeds dat het person (id=1) “Lao Tzu�? heet, maar de applicatie denkt dat deze reeds “John Doe�? heet. Niemand realiseert zich dat deze verandering niet heeft plaats gevonden, totdat de server opnieuw wordt opgestart.
Dit is één van de meest voorkomende problemen waar de applicatie ontwikkelaars tegen aan lopen wanneer zij Toplink gebruiken. Op een bepaald moment realiseren zij zich niet waar het object vandaan komt, namelijk: de shared cache of de Unit of Work. Dit kan resulteren in het probleem zoals hierboven beschreven, maar dit is wel een heel erg simpel voorbeeld. Probeer het probleem maar eens te vergroten met complexe objecten met veel relaties en meng, per ongeluk, shared objects met kloons uit de Unit of Work. Probeer dan maar eens geen hoofdpijn te krijgen!

De makkelijkste work around is om een naming convention toe te passen. Bijvoorbeeld door alles wat geregistreerd is bij de Unit of Work
Clone te noemen, is een simpel en effectief manier om hiermee om te gaan.

Person person = (Person) 
session.readObject(new Person(new Long(1))); // Read it from the session cache
UnitOfWork uow = session.acquireUnitOfWork();
Person personClone = (Person) uow.registerObject(person);
personClone.setFirstname("John"); // yes modify the clone
personClone.setLastname("Doe"); // yes modify the clone
// maybe if we expand the object model we would do something like this
// personClone.setFather(fatherClone);
// personClone.setMother(motherClone);
uow.commit();

Deze methodiek behoudt de leesbaarheid van je code en helpt je bij het identificeren van de objecten die je gebruikt. Voor meer tips over troubleshooting van je Toplink applicatie, lees “Oracle Toplink Unit of Work Primer�? en de “Oracle TopLink Developers Guide�?.

Keep track of your objects

Woordenlijst

  • Object-Relational Mapping (ORM), is a programming technique that links databases to object-oriented language concepts, creating (in effect) a “virtual object database.” There are both free and commercial packages available that perform object-relational mapping, although some programmers opt to code their own object-relational mapping for their systems.
  • Data Manipulation Language (DML) is a family of computer languages used by computer programs or database users to retrieve, insert, delete and update data in a database.

Useful Links

  1. Mapping Objects to Relational Databases: O/R Mapping In Detail
  2. Oracle TopLink Developer’s Guide
  3. Oracle Toplink Unit of Work Primer

Reageer

RSS feed for comments on this post · TrackBack URI