ČESKÁ ZEMĚDĚLSKÁ UNIVERZITA



Podobné dokumenty
8.2 Používání a tvorba databází

STARÁ DOBRÁ JAVA A PERSISTENCE S CACHÉ

Databáze II. 1. přednáška. Helena Palovská

Ukládání a vyhledávání XML dat

Úvod do databázových systémů

2. blok část B Základní syntaxe příkazů SELECT, INSERT, UPDATE, DELETE

Základy informatiky. 08 Databázové systémy. Daniela Szturcová

Primární klíč (Primary Key - PK) Je právě jedna množina atributů patřící jednomu z kandidátů primárního klíče.

Použití databází na Webu

Základy informatiky. 06 Databázové systémy. Kačmařík/Szturcová/Děrgel/Rapant

GTL GENERATOR NÁSTROJ PRO GENEROVÁNÍ OBJEKTŮ OBJEKTY PRO INFORMATICA POWERCENTER. váš partner na cestě od dat k informacím

Databázové systémy. Doc.Ing.Miloš Koch,CSc.

C# - Databáze úvod, ADO.NET. Centrum pro virtuální a moderní metody a formy vzdělávání na Obchodní akademii T.G. Masaryka, Kostelec nad Orlicí

InnoDB transakce, cizí klíče, neumí fulltext (a nebo už ano?) CSV v textovém souboru ve formátu hodnot oddělených čárkou

Databáze I. 5. přednáška. Helena Palovská

Úvod do Entity Frameworku

Michal Krátký, Miroslav Beneš

1. Programování proti rozhraní

Obsah přednášky. Představení webu ASP.NET frameworky Relační databáze Objektově-relační mapování Entity framework

Marketingová komunikace. 2. soustředění. Mgr. Pavel Vávra Kombinované studium Skupina N9KMK1aPH/N9KMK1bPH (um1a1ph/um1b1ph)

Obsah přednášky 7. Základy programování (IZAPR) Přednáška 7. Parametry metod. Parametry, argumenty. Parametry metod.

Platforma Java. Petr Krajča. Katedra informatiky Univerzita Palackého v Olomouci. Petr Krajča (UP) KMI/PJA: Seminář V. 27. říjen, / 15

SQL - trigger, Databázové modelování

InterSystems Caché Post-Relational Database

Databázové systémy. - SQL * definice dat * aktualizace * pohledy. Tomáš Skopal

Využití OOP v praxi -- Knihovna PHP -- Interval.cz

PB161 Programování v jazyce C++ Přednáška 7

PB161 Programování v jazyce C++ Přednáška 7

Kapitola 1: Úvod. Systém pro správu databáze (Database Management Systém DBMS) Účel databázových systémů

Bridge. Známý jako. Účel. Použitelnost. Handle/Body

Databázové patterny. RNDr. Ondřej Zýka

Business Intelligence

Informační systémy 2008/2009. Radim Farana. Obsah. Dotazy přes více tabulek

Návrh a tvorba WWW stránek 1/14. PHP a databáze

3. Je defenzivní programování technikou skrývání implementace? Vyberte jednu z nabízených možností: Pravda Nepravda

TÉMATICKÝ OKRUH Softwarové inženýrství

TÉMATICKÝ OKRUH Softwarové inženýrství

Základy databází. O autorech 17 PRVNÍ ČÁST. KAPITOLA 1 Začínáme 19

Anotace a Hibernate. Aleš Nosek Ondřej Vadinský Daniel Krátký

Čipové karty Lekařská informatika

Databázové patterny. MI-DSP 2013/14 RNDr. Ondřej Zýka,

Stěhování aplikací. Michal Tomek, Sales Manager

Databáze I. Přednáška 7

Úvod Třídy Rozhraní Pole Konec. Programování v C# Hodnotové datové typy, řídící struktury. Petr Vaněček 1 / 39

DUM 12 téma: Příkazy pro tvorbu databáze

Generické programování

