Rozhodl jsem se publikovat první technicky založený článek. Mapování objektů do relační databáze je pro dnešní objektové jazyky občas oříškem. Největší neplechu páchá asociace typu M:N. Ta není dnešními objektovými jazyky přímo podporována. V tomto článku přiblížím, jak úspěšně vysvětlit objektovým jazykům, že hvězdičky můžou svítit na obou stranách.
Cože to vlastně ta asociace typu M:N je? Každý se s ní už určitě setkal. Na diagramu 1 je zobrazen jednoduchý příklad. Učitel může vyučovat několik žáků z různých tříd a zároveň každý žák může být vyučován několika různými učiteli. Dokážu si představit, že obě analytické třídy budou nahrazeny návrhovými třídami a že hodnoty atributů budou uloženy ve dvou tabulkách relační databáze. Ve fázi analýzy je na tento diagram radost pohledět, jenže ve fázi návrhu už tak růžově nevypadá.
Podívejme se nyní do pozdější fáze analýzy, co se s naší školskou delegací stalo. Na diagramu 2 jsou vidět doplněné atributy a operace. Jen objasním, že operace zjistiSeznamZaku() vrací asociativní pole všech objektů typu Žák, které daný objekt typu Učitel vyučuje. Na opačné straně vrací operace zjistiSeznamUcitelu() asociativní pole všech objektů typu Učitel, které vyučují daný objekt typu Žák. Některé jazyky místo pole používají kolekce objektů, tím už se ale náš článek nezabývá. Ostatní atributy a operace popisovat nemusím, protože jsem zvolil opravdu jednoduchý příklad.
Přenesme se nyní mezi řádky nějakého skriptu, který vypisuje ke každému učiteli všechny vyučované žáky. Pokud by se databáze sestávala pouze ze dvou tabulek, jako je vidět v diagramu 3, budou nám stále chybět reference mezi jednotlivými objekty typů Učitel a Žák a náš skript, ať už bude sepsán jakkoli, náš cílený problém nevyřeší a nedokáže ke každému učiteli vypsat všechny jím vyučované žáky. Příklad tabulek je v MySQL.
Takže nám něco chybí. Pojďme se zbavit té ohyzdné asociace typu M:N a rozbijme ji na dvě asociace typu 1:M. Vložme mezi asociaci typu M:N jednu třídu navíc, kterou můžeme pojmenovat speciálním názvem například Výuka nebo jednoduše spojením názvů obou spojovaných tříd, tzn. UčitelŽák. Tato geniální třída bude sloužit jako pojítko mezi našimi třídami. Každá instance bude ukazovat právě na jeden objekt typu Učitel a právě na jeden objekt typu Žák. Jako celek jsem určil třídu Učitel z toho důvodu, že budeme vypisovat žáky, které učitel vyučuje. Objekty typu Učitel jsou tedy odpovědné za životní cyklusy objektů typu Výuka. Stejně tak bychom mohli třídu Výuka přiřadit třídě Žák. Výsledek je zobrazen na diagramu 4.
Na diagramu 5 jsou zobrazeny tabulky relační databáze, které jsou nyní propojené přes cicí klíče (foreign keys) tabulky Výuka.
(pozn. děkuji maartymu za upozornění komentářem, že EA umí modelovat databáze…)
Metoda zjistiSeznamZaku() nyní vybere z tabulky Výuka veškeré řádky, u kterých je idUcitele rovno id hledaného učitele. Metoda vytvoří pole objektů typu Žák, jejichž id se nachází v nalezených řádcích v tabulce Výuka. Toto pole nakonec vrátí. Již zmiňovaný skript nyní může pole projít a vypsat jméno a příjmení.
Tak jsme si osvětlili, jak se manuálně vypořádat s asociací typu M:N. Nad relačními databázemi dnes ale existují ORM nástroje, které se s asociací typu M:N vypořádají automaticky a programátor ani nepocítí, že se v databázi nachází ještě nějaká propojovací tabulka. Takový ORM nástroj pro PHP je například Doctrine, který je inspirován Hibernate pro Javu.
17.02.2009 20:09





Tomáš Dundáček
#1
19.02.2009 07:40
Jen by se dalo zauvažovat, jestli jsi zvolil nejlepší příklad – učitele stejně většinou nezajímá žák jako takový, ale třída jako celek: tzn. kdyby se přes tu spojovací tabulku navázalo id učitele a id třídy, bylo by to datově úspornější… Just a thought…
Lukáš Kubánek
#2
19.02.2009 09:16
kubanek.org
Jo čistě teoreticky máš pravdu, ale koukni se na náš školní systém. Tam by to sedle uplně přesně. Možná by ten příklad zněl lépe, jak říkáš, ale to už je jenom detail.
maarty
#3
22.02.2009 14:14
Hezkej srozumitelnej clanek.
Pekne pochopitelnej priklad.
Chvalim!
Jen dve maly poznamky:
1) V tabulce Vyuka by idUcitele a idZaka mely byt zahrnuty jako cizí klic, nikoliv jako primární. Enterprise architect to umí a to docela dobre! Nerikám, že takhle to nebude fungovat, ale pri modelovani ciste relacni databaze by se ty standardy dodrzovat mely.
2) na tu duhou poznamku sem behem psani ty prvni zapomnel
tak ti aspon pochvalim, zes tam liznul ty kolekce.
Preji hezky den a spoustu takovychto clanku
zdrh
#4
01.03.2009 00:07
www.zdenekhrdina.cz
[1] no těma třídama se to ale zas trochu komplikuje, protože třídy se dělí na skupiny, takže třeba angličtinu nebo tělocvik učí v dané třídě víc lidí. Některé předměty jsou vyučovány pro celou třídu, jiné pro půlky, některé jsou dokonce jako výběrové, kam chodí studenti napříč třídami. Takže z toho vidím řešení v podobě, že student nepatří do jedné třídy, ale do více tříd a tudíž i mezi žákem a třídou je m:n.
Lukáš Kubánek
#5
17.04.2009 16:45
kubanek.org
Článek o asociaci typu M:N týkající se přímo Doctrine naleznete zde.