public static void main(string[] args) { System.out.println(new Main().getClass().getAnnotation(Greet.class).text());

8 Třídy, objekty, metody, předávání argumentů metod

Marketingová komunikace. 2. a 3. soustředění. Mgr. Pavel Vávra 9103@mail.vsfs.cz. Kombinované studium Skupina N9KMK3PH (vm3aph)

Využití XML v DB aplikacích

Databázové systémy trocha teorie

Databázové systémy Cvičení 5.2

Oracle XML DB. Tomáš Nykodým

Databázové systémy úvod

O Apache Derby detailněji. Hynek Mlnařík

Jalapeño: pekelně ostrá Java persistence v Caché. Daniel Kutáč Senior Sales Engineer

Tvorba informačních systémů

PŘETĚŽOVÁNÍ OPERÁTORŮ

Programování v jazyku C# II. 5.kapitola

Dolování v objektových datech. Ivana Rudolfová

Semestrální práce z DAS2 a WWW

MATURITNÍ OTÁZKY ELEKTROTECHNIKA - POČÍTAČOVÉ SYSTÉMY 2003/2004 PROGRAMOVÉ VYBAVENÍ POČÍTAČŮ

7. Integrita a bezpečnost dat v DBS

7. Integrita a bezpečnost dat v DBS

Primární klíč, cizí klíč, referenční integrita, pravidla normalizace, relace

Michal Krátký. Tvorba informačních systémů, 2008/2009. Katedra informatiky VŠB Technická univerzita Ostrava. Tvorba informačních systémů

ANOTACE vytvořených/inovovaných materiálů

Tento studijní blok má za cíl pokračovat v základních prvcích jazyka Java. Konkrétně bude věnována pozornost rozhraním a výjimkám.

Relační DB struktury sloužící k optimalizaci dotazů - indexy, clustery, indexem organizované tabulky

Úvod. Boj se zavlečeným impedančním nesouladem na úrovni databáze

MS SQL Server 2008 Management Studio Tutoriál

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

1. Webový server, instalace PHP a MySQL 13

Relační databáze. V dnešní době existuje řada komerčních DBMS, nejznámější jsou:

Databázové systémy. Datová integrita + základy relační algebry. 4.přednáška

Vývoj moderních technologií při vyhledávání. Patrik Plachý SEFIRA spol. s.r.o.

Dědění, polymorfismus

Administrace Oracle. Práva a role, audit

Databázové systémy. Ing. Radek Holý

VZOROVÝ STIPENDIJNÍ TEST Z INFORMAČNÍCH TECHNOLOGIÍ

SQL Server Data Tools (SSDT) RNDr. David Gešvindr MVP: Azure MCSE: Data Platform MCSD: Windows Store

04 - Databázové systémy

Jaký je rozdíl v definicicíh VARCHAR2(20 BYTE) a VARCHAR2(20 CHAR):

4. lekce Přístup k databázi z vyššího programovacího jazyka

Virtual private database. Antonín Steinhauser

PB161 Základy OOP. Tomáš Brukner

Stored Procedures & Database Triggers, Tiskové sestavy v Oracle Reports

S KONFIGURACÍ POVOLENÝCH KOMBINACÍ DĚDICŮ

Návrhové vzory. Jakub Klemsa, Jan Legerský. 30. října Objektově orientované programování.

typová konverze typová inference

Návrhové vzory OMO, LS 2014/2015

PL/SQL. Jazyk SQL je jazykem deklarativním, který neobsahuje procedurální příkazy jako jsou cykly, podmínky, procedury, funkce, atd.

2. přednáška. Databázový přístup k datům (SŘBD) Možnost počítání v dekadické aritmetice - potřeba přesných výpočtů, např.

Nové jazykové brány do Caché. Daniel Kutáč

Statické proměnné a metody. Tomáš Pitner, upravil Marek Šabo

FIREBIRD relační databázový systém. Tomáš Svoboda

7.3 Diagramy tříd - základy

Výchozí a statické metody rozhraní. Tomáš Pitner, upravil Marek Šabo

Vyřešené teoretické otázky do OOP ( )

Funkční objekty v C++.

Transkript:

ČESKÁ ZEMĚDĚLSKÁ UNIVERZITA FAKULTA PROVOZNĚ EKONOMICKÁ Obor systémové inženýrství a informatika BAKALÁŘSKÁ PRÁCE Téma: Mapování dat mezi objektovými programovacími jazyky a relačními databázemi Vypracoval: Vedoucí bakalářské práce: Jan Tauchmann doc. Ing. Vojtěch Merunka Ph.D. Praha 2007

PROHLÁŠENÍ Prohlašuji, že jsem bakalářskou práci na téma: Mapování dat mezi objektovými programovacími jazyky a relačními databázemi zpracoval/a samostatně za použití uvedené literatury a po odborných konzultacích s doc. Vojtěchem Merunkou. V Praze dne 28. 4. 2007...

PODĚKOVÁNÍ Děkuji tímto panu Doc. Merunkovi za odborné vedení a rady při zpracování bakalářské práce. Zároveň děkuji panu Mgr. Julinkovi a jeho zaměstnancům za ochotu při poskytování potřebných podkladů.

ABSTRAKT Práce se v prvních dvou kapitolách zabývá problematikou proč vlastně potřebujeme objektovou persistenci a srovnává ji s jejími relačními a souborově orientovanými alternativami. Ve 3. kapitole se dozvíme, jaké máme možnosti pokud bychom se rozhodli postavit svoji aplikaci právě na přístupu relační databáze ORM. Kapitoly z pořadovými čísly 4, 5 a 6 se zabývají implementací ORM a na příkladech nástroje FORIS.ORM, jehož tvůrcem je autor této práce, se dozvíte něco o technikách a algoritmech, které je možné při tvorbě ORM použít. V poslední, sedmé kapitole definitivně přejdeme od teorie k praxi a ukážeme si praktické využití ORM frameworku na informačním systému knihovny. KLÍČOVÁ SLOVA Persistence, objekt, databáze, programování, modelování, návrh, SQL

Obsah 1 Cíl práce a metodika... 7 1.1 O čem je tato práce?... 7 1.2 Úvod do problematiky... 7 1.3 Objektová persistence dat... 9 2 Porovnání souborových, relačních a objektových přístupů k persistenci... 11 2.1 Definice datového modelu... 11 2.2 Vyhledávání a čtení dat... 12 2.3 Změny dat... 13 2.4 Úpravy existujícího datového modelu... 14 2.5 Transakce... 16 3 Možnosti ORM v současných programovacích jazycích... 17 3.1 Požadavky na databázi a programovací jazyk... 17 3.2 Umístění definice modelu... 18 3.2.1 Definice modelu pomocí DDL příkazů SQL... 18 3.2.2 Definice modelu pomocí externího souboru... 18 3.2.3 Definice modelu pomocí anotací a atributů... 20 3.3 Společný předek pro všechny persistentní objekty?... 21 3.4 Údálostmi řízené programování... 23 3.4.1 Triggery... 23 3.4.2 Validátory... 23 3.5 ORM aplikační server... 24 4 Základy ORM mapování... 27 4.1 Definice objektového modelu... 27 4.2 Projekce modelu do relační databáze... 28 4.3 Třída Session páteř každého O-R Mapperu... 30 4.4 Mapování jednoduché třídy... 31 4.5 Vyhledání záznamu... 32 4.6 Mapování referencí... 33 4.7 Mapování kolekcí... 34 4.8 Object Reader... 35 5 Implementace cachování a zpožděného načítání dat... 38 5.1 Základy cachování dat v ORM... 38 5.1.1 Cachování primárních klíčů... 40 5.1.2 Cachování indexů... 40 5.1.3 Cachování referencí... 41 5.2 Zpožděné načítání kolekcí... 42 5.3 Zpožděné načítání referencí na objekt... 44 5.4 Uvolňování paměti... 45 6 Mapování dědičnosti... 47 6.1 Dědění persistentních objektů... 47 6.2 Typy dědění... 48 6.2.1 Table-per-class... 49 6.2.2 Table-per-hierarchy... 50 6.3 Vrstva oddělující logický a fyzický datový model... 51

6.4 Nepersistentní dědění... 52 7 Praktické využití ORM frameworku... 55 8 Závěr... 56 9 Seznam literatury... 58 10 Přílohy... 59 Příloha A - Ukázka implementace generátoru kódu... 59 Příloha B - Příklad definice modelu knihovny pomocí FORIS.ORM... 61 Příloha C - Implementace Regex valitátoru... 66

1 Cíl práce a metodika 1.1 O čem je tato práce? Moje práce na téma Mapování dat mezi objektovými programovacími jazyky a relačními databázemi se v prvních dvou kapitolách zabývá problematikou proč vlastně potřebujeme objektovou persistenci a srovnává ji s jejími relačními a souborově orientovanými alternativami. Ve 3. kapitole se dozvíte, jaké máte možnosti pokud se rozhodnete postavit svoji aplikaci právě na přístupu relační databáze ORM. Kapitoly z pořadovými čísly 4, 5 a 6 se zabývají implementací ORM a na příkladech frameworku FORIS.ORM, jehož tvůrcem je autor této práce, se dozvíte něco o technikách a algoritmech, které je možné při tvorbě ORM použít. Sedmá kapitola srovnává několik vybraných ORM frameworků, které jsou v současnosti dostupné, snaží se vypíchnout jejich výhody a nevýhody a doporučit specifická řešení pro specifické úkoly. V poslední, osmé kapitole definitivně přejdeme od teorie k praxi a ukážeme si praktické využití ORM frameworku na informačním systému knihovny. 1.2 Úvod do problematiky Problém trvalého ukládání dat existuje ve světě IT snad už od jejího úplného počátku. Do popředí se však patrně dostal až díky rozmachu evidenčních systémů v 80. letech (evidence majetku, hromadné zpracování mezd aj.), kdy se ukázalo, že dosavadní přístupy založené na primitivní serializaci struktur do souborů nejsou pro tento účel nejvhodnější. Bylo tomu tak především z těchto důvodů: Ve strukturách se velmi obtížně hledalo. Pro urychlení hledání musely vznikat další pomocné soubory, dnešní indexy. Mnoho dat v souborech se opakovalo (např. jméno zákazníka bylo obsaženo v souboru klientů, objednávek i faktur) což zabíralo zbytečně mnoho místa. Ještě horší byla potom úprava těchto redundantních dat. Pokud se např. měnila adresa zákazníka, musel daný systém složitě dohledávat a měnit všechny záznamy, jež tuto adresu obsahovaly. Problémy nastávaly i se současným (paralelním) zpracováním více požadavků v jednom okamžiku, kdy do daného souboru potřebovalo zapisovat v jednom okamžiku více procesů. V neposlední řadě bylo na programátorovi, aby správně navrhnul a napsal mechanismy zajišťující konzistenci dat tzv. referenční integritu. Což se v drtivé většině případě případů nepovedlo. Všechny výše uvedené důvody byly impulsem ke vzniku relačních databází a tím i radikální změně chápání ukládaných dat. Namísto struktur, které existují v paměti počítače (kniha, zaměstnanec) a které pouze odkládáme na disk za účelem zajištění jejich

persistence jsme začali pojem DATA chápat tak, jak nám to definovali tvůrci databázových systémů: Do relační tabulky (obdoba souboru na disku) ukládáme kartézské součiny atributů dané tabulky a jejich hodnot Podmnožiny těchto kartézských součinů pak můžeme číst nebo měnit pomocí jazyka SQL, kde sloupce (atributy) tabulky je identifikujeme jejich názvem a řádky pomocí hodnot tzv. primárního klíče dané tabulky. Abychom se vyvarovali budoucím problémům s redundancemi a složitými, někdy až protichůdnými změnami přes více tabulek, měl by správný relační návrh vyhovovat tzv. normálním formám. V praxi to pak většinou zjednodušeně znamenalo to, že programátor psal aplikaci, která interně generovala příkazy SQL, které následně posílala databázovému stroji (RDBMS) k vykonání. Všechny relační databáze vracely výsledky čtení dat velmi podobně jako kartézský součin sloupců a jejich hodnot tj. dvourozměrné pole, se kterým pak aplikace dále pracovala. Tato architektura bývá často označována jako klient-server. DB Vykonej SQL Zpracuj ResultSet Generuj SQL Vrať / zobraz výsledek Požadavek z venku Obrázek 1 Ačkoliv objektové jazyky i vývojová prostředí existovala už podstatně dříve, velký komerční BOOM tzv. objektově orientovaných nástrojů pro vývojáře (PowerBuilder, Delphi) nastal až někdy v polovině devadesátých let. Ty s sebou sice přinesly elegantní řešení návrhu prvků uživatelského rozhraní (s využitím OOP), ale vlastní implementaci business logiky (bloky označené ve schématu jako Generuj SQL a Zpracuj ResultSet ) psal programátor povětšinou stále strukturálně. S datovými entitami se nepracovalo jako s objekty, nýbrž jako s dvourozměrným polem (přesně tak, jak je vracel RDBMS).

A právě zde nastal asi největší rozpor. Objektu Book totiž můžeme poslat zprávu LendTo() (vypůjčit), ale poli hodnot vlastností patřící určité knize nikoliv. emp1:employee 1: LendTo(aReader) Krteček2:Book emp2:employee Není možné 4433 Krtecek Zdenek Miller 1977 Obrázek 2 A právě problematikou, jak z pole hodnot, které nám vrátí relační databáze, udělat skutečný objekt, kterému mohou ostatní objekty posílat zprávy (a naopak jak zapsat objekt v paměti do databáze) se zabývají technologie označované jako ORM Object to Relational Mapping Mapování objektů do relační databáze. 1.3 Objektová persistence dat Podívejme se nyní ještě jednou na obě doposud probrané možnosti persistence, které jsem popsal v úvodu a položme si otázku: Který z těchto dvou přístupů je jednodušší pro vývoj a snáze pochopitelný? Původní, souborově orientovaný (nerelační) data jsou definována jako bajty v paměti představující určitou strukturu (záznam, objekt). Tato struktura má specifické atributy (jméno, datum narození atd) a lze je nějakým způsobem odložit na určité persistentní úložiště (disk, CD, karta, ) Příklad ukládání: Book mybook; mybook.isbn = "123456"; mybook.title = "Krtecek"; byte[] data = Serialize(myBook); File.WriteAllBytes("book.db",data); Příklad načítání dat: int position = ID*sizeof(Book); //physical position in file byte[] data = File.Read("book.db", position, sizeof(book)); Book mybook = Deserialize(data); Relační data již onen zmíněný kartézský součin atributů a hodnot primárního klíče a jsou uložena v relační databázi. V aplikaci se nimi pracuje jako s dvourozměrným polem.

Vložení knížky: string SQL = "INSERT INTO BOOK(ISBN,TITLE) VALUES ('" isbn "', '" title "')"; sqlcon.executesql(sql); Načtení z DB: string SQL = "SELECT * FROM BOOK WHERE ID=" id; DataSet ds = sqlcon.executedataset(sql); Každý z přístupů má své pro a proti: Relační databáze nám sice poskytuje robustní datové úložiště, kontrolu referenční integrity, podporuje indexování dat, transakce a automatické sestavení optimálního prováděcího plánu, ale za cenu toho, že s databází budeme komunikovat pomocí SQL a co je horší, výsledek dotazu nebudou objekty jako v prvním případě, ale ResultSet třída, která je společná pro všechny výsledky volání příkazu SELECT. Naopak souborový přístup, který je služebně starší, nás nutí, abychom si většinu funkčností, které RDBMS už obsahuje, napsali sami. S daty ale pracujeme jako s instancemi struktur (v našem příkladě Book), které jsou klientskému jazyku daleko bližší, než nějaké ResultSety a SQL. Vlastní kód pracující s takovými strukturami je pak jednodušší a lépe čitelný, než kód aplikace využívající relační přístup. Jak správně tušíte, objektový způsob persistence dat používá od každého něco. Na klientovi pracujete s daty podobně jako se strukturami u souborového přístupu: Book book = new Book(); //create book in memory book.title = "Krtecek"; //set some attributes book.isbn = "123456"; sess.save(book); //store to databaze Avšak na pozadí je robustní databázový stroj, který si většinou sám ohlídá referenční intergritu, indexování, transakce atd. Tento stroj může být buď přímo objektový, pak mluvíme o nativní objektové databázi (GemStone, Caché), nebo relační (Oracle, DB2, MSSQL ) s nadstavbou od třetích stran, kde potom mluvíme o tzv. Objektově-relačním mapování. Oné nadstavbě třetích stran, která převádí objekty v paměti na řádky v DB budeme říkat object to relational framework, nebo zkráceně ORM. O objektových databázích se v této práci zmíním pouze okrajově, musím však podotknout, že ačkoliv implementace objektového databázového stroje se od relačního velmi významně liší, způsob práce s oběma řešeními je dosti podobný. Databáze, o kterých jejich výrobce uvádí, že jsou tzv. Objektově orientované, zde probírat nebudeme vůbec, neboť se většinou jedná o klasické relační DB, pouze s podporou ukládání různých objektů v jejich binární formě. Atributy těchto binárních objektů pak pochopitelně není možné indexovat ani jinak speciálně zpracovávat v rámci daného RDBMS a výraz Objektově orientovaný je v tomto případě opravdu pouze dílem specialistů na marketing.

2 Porovnání souborových, relačních a objektových přístupů k persistenci V této kapitole se pokusím shrnout rozdíly mezi třemi základními přístupy k datům. Pod souborovým způsobem persistence si nemusíme nutně představit pouze rozsáhlé úložiště souborů nějakého zastaralého účetního programu, ale například i práci s konfiguračním souborem aplikace, zápis stavu oblíbené počítačové hry (save) nebo dokonce ukládání této bakalářské práce na pevný disk pomocí aplikace MS Word. Pod objektovým přístupem mám na mysli objektové databáze či kombinaci RDBMS a ORM. 2.1 Definice datového modelu U aplikací využívající relační přístup k datům neexistuje přímá vazba mezi verzí aplikace a verzí datového modelu. Jinými slovy musíme hlídat, aby to, co je napsáno v kódu aplikace (např. že pro entitu Book existuje atribut Title) byla skutečně pravda i v databázi. Datový model máme tím pádem vlastně definovaný duplicitně na dvou místech. Problém si ukážeme na jednoduchém příkladu. Nechť v databázi existuje tabulka BOOK definovaná například takto : CREATE TABLE BOOK ( ID int not null, TITLE varchar(200),... ) V aplikaci nám žádný mechanismus není schopen zabránit, abychom omylem zaměnili název atributu TITLE za NAME : sqlcon.execsql("select NAME FROM BOOK"); //"NAME" should be TITLE!!! U souborového přístupu definujeme strukturu dat pouze aplikaci, takže k podobnému problému docházet nemůže. Objektový způsob persistence má podobně jako souborový pouze jeden zdroj metadat a tím je definice persistentních tříd v klientském programovacím jazyce. Pokud využíváme relační databázi a ORM framework, je ORM zodpovědné za udržení konsistence mezi definicí persisteních tříd a schématem v databázi. U objektového modelu definujeme provázanost objektů pomocí skládání a dědění, v relačním pomocí referenční integrity.

2.2 Vyhledávání a čtení dat Co se týče způsobů dotazování se na data, musím konstatovat, že možnosti relačního i objektového přístupu jsou v tomto směru plně dostačující. V relačním světě využíváme standardních nástrojů jazyka SQL jakými jsou selekce (WHERE) a spojení (JOIN), v objektovém hledáme data pomocí operací přímo nad kolekcemi. Ve většině objektových systémů sice existuje také dotazovací jazyk podobný SQL, ale narozdíl od relačního přístupu, tento jazyk v žádném případě netvoří rozhranní mezi aplikací a databází. Objektová DB Relační DB ormsess.get<book>(5); ormsess.getallinstances<book>(); ormsess.getbyreferences<loaditem, Book>(5) SELECT * FROM BOOK WHERE ID=5 SELECT * FROM BOOK SELECT * FROM LOAN WHERE BOOK=5 Aplikace Obrázek 3 Aplikace2 Čtení dat se u relačního a objektového přístupu se výrazně liší. Souborový a objektový přístup pracuje se strukturami, které jsou už v době kompilace známé, zatímco relační přístup využívá ResultSet, na jehož položky se odkazujeme dynamicky - většinou pořadovým číslem nebo pomocí stringových literálů. Relační: //query for data string sql = "SELECT * FROM BOOK"; DataSet ds = sqlcon.executedataset(sql); //print all titles foreach (DataRow row in ds.tables[0].rows) Console.Out.WriteLine(row["TITLE"]); Objektový: //query for data List<Book> books=ormsession.getallinstances<book>(); foreach (Book book in books) Console.Out.WriteLine(book.Title); Na uvedeném příkladu vidíme, že v případě relační databáze se na data dotazujeme select * from... i čteme row[ TITLE ] pomocí stringových literálů, jejichž správnost kompilátor pochopitelně není schopen ověřit.

U objektového přístupu jsou atributy dotazu na data, i čtení výsledků ověřeneny během kompilace. 2.3 Změny dat Pokud aplikace využívá souborový přístup persistence dat, provádí většinou zápis celého souboru při jeho každé změně. Existují i výjimky, ale jejich realizace s sebou přináší skoro vždy řadu dalších problémů a nevýhod. Proto se také v dnešní době používá souborový přístup pouze pro databáze malé velikosti a složitosti. Z pohledu robustnosti je mezi souborovým a relačním přístupem obrovský rozdíl. V dobře navrženém relačním modelu se data nejen mnohem snadněji vkládají, upravují a mažou, ale navíc nad každou takovouto operací sedí RDBMS, který kontroluje, jestli se naše aplikace nepokouší dělat něco, co by mohlo narušit integritu dat. Bohužel ve většině aplikací využívajících relačního přístupu úplně chybí informace o tom, že to co do databáze ukládáme, je vlastně objekt. Spousta programátorů v tom vidí pouze hodnoty prvků uživatelského rozhranní, které na stisk tlačítka Zápis jejich aplikace pouze doplní jako potřebné parametry SQL příkazu INSERT a odešle databázi. INSERT INTO BOOK (TITLE, AUTHOR) VALUES ('Krteček','') SQL databáze Obrázek 4 Bohužel právě na změnu dat se ve naprosté většině aplikací vážou různé další úkoly jako je jejich validace (více kap. 3.4.2), či dovytvoření dalších potřebných záznamů v jiných entitách a jejich naplnění hodnotami. Jakmile se některý z těchto požadavků dodatečně objeví (málokterý zadavatel je schopen požadavky na validace dat formulovat již při návrhu), programátora většinou napadnou 2 možnosti, kam tuto funkčnost implementovat: Kamsi do kódu obvykle si vybere to nejhorší místo např. handler pro tlačítko Update public void button1onclick(object sender) if(textbox1.text=="") MessageBox.Show("Title is required item!"); return; //insert data into database

... Do uložené procedury (či triggeru) pro update příslušné tabulky CREATE TRIGGER t_book_insert BEFORE INSERT as begin if inserted.title is NULL then reaiseerror('title must be specified!') end Implementace business logiky na straně RDBMS (trigger) je obecně špatná, neboť uzavírá možnost distribuovat v budoucnu systém na více aplikačních serverů, zvyšuje závislost na platformě a drobí kód obsahující business pravidla na část aplikační a databázovou, což velmi komplikuje přehlednost. Handler, který se spouští při nějaké akci v GUI je druhý, stejně špatný, extrém. Pokud bude docházet k úpravám téhož objektu nejen pomocí daného tlačítka, ale i v jiném případě užití např. z jiného okna, či přes nově dodělaný web interface - povede toto řešení ke psaní duplicitního kódu. Správné místo pro implementaci business logiky proto, dle mého názoru, může být instanční metoda persistentního objektu, na kterém definujeme dané pravidlo. [DataEntity] class Book public void ValidateMe() if(string.isnullorempty(title)) throw new Exception("Title must be specified!");.... Persistentní objekty automaticky vznikají pouze při použití objektového přístupu. Pochopitelně, že problém popsaný výše lze řešit i neobjektově, např. pouhým refaktorováním extract method, ale objektové řešení je snazší a lépe čitelné pro ostatní. 2.4 Úpravy existujícího datového modelu Datový model upravujeme tehdy, shledáme-li současný model vzhledem k aktuálním požadavkům na rozšíření stávající aplikace nedostatečný. V našem příkladě s knihovnou by jím mohl např. být požadavek na poskytovaní důchodcovských slev z registračních

poplatků v dané knihovně. K implementaci takovéto funkčnosti potřebujeme znát věk čtenáře, který ovšem stávající systém neeviduje. Musí tedy dojít rozšíření datového modelu o datum narození čtenáře. U souborového způsobu persistence obtížnost takové změny závisí na její konkrétní implementaci. Např. pro persistenci založenou na XML by taková změna neměla představovat vůbec žádný problém a XML soubory s původní a novou verzí databáze by spolu byly dokonce oboustranně kompatibilní... Pokud ale pod souborovou persistencí chápeme spíše jednoduchý binární soubor záznamy určité struktury soubory jednotlivých verzí mezi sebou kompatibilní nebudou a spolu s upgradem aplikace musíme provádět i migraci dat. Migrační utilitu navíc musíme vytvořit vlastními silami. Během migrace je aplikace nedostupná pro uživatele. Migrace (z 1.0.0.0 na 2.0.0.0) reader.db Obrázek 5 reader2.db U relačních databází je situace jednodušší v tom, že k úpravě datového modelu není třeba vlastní migrační aplikace. Místo toho můžeme použít příslušného DDL příkazu, který migraci všech položek provede za nás. ALTER TABLE READER ADD BIRTH_DATE DATE Relační DB Integrátor Obrázek 6 Pomocí SQL příkazu INSERT...SELECT dokonce můžeme migrovat data i do úplně jiných struktur. Např následující příkaz rozšíří evidenci čtenářů i o všechny zaměstnance dané knihovny: INSERT INTO READER (FIRST_NAME, LAST_NAME, BIRTH_DATE) SELECT FNAME, LNAME, BIRTH_DATE FROM EMPLOYEE Podstatnou nevýhodou obou přístupů (souborového i relačního) je ale fakt, že aplikace a datový model o sobě de facto nevědí a vlastní upgrade aplikační a databázové části musíme provádět odděleně. Pokud si nenaprogramujeme vlastní kontrolní mechanismus, může velmi jednoduše (např nepozorností migrátora) dojít k situaci, že aplikaci máme v jiné verzi než databázi. Systém se pak jeví jako relativně funkční a chybu poznáme většinou až po několika dnech provozu. Vzhledem k tomu, že přístup z využitím ORM definuje pouze jeden deskriptor datového modelu a tím je vlastní definice persistentních tříd, k podobných chybám docházet nemůže. Aplikace si jednoduše při startu ověří jestli definice databázových tabulek odpovídá třídám persistentních objektů (dále PO) a pokud tomu tak není, provede kroky potřebné k úpravě modelu v databázi. Mechanismus projekce objektového modelu do relační

databáze je popsán v kapitole 4.2. Způsob jakým provádí ORM framework úpravy existujícího datového modelu tak, aby vyhovoval objektovému není součástí této práce. 2.5 Transakce Transakcí rozumíme jako sled určitých operací nad daty, které se vnímají jako jedna atomická akce. Laicky řečeno, vše co běží v jedné transakci se musí provést v jeden okamžik. Pokud dojde k výjimce během transakce, všechna data se vrací do původního stavu (rollback). Pokud bychom psali například bankovní systém, určitě bychom použili transakce pro převod peněz z jednoho účtu na druhý. Operace odepsání částky z účtu A a připsaní na účet B musí proběhnout buď obě najednou nebo žádná z nich. Není možné, aby existovala situace, kdy částka existuje na obou účtech nebo naopak na žádném z nich. Aplikace využívající souborový přístup persistence dat prakticky transakce nevyužívají a pokud přece jen, zamykají většinou celý soubor (všechny záznamy dané entity) nebo používají zámky pro určité operace např. zajišťují, že nelze spustit 2 účetní závěrky najednou. Relační databáze jsou v tomto směru podstatně dál, zamykají většinou pouze jednotlivé záznamy (nebo stránky), které se v rámci dané transakce mění a umožňují definovat různé úrovně izolace, tj. operace, které lze ještě nad daty jež jsou součástí jedné transakce provádět v rámci jiné transakce. Transakce nad objekty lze provádět velmi podobně, pouze s tím rozdílem, že ORM framework běží na aplikační úrovni a umožňuje vývojáři dané aplikace řešit konflikty se změnami záznamů uvnitř transakce individuelně pro každou entitu. Důležitou možností při programování na aplikačním serveru je i možnost doprogramování podpory distribuovaných transakcí, tj. transakcí, kterých se zúčastňuje více (povětšinou heterogenních) systémů. Distribuované transakce obvykle řídíme přes produkt třetí strany tzv. distributed transaction coordinator. Vzhledem k omezenému rozsahu této práce se o transakcích přes ORM zmíním pouze okrajově ve vybraných kapitolách

3 Možnosti ORM v současných programovacích jazycích V následující několika krátkých kapitolách se pokusím představit základní možnosti ORM a vlastnosti, ve kterých se od sebe jednotliví dodavatelé ORM frameworků liší. 3.1 Požadavky na databázi a programovací jazyk Využívat výhod objektové persistence je sice teoreticky možné ve všech objektově orientovaných prostředích, nicméně její implementace by v některých z nich představovala velmi složitý úkol, několikanásobně přesahující rozsah této práce. Uveďme si proto alespoň základní požadavky na vývojové prostředí a relační databázi, kterou chceme pro účely ORM využívat: Požadavky na jazyk a prostředí: Objektově orientovaný jazyk Základní podpora přístupu do databáze (ODBC, JDBC, ADO.NET...) Podpora reflexe pro čtení i zápis Podpora meta atributů výhodou Podpora generických typů výhodou Požadavky na databázi: Základní podpora ANSI SQL Podpora alespoň jednoho kompatibilního rozhranní pro přístup z vybraného programovacího jazyka (ODBC, JDBC...) Podpora uložených procedur views výhodou Pokud bychom použili např. jazyk bez reflexe, museli bychom implementovat pro každý PO ještě metody pro serializaci a deserializaci do DB což by velmi zdržovalo a drobilo vývoj. V případě nekompatibilního rozhranní jazyk-databáze bychom dokonce museli implementovat vlastní JDBC driver či ADO provider! Jako nejvýhodnější se v současné době jeví použití jedné ze dvou pro vývojářskou komunitou dobře známých a věčně soupeřících technologií. Technologií postavených na kompilaci do byte kódu a vykonávání programu pomocí virtuální stroje - Javy či.netu Obě platformy vyhovují všem výše uvedeným požadavkům a existuje pro ně již mnoho produktů třetích stran, které podporu ORM implementují. Nutno ovšem poznamenat, že v současné době není možné obě technologie nějakým rozumně jednoduchým způsobem kombinovat. Ve zbylé části tohoto textu proto budu pod pojmem programovací jazyk myslet především Javu nebo některý z jazyků rodiny.net. Příklady budu uvádět v jazyce C#.NET, který je dobře čitelný jak pro vývojáře Javovské i.net komunity, tak i pro ostatní programátory disponující alespoň základní znalostí C či podobného jazyka.

Co se týče databáze, tak tu si vybírá většinou zákazník (resp. už má většinou nějakou koupenou:-), takže zde na výběr moc nemáme. Naštěstí v dnešní době snad všechny relační databáze nějakým způsobem implementují ANSI SQL i standardní aplikační interface, takže s DB by neměl být problém. Konkrétní příklady SQL uvedené v této práci jsou psané pro Oracle 9i. 3.2 Umístění definice modelu Volba vhodného umístění definice datového modelu bývá jedno z prvních rozhodnutí, které musíme učinit, rozhodneme-li se pracovat s ORM. Různé ORM framoworky podporují různé typy přístupů (nebo jejich kombinace). Na výběr však máme v podstatě tyto tři: definice databázového schématu (SQL), externí XML a meta atributy. Databáze? Model.XML? [DataEntity] public class Book [DataAttribute] public string Title.... Obrázek 7 3.2.1 Definice modelu pomocí DDL příkazů SQL Způsob, který je vhodný pro případ, že chceme vytvořit ORM vrstvou nad již existujícím schématem. Většina ORM frameworků má v sobě zabudovanou podporu generování zdrojových kódů persistentních tříd případně mapovacího XML s již existujícího schématu. Vzhledem však k tomu, že relační databáze by neměla být primárním deskriptorem datového modelu (viz především kapitola 2.4), měla by být tato možnost použita pouze pro prvotní vygenerovaní deskriptoru jednoho z alternativních přístupů uvedených níže. Následné změny schématu v relační databázi by už měl provádět pouze ORM framework na základě neshody definice PO a databázového schématu. Příklad vytvoření třídy book pomocí DDL: CREATE TABLE BOOK ( ID int not null PRIMARY KEY, TITLE varchar(255) not null, AUTHOR_ID int FOREIGN KEY REFERENCES AUTHOR ) Pomocí tohoto přístupu nevytváříme skutečný objektový model, ale model relační, k němuž pak přistupujeme pomocí ORM. 3.2.2 Definice modelu pomocí externího souboru Spočívá v tom, že model definujeme v externím, zpravidla XML souboru. Z tohoto XML souboru pak generujeme schéma to databáze a můžeme generovat i vlastní kód reprezentující třídu v daném jazyce.

Mapovací XML jednoduše popisuje, jaký sloupeček v jaké tabulce se váže na který atribut jaké třídy. Příklad: <mapping> <class name="foris.orm.example.book" table="book"> <id name="id" column="id" type="system.int32"> <generator class="sequence"> <param name="sequence">s_book</param> </generator> </id> <property name="title" column="title" type="java.lang.string" /> <property name="author" column="author_id" type="foris.orm.example.author" /> </class> </mapping> Všimněte si zejména, že cizí klíč Author zde již není typu INT, nýbrž FORIS.ORM.Example.Autor, což je entita reprezentující autora. Generovaný CREATE TABLE je pak stejný jako v příkladě u kapitoly 3.2.1 a generovaná definice PO může vypadat následovně: public class Book public string Title; public Author Author; Tento přístup je používán zejména v jazycích, které neumožňují definovat tzv. meta atributy (viz dále). Příkladem takového jazyka, který sice splňuje požadavky definované v kapitole 3.1, ale pojem meta-atribut nezná na třeba Java až do verze 1.4. Řešení pomocí externího mapovacího souboru má dva podtypy: Kód pro třídy PO generujeme pokaždé po změně mapovacího XML Kód pro PO třídy generujeme pouze pro nové entity a všechny následné úpravy PO provádíme dvojmo - jak ve vlastních objektech tak i v mapovacím XML. Mezi hlavní nevýhody prvního podtypu patří především nemožnost jakkoliv zasahovat do generovaného kódu PO. Tj. PO mohou obsahovat pouze položky odpovídající jednotlivým sloupečkům v DB. Toto řešení je natolik omezující, že od něj každý vývojář dříve nebo později upustí. Nevýhodou druhého podtypu je potom roztříštěnost změn a nutnost provádět s každou změnou definice PO i změny v mapovacím souboru. Zajímavou alternativou k těmto dvěma silně omezujícím řešením jsou projekty jako například XDoclet. XDoclet si můžeme zjednodušeně představit jako program, který analyzuje zdrojový PO a na základě speciálních značek v komentářích u jednotlivých prvků třídy generuje kód mapovacího XML.

/** * @persistent-class BOOK /* public class Book /** * @column /* public string Title.... XDoclet engine Generování mapovacího souboru <mapping> <class name="foris.orm.example.book" table="book"> <id name="id" column="id" type="system.int32"> <generator class="sequence"> <param name="sequence">s_book </param> </generator> </id> <property name="title" column="name" type="java.lang.string" /> Obrázek 8 Pro jazyky, které meta-atributy podporují je však jednoznačně výhodnější neudržovat ani negenerovat žádný externí mapovací soubor, ale využít právě anotací či atributů. 3.2.3 Definice modelu pomocí anotací a atributů Anotacemi (podle javovské terminologie) či atributy (dle.netu) myslíme doplňkové meta-informace, kterými můžeme rozšířit definici třídy nebo její libovolné položky. Jedná se de facto o speciální druh komentáře podobného tomu, který jsem popsal v kapitole 3.2.2, když jsem vysvětloval Xdoclet. Narozdíl od klasického komentáře má však meta-atribut 2 velmi důležité vlastnosti: Kompilátor ověřuje syntaxi meta atributu stejně jako jakékoliv jiné položky třídy Meta atribut je (pomocí reflexe) přístupný i uvnitř zkompilovaného kódu a stává se součástí třídy, ve které je definován. Pro použití v ORM má meta atribut velmi cennou funkci umožňuje nám definovat mapování jazyk-db aniž bychom potřebovali jakýkoliv další externí mapovací soubor, neboť všechny potřebné údaje můžeme získat pomocí reflexe. Příklad definice třídy Book pomocí meta atributu: [DataEntity("BOOK", PK="ID")] public class Book [DataAttribute] [DataIndex( i_book_title )] public string Title; [DataAttribute] public Author Author; Pochopitelně, že žádné duplicitní údaje jako název či datový typ položky, u které požadujeme persistenci už uvádět explicitně v atributu nemusíme, neboť náš ORM framework je schopen získat tyto údaje právě pomocí reflexe. Navíc máme informace, které spolu logicky souvisí pěkně u sebe jednom souboru objekt a popis jakým způsobem se serializuje do databáze. Přitom všem máme danou definici objektu plně pod kontrolou a můžeme do ni libovolně přidávat vlastní metody i další, nepersistentní položky. Výhody meta atributů nepřímo ocení i pracovníci provádějící deployment u zákazníka, neboť jim odpadne nutnost udržovat další externí XML soubor.

Nezbývá mi nic jiného, než toto řešení doporučit všem, kteří používají.net nebo Javu 1.5 a vyšší. Pochopitelně existují i vyjímky, které potvrzují pravidlo. Představme si např., že máme CORE aplikaci, která umí vyhledávat knížky podle názvu. Nad touto aplikací vyvíjíme customizaci pro zákazníka, který si přeje také vyhledávat podle jména autora. Vlastní vývoj probíhá tak, že se od základních tříd dědí třídy custom modelu: Author Name : string Compiled assembly CustomAuthor BirthDate : System.DateTime Obrázek 9 Potřebovali bychom přidat atribut [DataIndex] ke třídě Author. Jak ale vidíte na schématu, sestavení, ve kterém se daná třída nachází je již zkompilované (customizační tým nemá možnost měnit CORE části) a tutíž přidání daného atributu není možné. V takovém případě je asi nejlepším řešením povolit kombinaci mapování - pomocí atributů i externího XML souboru. Ve vlastních custom třídách pak můžeme datový model dále definovat pomocí meta atributů a mapování tříd v CORE části bude možné poupravit právě pomocí externího XML, popsaného v kapitole 3.2.2. 3.3 Společný předek pro všechny persistentní objekty? Existují O-R mappery, které vůbec nevyžadují, aby byly persistentní třídy zděděny od nějakého objektu (např. Hibernate) ale naopak, viděl jsem i implementace (třeba microorm), u nichž už jen pouhý fakt, že třída byla potomkem nějakého předka automaticky znamenal, že bude persistentní. Obě tato extrémní řešení svá pro i proti: Pokud u PO nevyžadujeme, aby dědily ze společného předka ani implementovaly nějaký společný interface, musí nutně všechny metody pracující s PO přijímat nebo vracet object. To může působit problémy hlavně programátorům, kteří daný framework teprve začínají používat. Navíc např. není možné definovat instanční proměnné a metody, které by byly metody společné pro všechny objekty. Naopak pokud budeme systém nutit, aby každou podtřídu určité třídy frameworku chápal automaticky jako persistentní zavřeme si cestu k tzv. nepersistentní dědičnosti (viz 6.4).

V našem ORM frameworku jsme zvolili nejrestriktivnější přístup. Aby byla třída persistetní, musí být odvozena od určitého předka ale zároveň musí o obsahovat atribut [DataEntity] informující framework o její persistenci. i i i i DBObject BeforeSave AfterSave BeforeLoad AfterLoad : int : int : int : int [DataEntity] [DataEntity] [DataEntity] AnyPersistentClass1 AnyPersistentClass2 AnyPersistentClass3 Obrázek 10 Objekt, ze kterého každá třída PO dědí má kromě povinného PK a i několik virtuálních metod (s prázdnou implementací), které ORM provolává pří určitých akcích např. jako triggery: public class DBObject /// <summary> /// Executed before real saving into DB /// </summary> /// <param name="user">identification of user who requested given update</param> /// <returns>true if saving is handled inside BeforeSave method, otherwise false</returns> public virtual bool BeforeSave(UserIdentifier user) return false; user).. /// <summary> /// Executed after object is stored into DB /// Useful for modifications after object is saved /// </summary> public virtual void AfterSave(bool inserted, UserIdentifier

3.4 Údálostmi řízené programování Další velmi příjemnou a často neprávem opomíjenou možností, kterou nám ORM nabízí je tzv. inversion of control neboli také event driven programming událostmi řízené programování. Vývojář v tomto případě nevytváří hadler pro konkrétní akci či use case, ale pouze definuje, co se má stát, pokud nastane něco, co danou událost vyvolá - např. dojde-li k zápisu knížky do DB. V našem ORM budeme rozlišovat 2 druhy zpracování událostí: triggery a validace. 3.4.1 Triggery Podobně jako u databázových triggerů se i aplikační triggery spouští při nějaké akci nad objektem ve vztahu k jeho persistenci. Tou akcí může být buď načtení objektu (select) nebo zápis jeho stavu (insert, update, delete). Vlastní kód triggeru napíšeme přímo do objektu jako přepsaní metody předka: public override void AfterSave(UserIdentifier user) base.aftersave(user); //call ancestor LogWriter.Log("User " used " saved!"); Aplikační triggery mají oproti svým databázovým kolegům nejméně 2 nesporné výhody: Kód triggeru zná stav aplikace. Pod pojmem stav si můžeme představit všechny dostupné identifikátory z daného aplikačního kontextu (statické položky, singletony či vlastní stav objektu). Tyto identifikátory mohou obsahovat nejrůznější možné informace jako např. údaje o přihlášeném uživateli, jeho opravněních a spoustu dalších aktuálně cachovaných hodnot z DB. Kód triggerů může číst i volat externí zdroje jako jsou externí systemy přístupné přes aplikační rozhranní či soubory lokálně uložené na filesystému. O dalších výhodách triggerů na aplikační úrovní pojednává kapitola 2.3. 3.4.2 Validátory Validační kód bychom sice mohli provádět v triggerech (a složitější validace dokonce dál musíme), ale v případě jednoduchých, často opakujících se validací se jedná o tak specifickou funkčnost, že pro ni většina O-R mapperů implementuje vlastní podporu. Mějme rozhranní IValidator definované například takto: /// <summary> /// Every validators specified in <code>[validation(validatortype,params)]</code> must implements this interface. /// </summary> public interface IValidator /// <summary> /// If validation fails, an exception should be thrown, otherwise nothing should be done.

/// </summary> /// <param name="value">value of specified property</param> void Validate(object value); /// <summary> /// parameter(s) for validator (such as reference table name or regexp). Write only property initialized when validator is created. /// </summary> object[] Parameters set; A například třídu RegexValidator, která implementuje validaci položky PO proti standardnímu regulárního výrazu: IValidator Parameters : object[] Validate (object obj) : void RegexValidator <<Implement>> Validate (object obj) : void Obrázek 11 Pak můžeme validovat jednotlivé položky PO pomocí regulárního výrazu pouhým přiřazením atributu [Validation]: //author's name must not be longer than 50 characters [Validation(typeof(RegexValidator),"^.[0-50]$")] [DataAttribute] public string Name Atribut [Validation] zajistí provolání příslušného validátoru při každém pokusu o uložení objektu do DB. V případě že validace selže, objekt se nezapíše a klient to pozná prostřednictvím vyhozené výjimky. Kompletní implementaci Regex validátoru naleznete v příloze. 3.5 ORM aplikační server Až do této kapitoly části vlastně popisovali jakýsi ORM framework aniž bychom specifikovali, kde daný kód, který zajišťuje ORM mapování, vlastně běží. Vlastní ORM framework není de facto nic jiného než sada knihoven, které přidáme do projektu stejným způsobem jako jakoukoliv jinou knihovnu třetí strany, jež nám umožní používat public třídy definované uvnitř.

Pokud bychom tedy chtěli vyvíjet aplikaci typu obyčejný klient-server, nic nám nebrání vytvořit takovouto architekturu: sestavení knihovna-klient.exe ORM Framework SELECT * FROM BOOK WHERE ID=5 Relační databáze ormsession.get<book>(5); Aplikace "Knihovna" Obrázek 12 V reálném světě však každý zákazník požaduje, do systému mohlo připojovat více klientů. Každý takový klient, by pak ale používat vlastní instanci ORM s vlastní konfigurací a vlastní cache, což pochopitelně nechceme. Jedním řešením by mohlo být pouhé upřesnění daného diagramu, že aplikace knihovna-klient.exe je webová aplikace. Pokud by ovšem tomu tak nebylo, bylo by ideální mít pouze jeden aplikační server, který by se choval jako objektová databáze: sestavení knihovna-klient.exe sestavení knihovna-server.exe Aplikace "Knihovna" service.getbook(5); ^BookDto Aplikační server ^Book ormsession.get<book>(5); ORM Framework ^ResultSet SELECT * FROM BOOK WHERE ID=5 Relační databáze Obrázek 13 Na schématu nám především přibyla komponenta Aplikační server.

Abychom mohli pokračovat dále ve výkladu, musíme si nejprve popsat, co všechno se při danám požadavku na knížku s ID=5 děje: 1. Klient vytvoří požadavek a zavolá aplikační server přes nějaký standardní interface pro distribuovaná volání (Remoting, RMI...) - service.getbook(5) 2. Aplikační server požadavek přijme a přeloží jej na volání ORM frameworku, který běží jako referencovaná knihovna ve stejném aplikačním kontextu. ormsession.get<book>(5) 3. ORM framework zjistí jestli nemá již příslušný objekt v cache. Pokud ano, vrátí jej a pokračuje krokem 6. Pokud ne, zavolá databázi, aby mu vrátila ResultSet obsahující daný objekt. SELECT * FROM BOOK WHERE ID=5 4. Databáze najde příslušný záznam a vratí ho. - ^ResultSet 5. ORM ResultSet přetransformuje do určité instance persistentní třídy a vrátí aplikačnímu serveru. - ^Book 6. Aplikační server přijme objekt třídy Book. Ten však není možné vrátit volajícímu systému, protože obsahuje spoustu implementací různých metod využívajících reference na sestavení, která klientská aplikace nemá k dispozici. Překopíruje tedy všechny hodnoty persistetních atribututů třídy Book do jednoduché struktury BookDto (DTO=Data Transport Object), která je součástí interface mezi serverem a klientem a vrátí jej BookDto 7. Z pohledu klienta se celá operace jeví jako jednoduché volání funkce GetBook(id), která vrací BookDto... Kroky 3 a 5 tedy provádí ORM framework. Co ale s kroky 2 a 6? A jak vůbec vytvářet interface pro aplikační server a objekty DTO, když primárním deskriptorem datového modelu jsou třídy PO? Je téměř jasné, že v tak přísně typových jazycích jako je Java nebo C# se v tomto případě bez generovaného kódu neobejdeme. Pokud tedy chceme používat vybraný ORM framework jako aplikační server, měli bychom se také podívat, jestli podporuje alespoň generování interface pro aplikační server, v lepším případě i jeho implementace, tj kód prováděný v krocích 3 a 5. Vlastní vývoj konkrétní aplikace s využitím ORM se pak rozpadá do několika málo částí: Tvorbu datového modelu, pomocí definice persistentních tříd Implementaci business logiky pomocí triggerů (kap. 3.4.1) a validátorů (kap 3.4.2) Vygenerování DB schématu Vygenerování API aplikačního serveru (interfacové i implementační části) Tvorbu klienta využívajícího serverové API

4 Základy ORM mapování V této části si vysvětlíme princip ORM mapování a techniky, jakými lze dosáhnout požadovaného chování. 4.1 Definice objektového modelu O definici objektového modelu jsem toho napsal dost již v předchozích kapitolách. Kapitola 2.1 pojednávách o výhodách definice modelu v objektové formě oproti relační, kapitola 3.2 zas rozebírá různé možnosti, jak lze daný definovat. My nyní upustíme od syntaxe, jakou lze daný model definovat, ale zaměříme se na vlastní implementaci metamodelu, tj. objektového modelu, kterým je po načtení konfigurace reprezentován konkrétní model dané aplikace v paměti. Metamodel je základem každého ORM frameworku a měl by obsahovat minimálně následující údaje: Všechny persistetní entity a jejich mapování na tabulky Všechny atributy PO a jejich mapování na sloupce v DB Všechny cizí klíče pro dané atributy a jejich mapování na příslušné kolekce či reference objektového modelu Všechny klíče (indexy) podle kterých je možné vyhledávat Všechna sestavení (assembly) ze kterých byl daný model vytvořen a kolekce entit, které toho sestavení definuje AssemblyProperty IndexProperty 1..1 0..* 0..* Index columns SchemaProperty 1..1 1..* 0..* 1..1 EntityProperty 1..1 0..* 1..* ColumnProperty 0..* 1..1 0..* 1..1 FkColumn FkProperty Obrázek 14

Instance metamodelu vzniká zpravidla při startu aplikace, konkrátně při načítání (parsování) definice daného modelu. Metamodel slouží jako cache pro definici metadat a zároveň odděluje fyzickou definici modelu (DDL, XML soubor, meta-atributy) od logické. To znamená že implementace ORM frameworku pracuje vždy pouze s metamodelem a nikdy nečte žádný externí XML soubor ani atributy persistentních tříd. Naopak o naplnění modelu se starají speciální třídy tzv. MetamodelBuilder, jež mají za úkol naparsovat metadata z konkrétních vstupů (DDL, XML...). Jedná se návrhového vzoru Builder. IMetamodelBuilder Source : object BuildEntities () BuildAttributes () BuildReferences () BuildInheritance () : void : void : void : void XmlMetamodelBuilder AttributeMetamoderBuilder ServiceMetamodelBuilder <<Implement>> <<Implement>> <<Implement>> <<Implement>> BuildEntities () BuildAttributes () BuildReferences () BuildInheritance () : void : void : void : void <<Implement>> <<Implement>> <<Implement>> <<Implement>> BuildEntities () BuildAttributes () BuildReferences () BuildInheritance () Obrázek 15 : void : void : void : void <<Implement>> <<Implement>> <<Implement>> <<Implement>> BuildEntities () BuildAttributes () BuildReferences () BuildInheritance () : void : void : void : void Takes metadata from any external system - global data calalogue Za zmínku stojí tzv. ServiceMetamodelBuilder, což je třída, která narozdíl od svých sourozenců nehledá definice persistentních objektů v žádném z lokálních zdrojů, ale pomocí speciálního interface se připojí ke službě, která globálně definuje všechna metadata pro více systémů. 4.2 Projekce modelu do relační databáze Pokud máme veškerá metadata naparsovaná v jednom metadata modelu, můžeme je využívat k různým účelům. Jedním z nich je např. projekce modelu do relační databáze, tj. generování DDL příkazů, které v DB vytvoří potřebné tabulky, views a uložené procedury, jež bude dále náš ORM framework využívat. Vlastní generátor bychom mohli napsat přímo v použitém programovacím jazyce, ale brzy bychom zjistili, že generování vlastního (navíc platformově závislého) SQL kódu není nic víc, než různé, relativně složité iterování přes kolekce metamodelu a spojování řetězců a proměnných. string result = ""; foreach (EntityProperty entity in Entities) result = "CREATE TABLE " entity.name " ("; foreach (ColumnProperty column in entity.columns) result = column.name" "column.type" "column.constraints", \r\n";

Kód se tak stane brzy velmi nepřehledným a náchylným k chybám. My si zde ale úkážeme implemetaci jednoduchého šablonové orientovaného deklarativního jazyka, pomocí něhož můžeme definovat jednoduché a přehledné šablony pro veškerý SQL kód a dokonce i kód aplikačního serveru popsaný v kapitole 3.5. Jazyk zná pouze 2 konstrukce: $[xxxx] bude nahrazena hodnotou atributu xxxx aktuálního objektu $[yyyy [ opakovanykod ]] zajistí iteraci přes všechny prvky kolekce yyyy patřící aktuálnímu objektu. Idenfitikátor xxxx či yyyy může být buď jednoduchý název vlastnosti aktuálního objektu, nebo složitější cesta přes ukazatele k podobjektům daného objektu zapsaná pomocí známe tečkové notice. Tj např. $[foreignkey.sourceentity.name] představuje název zdrojové entity cizího klíče (viz schema v kapitole 4.1) Každá iterace mění kontext aktuálního objektu na objekt, přes který právě iterujeme. Vše co není uvozeno pomocí konstrukce $[xxx] se kopíruje na výstup jako prostý text. Takže celý zdrojový soubor generátoru vlastně představuje jakousi šablonu, podobně jako tomu je např. jazyku XSLT. K tomu, abychom mohli psát šablony generátoru potřebujeme kromě dvou výšeuvedených definic znát už jen metadatový model popsaný v kapitole 4.1. Kód, který dělá to samé, co ukázka C# kódu na generovaní CREATE TABLE v úvodu této kapitoly by v našem deklarativním jazyce mohl vypadat např. takto: $[Entities[ ]] CREATE TABLE $[Name] ( [$Column[ $[Name] $[Type] $[Constraints], ]])... V obou případech by bylo výsledkem něco jako: CREATE TABLE BOOK ( ID int not null PRIMARY KEY, TITLE varchar(255) not null, AUTHOR_ID int FOREIGN KEY REFERENCES AUTHOR ) pouze s tím rozdílem, že ve druhém případě je kód výrazně jednodušší pro zápis i čtení. Navíc se nám jako vedlejší efekt podařilo vytáhnout závislost na platformě (v tomto případě na DB Oracle) do externího souboru.

4.3 Třída Session páteř každého O-R Mapperu Většina příkladů kódu na využití ORM, které jsem použil v této práci využívají jistý objekt třídy Session (často označovaný jako sess). Tento objekt vlastně představuje vlastní pomyslný můstek mezi vaší aplikací a ORM. Jeho význam je velmi podobný objektům Connection ze světa aplikací, jež využívají relačných databází přímo. Ve třídě Session je vlastně definováno API celého ORM. Jak se ukážeme v následujích kapitolách, objekt třídy Session nám umožňuje hledat, načítat i naopak zapisovat stavy persistentních objektů z/do DB. Každý objekt třídy Session v sobě interně drží jednu instanci připojení (Connection) do DB. Konfigurace toho, ke které konkrátní DB se máme připojit se náčítá z konfiguračních souborů. Proces vytváření session: Načti konfigurace orm.config Vytvoř připojení Connection pool Obrázek 16 Aby si aplikace postavená nad ORM nemusela předávat objekt Session ve všech objektech či metodách jako parametr, bylo by dobré ji zpřístupnit nějak globálně. Otázkou je ale jak, neboť programujeme většinou aplikační server a tam naše aplikace často běží ve více vláknech (threadech). Jedno přípojení do databáze však dokáže v jeden okamžik obsloužit pouze jeden požadavek, použití návrhového vzoru Singleton tedy nepřichází v úvahy, neboť by mohlo docházet ke konfliktům mezi požadavky od různých vláken. Tento problém řeší návrhový vzor thread local, někdy známý také jako thread scope variable (více na http://www.codeproject.com/threads/threaddata.asp) Princip použití je velmi jednoduchý. Třída Session má statickou property Current, jejíž accessor (get metoda), nejprve ověří, jestli jsme v daným vlákně již Session nepoužívali. Pokud zjistí, že ano, vratí již použitý objekt, v opačném případě Session (i s připojením do databáze) nově vytvoří. Z pohledu klienta je pak použití stejné jako by bylo v případě, kdybychom použili singletonu. Session je všude dostupná pomocí statické vlastnosti Current. Session.Current.Save(anObject);

Implementace Session.Current Již použita v rámci threadu? [Ano] Použij stávající Session [Ne] Vytvoř novou Session Viz předchozí schéma Obrázek 17 A dokonce platí: Session a=session.current; Console.Out.Write(Session.Current==a); //vrátí true 4.4 Mapování jednoduché třídy Pokud bychom měli nějakou primitivní persistetní třídu, která nemá žádné reference na jiné PO, odpovídala by jí právě jedna tabulka v DB se stejným počtem i názvy atributů. Třída: [DataEntity("BOOK")] public class Book: DBObject [DataAttribute] public string Title; Book Title : string Definice tabulky v DB: CREATE TABLE BOOK ( ID int not null PRIMARY KEY, TITLE varchar(256) not null ) ID TITLE PK <pi> <pi> BOOK Number Characters (256) <M> Každé instanci tatovéto třídy pak bude v DB odpovídat jeden záznam (řádek) v tabulce odpovídající příslušné entitě. Např. pro volání: Book book = new Book(); book.title = "Krtecek"; sess.save(book); Console.Out.WriteLine("Vlozeno pod ID:"book.Id);