SVEUČILIŠTE U ZAGREBU FAKULTET ORGANIZACIJE I INFORMATIKE V A R A Ž D I N Danijel Filipović UZORCI DIZAJNA ZA PODATKOVNI SLOJ DIPLOMSKI RAD Varaždin, 2017.
SVEUČILIŠTE U ZAGREBU
FAKULTET ORGANIZACIJE I INFORMATIKE
V A R A Ž D I N
Danijel Filipović
UZORCI DIZAJNA ZA PODATKOVNI SLOJ
DIPLOMSKI RAD
Varaždin, 2017.
SVEUČILIŠTE U ZAGREBU
FAKULTET ORGANIZACIJE I INFORMATIKE
V A R A Ž D I N
Danijel Filipović
Matični broj: 44423/15-R
Studij: Baze podataka i baze znanja
UZORCI DIZAJNA ZA PODATKOVNI SLOJ
DIPLOMSKI RAD
Mentor:
Prof. dr. sc. Dragutin Kermek
Varaždin, rujan 2017.
III
1. Uvod ....................................................................................................................................... 1
2. Osnovne ideje primjene uzoraka dizajna ............................................................................... 2
2.1. Zašto se koriste uzorci dizajna?....................................................................................... 2
2.2. Kako uzorci dizajna rješavaju probleme u dizajnu? ........................................................ 3
3. Predložak za opis uzoraka dizajna ......................................................................................... 5
3.1. Usporedna analiza ........................................................................................................... 5
3.2. Struktura korištenog predloška ........................................................................................ 7
4. Uzorci dizajna za podatkovni sloj .......................................................................................... 8
4.1. Identity Field ................................................................................................................... 9
4.2. Identity Map .................................................................................................................. 11
4.3. Repository ...................................................................................................................... 14
4.4. Ostali uzorci dizajna ...................................................................................................... 17
5. Uzorci dizajna za rad s bazom podataka .............................................................................. 20
5.1. Active Record................................................................................................................. 21
5.2. Data Mapper ................................................................................................................. 29
5.3. Row Data Gateway ........................................................................................................ 35
5.4. Query Object ................................................................................................................. 41
5.5. Table Data Gateway ...................................................................................................... 49
5.6. Unit of Work .................................................................................................................. 54
6. Uzorci dizajna za ORM sustave ........................................................................................... 59
6.1. Uzorci dizajna za preslikavanje ..................................................................................... 59
6.2. Uzorci dizajna hijerarhije nasljeđivanja tablica ............................................................ 60
6.3. Uzorci dizajna za mnogostrukost veza između relacijskih tablica ................................ 60
6.4. Embedded Value ............................................................................................................ 61
6.5. Identity Field ................................................................................................................. 61
6.6. Identity Map .................................................................................................................. 61
6.7. Query Object ................................................................................................................. 62
6.8. Unit of Work .................................................................................................................. 62
7. Refaktoriranje podatkovnog sloja ........................................................................................ 63
7.1. Refaktoriranje programskog koda ................................................................................. 63
7.2. Refaktoriranje baze podataka ........................................................................................ 66
8. Aplikacija ............................................................................................................................. 70
8.1. Opis aplikacije ............................................................................................................... 70
8.2. Korištene tehnologije .................................................................................................... 71
8.3. Struktura baze podataka ................................................................................................ 72
8.4. Klase entiteta ................................................................................................................. 76
8.5. Korišteni uzorci dizajna ................................................................................................ 79
IV
8.6. EntityMapper i EntityMap ............................................................................................. 84
9. Zaključak .............................................................................................................................. 87
Literatura .................................................................................................................................. 88
1
1. Uvod
Cilj ovog rada je izdvojiti i opisati uzorke dizajna koje bi se koristile u podatkovnom sloju za različite
računalne aplikacije. Specifično, izdvojiti će se i opisati uzorci dizajna za podatkovni sloj aplikacije i uzorci
dizajna koji bi se koristili za rad s bazom podataka.
Objasniti će se osnovne ideje primjene uzorka dizajna, tj. objasniti će se zašto se koriste uzorci dizajna i kako
uzorci dizajna rješavaju probleme koji se pojavljuju u dizajnu računalnih sustava. Nakon toga će se u
zasebnom poglavlju odrediti struktura po kojoj će se opisati uzorci dizajna. Također će se u istom poglavlju
spomenuti zašto je potrebno odrediti strukturu opisa uzoraka dizajna.
Zatim, krenut će se s opisom uzoraka dizajna. Uzorci će biti podijeljeni u dvije kategorije, odnosno dva
poglavlja:
• uzorci dizajna za podatkovni sloj i
• uzorci dizajna za rad s bazom podataka,
Svaki uzorak dizajna će biti opisan prema definiranoj strukturi i, u sklopu te strukture, ti opisi će biti
popraćeni s primjerom u programskom jeziku Java.
Nakon objašnjenja uzoraka dizajna u spomenutim poglavljima, zasebno poglavlje biti će posvećeno
objašnjavanju koji od opisanih uzoraka dizajna bi se mogli koristiti u ORM (object-relational mapping)
sustavima1. Poslije toga, slijedi poglavlje posvećeno refaktoriranju na razini podatkovnog sloja.
Zatim, slijedi poglavlje o aplikaciji koja je izrađena u sklopu ovog rada. Aplikacija neće biti objašnjena u
tom poglavlju, već će biti objašnjeni samo dijelovi aplikacije koji se odnose na uzorke dizajne. Poglavlje o
aplikaciji sadržavat će opis aplikacije, opis korištenih tehnologija te opis implementacija uzoraka dizajna.
Opis implementacija biti će popraćeno programskim kodom. Nakon toga, slijedi zaključak cijelog rada.
1ORM sustavi preslikavaju relacijske tablice iz baze podataka u objekte za objektno-orijentirane sustave (i obratno).
Jedan red u relacijskoj tablici predstavlja jedan objekt u OO sustavu.
2
2. Osnovne ideje primjene uzoraka dizajna
Kod objašnjavanja pojma uzorka dizajna autori knjiga, osobito Gamma et al. (1995), Buschmann et
al. (1996) i Fowler (2003), se referenciraju i citiraju arhitekta Alexander Christopher. Christopher je bio
arhitekt, ali njegovo objašnjenje za pojam "uzorak", iako se koristio u kontekstu arhitekture kuća i zgrada,
mnogi autori smatraju prigodnim i u kontekstu programske arhitekture.
Objašnjenje pojma uzoraka dizajna glasi otprilike ovako (Gamma et al., 1995:2; Fowler, 2003:9-10): "Svaki
uzorak opisuje problem koji se stalno pojavljuje u našoj okolini i opisuje osnovno rješenje tog problema na
takav način da se to rješenje može koristiti milijun puta bez ponavljanje načina na kojem je napravljeno."
Ukratko rečeno, uzorak dizajna opisuje problem u dizajnu programske arhitekture i opisuje rješenje tog
problema. Potrebno je napomenuti da uzorak dizajna izričito opisuje rješenje, a ne daje rješenje. Uzorak
dizajna je apstraktne prirode, odnosno ono je predložak kojeg programer može urediti po potrebi kako bi ga
primijenio u kontekstu svoje aplikacije , čime se ostvaruje osobina ponovne iskoristivosti uzoraka (Gamma
et al., 1995:2-3).
Dalje, u ovom poglavlju, razmotriti će se zašto se koriste uzorci dizajna u rješavanju specifičnih problema u
dizajnu programske arhitekture i kako baš oni rješavaju takve probleme.
2.1. Zašto se koriste uzorci dizajna?
Uzorci dizajna se temelje na postojećoj i dobro priznatoj praksi. U knjigama od Gamma et al. (1995) i
Buschmann et al. (1996) kao primjer jednog takvog uzorka dizajna koji je priznat od strane prakse koriste
uzorak Model-View-Controller (nadalje MVC). Po uzoru na te dvije knjige, i ovdje će se ukratko objasniti
spomenuti uzorak dizajna u nadi lakšeg razumijevanja zašto se koriste uzorci dizajna.
MVC je uzorak dizajna za interaktivne sustave koji dijeli aplikaciju na tri komponente (Buschmann et al.,
1996:125-127):
• Model
◦ Sadrži podatke s kojima radi aplikacija i također implementira osnovnu funkcionalnost
aplikacije.
• View
◦ Koristi se za prikaz informacija korisniku. Podaci, koji se koriste u prikazu, se dohvaćaju iz
model-a. Za jedan model može postojati više view-ova.
• Controller
◦ Prima i obrađuje događaje (eng. events) nastale korisničkom interakcijom sa sustavom. Za svaki
view postoji jedan controller.
Prednost uzorka dizajna MVC je što nam ono omogućava da mijenjamo jednu od navedenih tri komponenti
bez značajnog utjecaja na ostale komponente. Npr. ako želimo promijeniti način na koji se prikazuju podaci,
samo će se promijeniti view komponenta dok model komponentu ne treba ni taknuti. Ako se želi promijeniti
3
način na koji se obrađuje neki događaj nastao korisničkom interakcijom, tada će se promijeniti samo
controller komponenta. Može postojati slučaj gdje promjena u view komponenti povlači i promjenu u
controller komponenti (moguće i obratno), ali to je uglavnom u slučajevima gdje bi događaj nastao iz view
komponente, npr. klik na gumb koji je prikazan u view-u.
Iako MVC nije primjer uzorka dizajna za podatkovni sloj, ono je dobar primjer za shvaćanje zašto bi
programer koristio ili barem razmotrio korištenje uzoraka dizajna u vlastitim aplikacijama.
2.2. Kako uzorci dizajna rješavaju probleme u dizajnu?
Dizajniranje arhitekture računalnog sustava ponekad nije jednostavan posao. U objektno-
orijentiranom programiranju potrebno je cijeli sustav rastaviti na objekte. Nakon toga treba definirati veze
između tih objekata, odnosno način na koji ti objekti međusobno komuniciraju. Treba razmotriti kakva
sučelja trebaju nasljeđivati objekti konkretnih klasa ili pak treba definirati jednu klasu čije ponašanje će biti
podijeljeno i prošireno putem njenih podklasa. Također postoji pitanje kreiranja fleksibilnih sustava. Sve te
zadaće, i vjerojatno još više njih, programer susreće tijekom razvoja računalnog sustava, odnosno tijekom
dizajna sustava.
Zato, uzorci dizajna mogu riješiti mnoge takve probleme vezane uz dizajn računalnog sustava i, u neku ruku,
bolje podučiti o objektno-orijentiranom programiranju.
Uzorci dizajna nam mogu pokazati koje apstrakcije ulaze u rješenje nekog problema, bilo da se te apstrakcije
pojavljuju u prirodi ili ne, i pretvoriti ih u objekte (Gamma et al., 1995:12-13). Također nam mogu dati uvid
u količinu objekata koji su potrebni za realizaciju rješenja problema (Gamma et al., 1995:13).
Sučelja, apstraktne klase i konkretne klase su elementi objektno-orijentiranog programiranja. Tijekom
dizajna sustava neiskusnim programerima je ponekad teško zamisliti treba li za neku konkretnu klasu
definirati sučelje ili treba li ta konkretna klasa naslijediti i proširiti ponašanje neke apstraktne klase ili druge
konkretne klase. Gamma et al. (1995:17) govore o razlikama između dviju vrsta nasljeđivanja:
• Klasno nasljeđivanje se koristi kada se želi ponašanje neke klase dijeliti s drugim klasama ili kada
se želi proširiti postojeće ponašanje neke klase putem podklasa.
• Nasljeđivanje sučelja, uz to što ograničava ponašanje klasa koje implementiraju sučelje na
deklarirane metode, također služi kao osnovni tip i svaka klasa koja nasljeđuje to sučelje definira
vlastiti podtip.
Uzorci dizajna ovdje pomažu jer u svojim rješenjima, odnosno strukturama rješenja, opisuju koji objekti
sudjeluju u rješenju i koja sučelja ili klase ti objekti moraju naslijediti, ako ih neki objekt uopće treba
naslijediti.
No, dosad se govorilo o tome kako uzorci dizajna rješavaju probleme u dizajnu na nižoj razini, tj. na razini
objekata, veza između objekata, klasa, sučelja, itd. Treba spomenuti i kako uzorci dizajna rješavaju probleme
u dizajnu na višoj razini, odnosno na razini softvera koji se gradi.
Buschmann et al. (1996:7) govore kako uzorci dizajna pružaju "kostur" funkcionalnosti što omogućuje
4
programerima da na taj "kostur" dodaju ostatak osmišljene funkcionalnosti. Isti autori također uzorke dizajna
opisuju kao pred-definirane artefakte dizajna koji se mogu koristiti kao gradbeni blokovi (eng. building
blocks) za izgradnju arhitekture nekog računalnog sustava.
Zaključno, uzorci dizajna se mogu koristiti kao sredstva za rješenje nekog problema u dizajnu koji je nastao
tijekom razvoja ili kao temelj za razvoj arhitekture računalnog sustava.
5
3. Predložak za opis uzoraka dizajna
Stručne knjige za uzorke dizajna, kao što su to knjige od Gamma et al. (1995), Buschmann et al.
(1996) i Fowlera (2003), napravljene su kao jedan oblik kataloga uzoraka dizajna. Za opis uzoraka dizajna
autori su odredili predložak prema kojem opisuju pojedini uzorak dizajna. Iako ovaj rad nije zamišljen kao
stručna literatura za uzorke dizajna, ipak će se odrediti predložak po kojem će se opisati uzorci dizajna za
podatkovni sloj.
Gamma et al. (1995) i Buschmann et al. (1996) u svojim knjigama su odredili detaljan predložak za opis
uzoraka dizajna, dok je Fowler (2003) odredio znatno jednostavniji predložak koji, u određenim slučajevima,
nije u potpunosti koristio. Uzorci dizajna koji su opisani u ovom radu izvučeni su iz Fowlerove knjige, ali za
te uzorke određen je poseban, vlastiti, predložak za njihov opis koji se koristi u ovom radu.
Nadalje, u ovom poglavlju napraviti će se usporedna analiza predloška za opis uzoraka dizajna koje su
korištene u knjigama od Gamma et al. (1995), Buschmann et al. (1996) i Fowler (2003). Nakon spomenute
analize, pokazati će se struktura predloška koji se koristi za opis uzoraka dizajna u ovom radu.
3.1. Usporedna analiza
U ovoj analizi usporediti će se predlošci koje koriste Gamma et al. (1995), Buschmann et al. (1996)
i Fowler (2003). Specifično, uspoređivati će se koji elementi uzorka dizajna jesu ili nisu opisani u odabranim
predlošcima te, ako jesu, gdje u strukturi svakog predloška jesu opisani.
Jedan od elemenata koji je uključen u sva tri spomenuta predloška je naziv uzorka dizajna. Važno je uzorcima
dizajna dodijeliti naziv jer se time uspostavlja rječnik uzoraka dizajna, što olakšava komunikaciju između
programera (Fowler, 2003:119). Gamma et al. (1995) i Buschmann et al. (1996) u svojim knjigama također
navode alternativne nazive za opisujuće uzorke, što Fowler (2003) u svojoj knjizi nema.
Svi autori opisuju namjere uzorka dizajna, odnosno u par rečenica opisuju što uzorak dizajna radi. Gamma
et al. (1995) navodi namjeru pod posebnim elementom (Intent, hrv. namjera) dok ih Fowler (2003) navodi
kao dio uvoda u uzorak dizajna. Buschmann et al. (2003) daje kratak opis uzorka u imenu uzorka dizajna.
Svi autori opisuju problem kojeg uzorak dizajna rješava i također detaljno opisuju rješenje te spomenuto
rješenje funkcionira. No, sva tri autora problem i rješenje objašnjavaju na različite načine. Kod predloška od
Buschmann et al. (1996) definiran je poseban element u kojem se opisuje problem i poseban element u kojem
se opisuje rješenje. Gamma et al. (1995:6-7) problem i rješenje opisuju pod Motivation (hrv. motivacija) gdje
opisuju scenarije gdje se pojavljuje problem u dizajnu i kako objektna i klasna struktura vezana za opisujući
uzorak dizajna rješava taj problem. Fowler (2003) problem opisuje u svojevrsnom uvodu u uzorak dizajna, a
rješenje opisuje, odnosno diskutira, pod How It Works (hrv. kako radi).
Također, svi autori opisuju i situacije u kojima su opisujući uzorci dizajna primjenjivi. Gamma et al. (1995)
opisuje takve situacije pod Applicability (hrv. primjenjivost), Buschmann et al. (1996) ih opisuje pod Context
(hrv. kontekst) i Fowler (2003) ih opisuje pod When To Use It (hrv. kada koristiti).
I Gamma et al. (1995) i Buschmann et al. (1996) određuju poseban element u strukturi predloška u kojem
6
prikazuju strukturu rješenja kao dijagram klasa. Gamma et al. (1995:7) također, u nekim uzorcima dizajna,
koriste dijagram sekvence za prikaz komunikacije između objekata koji sudjeluju u uzorku, a Buschmann et
al. (1996:20) uz dijagram klasa koristi i CRC kartice (eng. CRC Cards). Također, Buschmann et al. (1996:20-
21) pod Dynamics (hrv. dinamika) pomoću dijagrama sekvence prikazuju ponašanje objekata tijekom rada.
Fowler (2003) ne određuje poseban element predloška u kojem grafički prikazuje strukturu rješenja
opisujućeg uzorka dizajna. Naime, autor u uvodu u uzorak dizajna koristi grafičku reprezentaciju uzorka
dizajna i to uglavnom dijagram klasa, ER model ili, u određenim situacijama, proizvoljnu sliku. Isti autor
također zna koristiti dijagram klasa, ER model ili sekvencijalni dijagram u drugim elementima predloška kao
pomoć pri objašnjavanju.
Predložak od Gamma et al. (1995:7) sadrži element u kojima opisuje objekte i klase koji sudjeluju u realizaciji
uzorka dizajna te koja su njihova zaduženja (Participants, hrv. sudionici) i element u kojem objašnjava kako
ti navedeni sudionici surađuju (Collaborations, hrv. suradnje). Predložak od Buschmann et al. (1996) ne
sadrži takve elemente, ali suradnju između objekata je objašnjavaju kao komunikaciju između objekata i
prikazuju pomoću dijagrama sekvenci pod Dynamics.
Svi autori u svojim predlošcima daju upute za implementaciju opisujućeg uzorka dizajna. Gamma et al. (1995)
i Buschmann et al. (1996) u svojoj strukturi predloška sadrže element Implementation (hrv. implementacija),
dok Fowler (2003) o implementacijskim detaljima raspravlja pod How It Works.
Gamma et al. (1995) i Buschmann et al. (1996) u svojim opisima uzoraka dizajna navode poznate primjene
opisujućeg uzorka. Za to su definirali poseban element strukture predloška za opis uzoraka dizajna, Known
Uses (hrv. poznate primjene). Fowler (2003) takvo nešto nema u strukturi svog predloška, već koristi samo
jednostavne i razumljive primjere.
Gamma et al. (1995:7) u svom predlošku definira element Consequences (hrv. posljedice) u kojem opisuje
rezultate i kompromise korištenja opisujućeg uzorka dizajna. Buschmann et al. (1996) takvo nešto ne sadrži
u strukturi predloška. Fowler (2003:11) opisuje prednosti i nedostatke opisujućeg uzorka pod How It Works,
te opisuje koji su kompromisi odabira tog uzorka dizajna u usporedbi s nekim drugim uzorkom dizajna pod
When To Use It.
Buschmann et al. (1996) u svom predlošku definira elemente Variants (hrv. varijante) i See Also (hrv. vidi
također). Prvi element opisuje razne varijante i specijalizacije opisujućeg uzorka, dok u drugome se
referencira na uzorke dizajna koji ili rješavaju sličan problem ili se poboljšavaju uzorak koji se opisuje
(Buschmann et al. 1996:21). Gamma et al. (1995) ne sadrži takve opise, nego u svom predlošku definira
element Related Patterns (hrv. povezani uzorci) u kojem opisuje uzorke dizajna koji su povezani s opisujućim
uzorkom, koje su im razlike te s kojim uzorcima dizajna se opisujući može zajedno koristiti. Fowler (2003)
ne sadrži išta od navedenoga, već tijekom opisa uzoraka dizajna ponekad referencira druge uzorke u svojoj
knjizi radi usporedbe.
Na kraju, po vlastitom mišljenju i veoma važno, svi već spomenuti autori u svojim knjigama definiraju
element u strukturi predloška u kojoj pokazuju primjer implementacije uzorka dizajna.
7
3.2. Struktura korištenog predloška
Predložak za opis uzoraka dizajna koji će se koristiti u ovom radu sadrži sljedeće elemente:
• Naziv
◦ Učahuruje opis problema, rješenja i ostalih elemenata koje obuhvaća uzorak dizajna i proširuje
rječnik dizajna. Svaki uzorak dizajna će imati svoje potpoglavlje unutar 4. i 5. poglavlja, a naziv
uzorka će biti ujedno biti i naziv potpoglavlja.
• Namjera
◦ Kratak opis što radi uzorak dizajna koji se opisuje.
• Problem
◦ Opisuje problem u dizajnu koji se rješava primjenom uzorka dizajna.
• Rješenje
◦ Opisuje rješenje, odnosno kako uzorak dizajna rješava problem u dizajnu te ograničenja tog
uzorka.
• Struktura rješenja
◦ Dijagram klasa koji prikazuje klase koje ulaze u rješenje i u kakvoj su međusobnoj vezi.
Struktura rješenja neće biti moguća za sve uzorke dizajna koji su opisani u ovom radu.
• Primjer
◦ Jednostavan primjer implementacije uzorka dizajna u programskom jeziku Java ili, u nekim
slučajevima, kao UML dijagram.
8
4. Uzorci dizajna za podatkovni sloj
U ovom poglavlju opisati će se uzorci dizajna koji su specifični za podatkovni sloj aplikacija. Kao
što se može zaključiti prema imenu, podatkovni sloj aplikacija je sloj orijentiran na podatke i uglavnom se
sastoji od:
1. podataka pohranjenih u bazi podataka (npr. u relacijskoj bazi podataka poput PostgreSQL),
2. reprezentacija tih podataka u aplikaciji koja ih koristi (npr. u obliku objekata u programskom jeziku
Java),
3. komunikacije između aplikacije i baze podataka radi izvršavanja operacija kreiranja, čitanja,
ažuriranja i brisanja podataka.
Ovo poglavlje fokusirano je više na uzorke dizajna vezane za prvi i drugi element podatkovnog sloja, dok je
poglavlje 5. Uzorci dizajna za rad s bazom podataka fokusirano na treći element podatkovnog sloja.
Određeni uzorci dizajna mogu pojednostaviti i, u neku ruku, obogatiti podatkovni sloj na razne načine. Neki
uzorci mogu pokazati kako spriječiti kreiranje istog objekta nekog podatka više puta (Identity Map), a neki
uzorci mogu predložiti arhitekturu baze podataka koja bi podržavala različite vrste zavisnosti između
objekata (Foreign Key Mapping, Association Table Mapping).
Nadalje u ovom poglavlju, opisati će se razni uzorci primjenjivi na podatkovnom sloju, a za njihov opis
koristiti će se vlastiti predložak za opis uzoraka dizajna opisan u poglavlju 3. Predložak za opis uzoraka
dizajna.
9
4.1. Identity Field
4.1.1. Namjera
Sprema identifikacijsko polje (primarni ključ) tablice iz baze podataka u objekt kako bi se održao
identitet između objekta u memoriji i reda u bazi podataka (Fowler, 2003:216).
4.1.2. Problem
U relacijskim bazama podataka primarni ključevi se koriste kako bi se razlikovali redovi unutar neke
tablice, ali objekti u memoriji ne zahtijevaju primarni ključ kako bi se razlikovali već svaki objekt ima svoju
referencu (Fowler, 2003:216). Problem se pojavljuje kada objekt u memoriji treba zapisati, odnosno ažurirati,
u bazi podataka.
4.1.3. Rješenje
Rješenje je veoma jednostavno: atribut primarnog ključa iz relacijske tablice treba imati
idgovarajuće polje u objektu za pohranu. No, Fowler (2003:216) navodi nekoliko problema koji se tiču
odabira primarnog ključa.
Prvi problem kod odabira primarnog ključa je odabir značajnog ili beznačajnog ključa. Značajan ključ može
biti OIB neke osobe. Beznačajan ključ je ključ čiju vrijednost automatski generira neka aplikacija ili sama
baza podataka. Iz vlastitog iskustva, preporučuje se korištenje beznačajnih ključeva za identifikaciju redova
u relacijskoj tablici, a ono što bi se smatralo značajnim ključem koristiti samo kao dodatan atribut u tablici.
Drugi problem kod odabira primarnog ključa je odabir jednostavnog ključa ili složenog ključa. Primarni ključ
je ključ koji se sastoji samo od jednog atributa. Složeni ključ se sastoji od dva ili više atributa. Jednostavni
ključ je najjednostavniji za implementaciju u objektu jer je potrebno dodati jedno polje čiji tip podatka
odgovara tipu podatka u bazi podataka (ako je u bazi podataka INTEGER, onda se u objektu može koristiti
int ili long). Za složeni objekt bi trebalo kreirati posebnu klasu koja bi držala listu varijabli koji odgovaraju
odabranim članovima složenog ključa u bazi podataka (Fowler, 2003:218).
Treći problem kod odabira primarnog ključa je odabir tipa podatka za ključ. Fowler (2003:217) preporučuje
korištenje tipova podataka u kojima se usporedba jednakosti brzo izvršava. To su uglavnom cjelobrojni tipovi
podataka.
Zadnji problem kod odabira primanog ključa je odabir između ključa koji je jedinstven na razini tablice (eng.
table-unique key) i ključa koji je jedinstven na razini baze podataka (eng. database-unique key).
Pretpostavimo da se primarni ključevi generiraju na temelju sekvenci u bazi podataka. U slučaju ključeva
koji su jedinstveni na razini tablice, svaka tablica bi imala svoju sekvencu koja generira vrijednost primarnog
ključa. U slučaju ključeva koji su jedinstveni na razini baze podataka, postoji jedna sekvenca za sve tablice
u bazi podataka. Drugi slučaj bi olakšavao implementaciju uzorka dizajna Identity Map jer bi postojala samo
jedna mapa koja bi pohranjivala sve podatke.
Fowler (2003:218-219) također opisuje probleme generiranja vrijednosti ključa, koji se svodi na: prepuštanje
bazi podataka generiranje vrijednosti ključa, korištenje GUID-a ili prepustiti generiranje vrijednosti ključa
10
programerima.
4.1.4. Struktura rješenja
Strukture uzorka dizajna Identity Field ovisi uglavnom o tome koristi li se jednostavni ili složeni
primarni ključ. Slika 4.7.1. prikazuje odnos između klase i relacijske tablice za neki entitet kada bi se koristio
jednostavni ključ. U slučaju na slici, korišten je tip podatka Object, ali u praksi bi se taj tip podatka prilagodio
tipu podatka iz relacijske tablice.
Slika 4.1.1. Odnos između klase i relacijske tablice za neki entitet koji
koristi jednostavni primarni ključ
Slika 4.1.2. prikazuje slučaj kada bi se za neki entitet koristio složeni primarni ključ. U tom slučaju uvodi se
klasa Key koja postaje kompozitni dio klase entiteta.
Slika 4.1.2. Odnos između klase i relacijske tablice za neki entitet koji koristi složeni ključ
Fowler (2003:224) navodi kako bi bilo dobro nadjačati metodu equals() za klasu Key kako bi se ključevi
međusobno mogli uspoređivati prema njihovim vrijednostima, tj. nad Key objektima bilo bi dobro primijeniti
uzorak dizajna Value Object.
4.1.5. Primjer
Jednostavni primarni ključ u objektno-orijentiranom sustavu može biti obično polje u klasi entiteta.
Za složeni ključ bi se trebala kreirati posebna klasa. U ovom primjeru kreirati će se klasa primarnog ključa
koja se može koristiti i za jednostavne primarne ključeve i za složene primarne ključeve.
Takva klasa će objekte, članove primarnog ključa, spremati u polje tipa Object. Također će imati konstruktor,
metodu za dohvaćanje pojedinog člana primarnog ključa, metodu za postavljanje primarnog ključa i
primijeniti će se uzorak dizajna Value Object.
11
Primjer koda 4.1.1. Klasa primarnog ključa
public class PrimaryKey {
private Object[] keyObjects;
public PrimaryKey(Object... keyObjects) {
this.keyObjects = keyObjects;
}
public Object get(int index) {
if (index >= 0 && index < keyObjects.length) return keyObjects[index];
else return null;
}
public void set(Object... keyObjects) { this.keyObjects = keyObjects; }
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PrimaryKey)) return false;
PrimaryKey other = (PrimaryKey) obj;
if (this.keyObjects.length != other.keyObjects.length) return false;
for (int i = 0; i < this.keyObjects.length; i++)
if (!this.keyObjects[i].equals(other.keyObjects[i])) return false;
return true;
}
@Override
public int hashCode() {
return 47 + Arrays.deepHashCode(this.keyObjects);
}
}
4.2. Identity Map
4.2.1. Namjera
Osigurava učitavanje objekata samo jednom tako što ih se sprema u mapu, a kasnije se ti objekti
dohvaćaju iz te mape (Fowler, 2003).
4.2.2. Problem
Ako programer ne bi pazio kako aplikacija učitava podatke iz baze podataka, može doći do
preuzimanja istog podatka na više objekata (Fowler, 2003:195). To dovodi do dvije vrste problema:
1. Radna memorija računala se puni s višestrukim kopijama istog podatka.
2. Ažuriranje može biti problematično jer ako jedan objekt promjeni unutarnje stanje objekta i time
ažurira bazu podataka, ostali objekti će i dalje zadržati staro stanje.
4.2.3. Rješenje
Rješenje je prilično jednostavno: uvede se rječnik koja će držati referencu na učitani podataka iz
baze podataka. Pod rječnikom se misli apstraktni tip podatka rječnik (eng. dictionary) koji se u drugim
12
programskim jezicima (npr. Java) može još zvati mapa (eng. map). Za svaku tablicu trebao bi postojati jedan
rječnik. Ključ za rječnik bi, u pravilu, bio primarni ključ tablice.
U slučaju Web aplikacija, Fowler (2003:197) navodi kako bi trebao postojati jedan Identity Map objekt po
sesiji. Ako postoji jedan Identity Map objekt za cijelu Web aplikaciju, tada spomenuti objekt treba zaštiti od
istovremenih transakcija.
4.2.4. Struktura rješenja
Za uzorak dizajna Identity Map nije zadana neka konkretna struktura rješenja. Sve što je potrebno je
uvesti rječnik unutar kojeg bi se pohranili učitani podaci iz baze podataka i prilagoditi operacija čitanja
podataka tako da:
1. provjeravaju postoji li traženi podatak, odnosno objekt, u odgovarajućem rječniku i ako postoji
vratiti referencu tog objekta,
2. ako podatak ne postoji tada se ono učitava iz baze podataka u objekt, objekt se pohrani u
odgovarajući rječnik i vraća se referenca na taj objekt.
Programer ima slobodu implementirati ovaj uzorak dizajna kako misli da je najbolje. Radi lakšeg korištenja,
možda bi bilo dobro sve bitne operacije sa rječnikom (dodavanje, dohvaćanje i brisanje) učahuriti u zasebnu
klasu. Tako bi za svaku tablicu postojao jedan objekt koji implementira uzorak dizajna Identity Map.
4.2.5. Primjer
Za primjer uzorka dizajna Identity Map pretpostaviti će se da sve klase entiteta nasljeđuju apstraktnu
klasu Entity koja sadrži polje za primarni ključ i metode za dohvaćanje i postavljanje vrijednosti primarnog
ključa. Primjer koda 4.2.1. prikazuje spomenutu apstraktnu klasu.
Primjer koda 4.2.1. Apstraktna klasa Entity
public abstract class Entity {
protected Long id;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
}
Kako bi se implementirao uzorak Identity Map, kreirat će se generička klasa koja će primati sve tipove
podataka koji nasljeđuju klasu Entity. Ta klasa će sadržavati polje tipa Map u kojem će se spremati objekti
entiteta i imati će definirane metode za dodavanje, dohvaćanje i brisanje objekta entiteta, provjeravanje
postojanosti entiteta u mapi, čišćenje te vraćanje popisa objekata koji su filtrirani pomoću parametra tipa
Predicate. Primjer koda 4.2.2. prikazuje tu klasu.
Primjer koda 4.2.2. Generična klasa IdentityMap
public class IdentityMap<T extends Entity> {
protected Map<Long, T> entities = new HashMap<>();
public void add(T entity) {
13
if (entity.getId() != null)
entities.put(entity.getId(), entity);
}
public void remove(Long id) {
entities.remove(id);
}
public boolean contains(Long id) {
return entities.containsKey(id);
}
public void clear() {
entities.clear();
}
public List<T> filter(Predicate<T> predicate) {
List<T> filteredEntities = new ArrayList<>();
entities.forEach((id, entity) -> {
if (predicate.test(entity)) {
filteredEntities.add(entity);
}
});
return filteredEntities;
}
}
Nadalje, tu klasu može naslijediti neka druga klasa koja bi specificirala s kojim tipom entiteta radi (primjer
koda 4.2.3.) ili se tip podatka može specificirati direktno tijekom instanciranja (primjer koda 4.2.4.)
Primjer koda 4.2.3. Nasljeđivanje klase IdentityMap
public class EmployeeMap extends IdentityMap<Employee> {}
Primjer koda 4.2.4. Direktna primjena klase IdentityMap
IdentityMap<Employee> employeeMap = new IdentityMap<>();
U bilo kojem slučaju, potrebno je operacije čitanja podatka izmijeniti tako da prvo provjeri postoji li već
traženi objekt u mapi te vratiti instancu tog objekta ako postoji.
14
4.3. Repository
4.3.1. Namjera
Služi kao posrednik između poslovnog sloja i sloja zaslužnog za preslikavanje podataka (eng. data
mapping layer) tako da se pristupa objektima preko sučelja slično onome koji se koristi za kolekcije (Fowler,
2003:322).
4.3.2. Problem
Fowlerov (2003) opis problema kojeg rješava uzorak dizajna Repository je, prema vlastitom
mišljenju, dosta zbunjujući. Problem se svodi na potrebu za dodavanje dodatnog sloja apstrakcije koji je
zaslužan za ispitivanje (eng. querying) podataka i taj se sloj obično dodaje nad slojem zaslužnim za
preslikavanje podataka (Fowler, 2003:322). Drugim riječima, ovaj uzorak dizajna dobro dođe ako se želi
apstrahirati rad s podacima (dodavanje, brisanje, pretraživanje i ažuriranje) tako da izgleda kao da se radi s
kolekcijom u objektno-orijentiranom programskom jeziku.
4.3.3. Rješenje
Rješenje je napraviti posrednički objekt koji će koristiti objekte zaslužne za preslikavanje podataka
kako bi glumio kolekciju objekata u objektno-orijentiranom sustavu (Fowler, 2003:322). Ovaj uzorak dizajna
sastoji se od nekoliko drugih uzoraka dizajna:
• uzoraka dizajna za preslikavanje podataka između baze podataka i objekata (npr. Data Mapper),
• uzorka dizajna za izgradnju upita kojima će se pretraživati određeni podaci (npr. Query Object).
Uzorak dizajna za izgradnju upita nije potreban, ali dodaje određenu fleksibilnost uzorku dizajna Repository
i, kako navodi Fowler (2003:323), dodaje veliku mjeru korisnosti za ORM sloj. Također, ovaj uzorak dizajna
se može implementirati tako da radi s više različitih baza podataka, odnosno izvora podataka (Fowler,
2003:324). Pod pretpostavkom da implementacija ovog uzorka dizajna ima različite konfiguracije za rad s
vanjskim izvorima podataka, jedan od tih izvora podataka može biti relacijska baza podataka poput
PostgreSQL-a, druga konfiguracija može biti NoSQL baza podataka poput MongoDB-a, treća konfiguracija
može biti opet relacijska baza podataka koja koristi drugačiju sintaksu za određene operacije (npr. definiranje
primarnog ključa koji se sam od sebe inkrementira), itd.
4.3.4. Struktura rješenja
Fowler (2003) u svom opisu uzorka dizajna Repository nije dao konkretni dijagram klasa za prikaz
strukture ovog uzorka već je opisao njegovo ponašanje putem dijagrama sekvence. Ovdje će se ipak koristiti
dijagram klasa i za svaku komponentu tog dijagrama objasniti će se što ona radi. Slika 4.3.1. prikazuje
dijagram klasa za uzorak dizajna Repository.
15
Slika 4.3.1. Dijagram klasa za uzorak dizajna Repository
Elementi prikazani na slici 4.3.1. imaju sljedeće uloge:
• AbstractMapper
◦ Sučelje ili apstraktna klasa koja implementira uzorak dizajna Data Mapper i koristiti će se za
provođenje operacija kreiranja, čitanja, ažuriranja i brisanja podataka iz baze podataka.
• Repository
◦ Sučelje ili apstraktna klasa koja će pomoću AbstractMapper i Criteria raditi s bazom podataka,
a pritom će korisniku izgledati kao da koristi običnu objektno-orijentiranu kolekciju.
• ConcreteRepository
◦ Implementacija sučelja Repository. Svaka implementacija koristi zasebnu implementaciju
sučelja AbstractMapper za preslikavanje podataka. Također, svaka od implementacija radi na
zasebnim objektom koji predstavlja podataka iz odgovarajuće tablice u bazi podataka.
Fowler (2003:324) navodi korištenje specijaliziranih objekata koji implementiraju strategije kojima bi se
proširila funkcionalnost uzorka dizajna Repository. Ti objekti su zapravo specifične implementacije uzorka
dizajna Strategy kojeg opisuju Gamma et al. (1995). Na taj način implementacija uzorka dizajna Repository
ne bi bila ograničena na samo jednu bazu podataka, tj. izvor podataka, nego više njih (Fowler, 2003:324).
Npr. jedna implementacija sučelja Strategy mogla bi se odnositi na PostgreSQL bazu podataka,a neka druga
implementacija istog sučelja bi se mogla odnositi na neku NoSQL bazu podataka poput MongoDB. Slika
4.3.2. prikazuje proširenje dijagrama klase sa slike 4.3.1. tako da koristi uzorak dizajna Strategy.
Slika 4.3.2. Dijagram klasa uzorka dizajna Repository koja koristi Strategy
16
4.3.5. Primjer
Ovaj primjer uzorka dizajna Repository telmeljiti će se na tome da će sve klase entiteta nasljeđivati
apstraktnu klasu Entity. Apstraktna klasa Entity, prikazana na slici 4.3.1., sadrži polje koje predstavlja
vrijednost primarnog ključa te metode za dohvaćanje i postavljanje tog polja. Ta klasa će se koristiti kako bi
se implementacije uzorka dizajna Repository ograničio da koristi samo objekte čije klase nasljeđuju Entity.
Primjer koda 4.3.1. Apstraktna klasa Entity
public abstract class Entity {
protected long id;
public long getID() { return id; }
public void setID(long id) { this.id = id; }
}
Glavna ideja ovog primjera je kreirati osnovnu Repository klasu koja će koristiti objekt uzorka dizajna Data
Mapper i objekt uzorka dizajna Identity Map. Ta klasa bi učahurila operacije s tim objektima kako bi se
formirao mali podsustav koji bi omogućavao osnovno brisanje i ažuriranje tih podataka, ali bi tijekom čitanja
dohvaćene podatke pohranio kako bi ti isti podaci ne bi čitali iz baze podataka već izravno iz memorije.
Primjer koda 4.3.2. pokazuje apstraktnu i generičku klasu Repository.
Primjer koda 4.3.1. Apstraktna klasa Entity
public abstract class Repository<T extends Entity> {
protected AbstractMapper<T> mapper;
protected IdentityMap<T> map;
public Repository(AbstractMapper<T> mapper, IdentityMap<T> map) {
this.mapper = mapper;
this.map = map;
}
public void add(T entity) {
mapper.create(entity);
map.add(entity);
}
public void remove(T entity) {
mapper.delete(entity);
map.remove(entity);
}
public void update(T entity) {
mapper.update(entity);
}
public T get(long id) {
if (map.contains(id)) {
return map.get(id);
}
T entity = mapper.find(id);
map.add(entity);
return entity;
17
}
public List<T> getAll() {
List<T> entities = mapper.findAll();
for (int i = 0; i < entities.size(); i++) {
T currentEntity = entities.get(i);
if (map.contains(currentEntity.getId()) {
entities.set(i, map.get(currentEntity.getId();
} else {
map.add(currentEntity);
}
}
return entities;
}
}
Nadalje, kreirana klasa Repository bi se proširivala putem podklasa koji bi specificirali s kojim tipom entiteta
bi radili (primjer koda 4.3.3.).
Primjer koda 4.3.3. Nasljeđivanje klase Repository
public class CustomerRepository extends Repository<Customer> {
public CustomerRepository() {
super(new CustomerMapper(), new IdentityMap<Customer>());
}
}
4.4. Ostali uzorci dizajna
U ovom potpoglvalju, ukratko će se opisati namjena ostalih uzoraka dizajna koji bi bili primjereni
za upotrebu u podatkovnom sloju. Za razliku od prethodna tri opisana uzorka dizajna, uzorci dizajna u ovom
potpoglavlju neće koristiti predložak za opis koji je definiran u 3.2. Struktura korištenog predloška.
4.4.1. Association Table Mapping
Association Table Mapping (Fowler, 2003:248) je uzorak dizajna koji se primjenjuje na bazu
podataka, odnosno na njenu strukturu. Ovaj uzorak dizajna rješava problem pohrane veze više-više između
dva objekta u obliku relacijske tablice. Rješenje je uvođenje dodatne tablice u bazu podataka koja se sastoji
od vanjskih ključa koji referenciraju relacijske tablice koji sudjeluju u vezi više-više.
4.4.2. Foreign Key Mapping
Foreign Key Mapping (Fowler, 2003:236) se također primjenjuje na bazu podataka i ono služi za
preslikavanje referenci između objekata kao vanjski ključ u relacijskim tablicama. Ovaj uzorak dizajna je
primjenjiv ako su objekti u vezi jedan-jedan ili jedan-više. U slučaju veze više-više primjenjuje se uzorak
dizajna Association Table Mapping.
4.4.3. Class Table Inheritance
Class Table Inheritance (Fowler, 2003:285) se primjenjuje u slučajevima kada se koncept
nasljeđivanja klasa u objektno-orijentiranom sustavu želi implementirati za relacijske baze podataka. Za
svaku pojedinačnu klasu (i apstraktne i konkretne) u hijerarhiji nasljeđivanja se kreira posebna tablica i svaka
18
tablica će imati samo one stupce koji odgovaraju poljima koji su definirano samo za tu klasu.
4.4.4. Concrete Table Inheritance
Concrete Table Inheritance (Fowler, 2003:293) je varijacija na uzorak dizajna Class Table
Inheritance u kojemu se kreira tablica za svaku konkretnu klasu u hijerarhiji nasljeđivanja. U tom slučaju,
svaka podređena tablica će također imati one stupce koji su definirani u nadređenoj tablici.
4.4.5. Single Table Inheritance
Single Table Inheritance (Fowler, 2003:278) je varijacija na prethodne dva Table Inheritance uzoraka
dizajna u kojemu postoji jedna, jedina, relacijska tablica za cijelu hijerarhiju klasa.
4.4.6. Dependent Mapping
Dependent Mapping (Fowler, 2003:262) omogućuje jednoj klasi (vlasniku) preslikavanje podataka
za klasu koja ovisi o njoj. Ovaj uzorak dizajna se koristi samo ako su objekti ovisne klase strogo vezani za
jednog vlasnika, odnosno ako bi se obrisao podatak o vlasniku onda se gube i ovisni podaci.
4.4.7. Metadata Mapping
Metadata Mapping (Fowler, 2003:306) drži detalje o preslikavanju entiteta između objekta i
relacijske tablice u nekom metapodatku. Metapodatak može biti posebna datoteka, koju aplikacija učita kako
bi saznala kako provesti preslikavanje, ili se mogu koristiti anotacije te bi aplikacija očitala detalje
preslikavanja pomoću refleksije. Ovaj uzorak dizajna se može koristiti zajedno s uzorcima za preslikavanja
podataka, a također služi kao temelj za fleksibilan ORM sustav.
4.4.8. Serialized LOB
Serialized LOB (Fowler, 2003:272) služi za preslikavanje strukture podataka nekog objekta, ili graf
objekata, njegovom serijalizacijom u jedan veliki objekt (eng. large object; LOB) koji se pohranjuje u jedno
polje u bazi podataka. Može koristiti serijalizacija objekata koju omogućava programski jezik, tada s graf
objekata preslikava kao BLOB (Binary Large Object), ili se može koristiti mehanizmi koji bi pretvorili graf
objekta u tekstualni oblik (npr. u XML), čime bi se graf objekta preslikavao kao CLOB (Character Large
Object).
4.4.9. Embedded Value
Embedded Value (Fowler, 2003:268) služi za preslikavanje jednog objekta, koji se postoji kao polje
u objektu entiteta, u dva ili više polja u relacijskoj tablici. Radi se o malim objektima koji imaju smisla
postojati u objektno-orijentiranom sustavu, ali u bazi podataka bilo bi suludo im dodijeliti vlastitu tablicu.
4.4.10. Value Object
Value Object (Fowler, 2003:486) je objekt čija se jednakost ne temelji na identitetu već na temelju
vrijednosti. Najjednostavniji primjer takvog objekta bi bilo objekti koji predstavljaju nekakav novčani iznos.
Ovaj uzorak dizajna se, u programskom jeziku Java, provodi nadjačavanjem metoda equals() i hashCode()
tako da se za usporedbu koriste polja od kojih se Value Object objekt sastoji.
19
4.4.11. Lazy Load
Lazy Load (Fowler, 2003:200) se koristi kada se želi odgoditi učitavanje određenih (ili svih)
podataka nekog objekta tek kada budu potrebni.
20
5. Uzorci dizajna za rad s bazom podataka
U ovom poglavlju opisati će se uzorci dizajna koji bi se primijenili za rad s bazom podataka i koji bi
ga u neku ruku pojednostavili. Pod radom s bazom podataka misli se na komunikaciju između aplikacije i
baze podataka. Općeniti postupak rada s bazom podataka (koji je više puta spomenut u ovom radu) je:
kreiranje veze na bazu podataka, provođenje SQL upita kroz tu vezu i, ovisno o vrsti provedene operacije,
učitavanje podataka ili provjera je li upit uspješno proveden.
Uzorci dizajna u ovom poglavlju imaju različite namjene što se tiče rada s bazom podataka. Na primjer, jedan
uzorak dizajna predstavlja objekt koji bi učahurio komunikaciju s određenom tablicom u relacijskoj bazi
podataka (Table Data Gateway), drugi skup uzoraka dizajna koristi se za preslikavanje podataka iz baze
podataka u objekt i obratno (Active Record, Data Mapper, Row Data Gateway).
Nadalje u ovom poglavlju, opisati će se uzorci dizajna primjenjivi za rad s bazom podataka. Kao što je
korišteno u poglavlju 4. Uzorci dizajna za podatkovni sloj, i za uzorke dizajna u ovom poglavlju će se koristiti
predložak za njihov opis koji je definiran u poglavlju 3. Predložak za opis uzoraka dizajna.
21
5.1. Active Record
5.1.1. Namjera
Predstavlja objekt koji sadrži vrijednosti jednog retka iz relacijske tablice ili pogleda u bazi podataka,
učahuruje način na koji objekt pristupa bazi podataka i sadrži dodatnu funkcionalnost koja je vezana za tu
specifičnu tablicu u bazi podataka (Fowler, 2003:160).
5.1.2. Problem
Kada je potrebno programski učitati podatke iz baze podataka, potrebno je kreirati vezu prema bazi
podataka (ako već ne postoji globalni pristup prema objektu koji drži vezu prema bazi podataka), napisati
SQL upit i pozvati metodu iz objekta za vezu prema bazi podataka koji će vratiti rezultat upita kao objekt
klase ResultSet. Nakon toga, prolazi se kroz svaki redak iz spomenutog objekta klase ResultSet i podaci se
učitavaju u jedan ili više drugih objekata. To je standardni, programski, postupak učitavanja podataka iz baze
podataka. Takav postupak može zauzimati malo ili veliki broj linija programskog koda, ovisno o
kompleksnosti relacijske tablice iz koje se žele učitati podaci. Naravno, jednokratno to ne predstavlja
preveliki problem, ali ako se podaci učitavaju u različitim dijelovima programa onda je potrebno ponovno
napisati (ili kopirati) takav kod.
No, opisani primjeri se odnosi samo na učitavanje podataka iz relacijske tablice. Tu postoje još operacije
kreiranja novog zapisa te ažuriranje ili brisanje postojećeg zapisa iz baze podataka.
5.1.3. Rješenje
Rješenje su klase čiji objekti bi sadržavali podatke iz odgovarajućih relacijskih tablica i koji bi sami
bili zaslužni za kreiranje, učitavanje, ažuriranje i brisanje podataka u bazu podataka. Svako polje u klasi treba
odgovarati stupcu iz odgovarajuće relacijske tablice u bazi podataka (Fowler, 2003:160).
Fowler (2003:161) navodi kako Active Record klasa sadrži sljedeće metode:
• metode za dohvaćanje (getter) i postavljanje (setter) vrijednosti polja u objektu,
• metoda koja kreira instancu Activer Record klase iz reda koji se očita iz rezultata SQL upita,
• metoda koja kreira instancu za kasnije dodavanje u relacijsku tablicu,
• metoda za ažuriranje baze podataka i dodavanje podataka iz Active Record objekta,
• statične metode kojima se traži i dohvaća specifičan red iz relacijske tablice i vraća u obliku instance
Active Record objekta,
• dodatna poslovna logika po potrebi.
Prema vlastitom mišljenju, metode za pretraživanje i dohvaćanje specifičnog zapisa ne trebaju nužno biti
statične metode, već mogu biti obične metode koje pune pozivajući objekt s dohvaćenim podacima. No, to
može biti stvar dizajna ili osobne preferense.
Naravno, uzorak dizajna Active Record sadrži i određena ograničenja. Jedno od tih ograničenja je što je jedna
22
Active Record klasa vezana za jednu relacijsku tablicu ili pogled u bazi podataka (Fowler, 2003:161-162).
Drugo ograničenje koje Fowler (2003:162) ističe je povezanost između dizajna u bazi podataka i objektnog
dizajna. Ako se promjeni shema relacijske tablice u bazi podataka, tada se treba promijeniti i prikladna Active
Record klasa.
5.1.4. Struktura rješenja
Fowler (2003) u svojem opisu uzorka dizajna Active Record nije dao konkretnu strukturu rješenja.
No, kao što je spomenuto, Active Record klasa po svojoj strukturi treba odgovarati odabranoj relacijskoj
tablici i treba implementirati CRUD2 operacije u koje su također učahuren način na koji će objekt pristupiti
bazi podataka.
Zato, umjesto prikaza apstraktne strukture rješenje, kako se to obično radi u knjizi od Gamma et al. (1995),
prikazat će se jednostavni primjer strukture rješenje za uzorak dizajna Active Record. Slika 5.1.1. prikazuje
relacijska tablicu entity i strukturu klase Entity koja je povezana s tom relacijskom tablicom.
Svaki stupac, odnosno atribut, relacijske tablice ima odgovarajuće polje u Active Record klasi. Tim poljima
se pristupa pomoću odgovarajućih metoda za dohvat vrijednosti polja (getter metodama) i ta se polja mogu
promijeniti metodama za postavljanje vrijednosti polja (setter metodama). Ostale metode služe za kreiranje,
čitanje, ažuriranje i brisanje zapisa iz baze podataka. Metode koje su definirane za klasu odgovaraju
metodama koje je opisao Fowler (2003:161) za Active Record klasu, ali bez dodatne poslovne logike koja se
može lagano dodati po potrebi.
2C – kreiranje (create), R – čitanje (read), U – ažuriranje (update), D – brisanje (delete)
23
Slika 5.1.1. Relacijska tablica "entity" i Active Record klasa "Entity"
povezana s tom tablicom
5.1.5. Primjer
U ovom primjeru pretpostaviti će se da sve klase entiteta trebaju nasljeđivati apstraktnu klasu Entity
prikazana u primjeru koda 5.1.1.
Primjer koda 5.1.1. Apstraktna klasa Entity
public abstract class Entity { protected long id; public long getId() { return id; } public void setId(long id) { this.id = id; } }
Sljedeće, kreirati će se apstraktna generička klasa ActiveRecord koja će implementirati temeljni mehanizam
za vlastito preslikavanje objekata. Određeni dijelovi tog mehanizma pozivati će apstraktne metode koje
moraju biti implementirane putem podklasa. Također, ta apstraktna klasa će nasljeđivati apstraktnu klasu
Entity tako da svaka klasa koja naslijedi ActiveRecord će biti prepoznata i kao klasa entiteta.
Prvo, implementirati će se metode za kreiranje novog zapisa u bazu podataka, koja je prikazana u primjeru
koda 5.1.2. Metoda kreira (ili dohvaća) vezu prema bazi podataka, priprema SQL upit za izvršavanje,
postavlja parametre pripremljenog upita te izvodi upit. Nakon upita metoda postavlja ID novog zapisa u
objekt pozivajući metodu setId() iz klase Entity. Metoda također poziva apstraktnu metodu
24
getCreateStatement() da dohvati SQL upit kojeg implementira podklasa i apstraktnu metodu
setCreateStatementParameters() za postavljanje parametara pripremljenog upita.
Primjer koda 5.1.2. Metode za kreiranje novog zapisa u bazu podataka
public abstract class ActiveRecord<T> extends Entity { public void create() { try (Connection conn = createConnection(); PreparedStatement ps = conn.prepareStatement( getCreateStatement(), Statement.RETURN_GENERATED_KEYS)) { setCreateStatementParameters(ps); ps.execute(); try (ResultSet rs = ps.getGeneratedKeys()) { if (rs.next()) { setId(rs.getLong(1)); } } } catch (SQLException ex) { System.out.println("Unable to create entity."); ex.printStackTrace(System.out); } } protected abstract String getCreateStatement(); protected abstract void setCreateStatementParameters(PreparedStatement ps) throws SQLException; // ... }
Sljedeće, napraviti će se metoda za ažuriranje podatka za bazu podataka. Metoda izgleda slično metodi za
kreiranje novog zapisa, jedine razlike su pozivanje druge apstraktne metode koja dohvaća SQL upit za
pripremu i pozivanje druge metode za postavljanje parametara pripremljenog upita. Metoda za ažuriranje
prikazana je u primjeru koda 5.1.3.
Primjer koda 5.1.3. Metoda za ažuriranje podatka za bazu podataka
public abstract class ActiveRecord<T> extends Entity { // ... public void update() { try (Connection conn = createConnection(); PreparedStatement ps = conn.prepareStatement(getUpdateStatement())) { setUpdateStatementParameters(ps); ps.execute(); } catch (SQLException ex) { System.out.println("Unable to update entity."); ex.printStackTrace(System.out); } } protected abstract String getUpdateStatement(); protected abstract void setUpdateStatementParameters(PreparedStatement ps) throws SQLException; // ... }
25
Zatim, kreirati će se metoda za brisanje podatka u bazi podataka, prikazana u primjeru koda 5.1.4. Metoda je
slična prethodnim dvjema metodama, osim što poziva drugu metodu za dohvaćanje SQL upita za pripremu i
poziva drugu metodu za postavljanje parametara pripremljenog upita.
Primjer koda 5.1.4. Metoda za brisanje podatka iz baze podataka
public abstract class ActiveRecord<T> extends Entity { // ... public void delete() { try (Connection conn = createConnection(); PreparedStatement ps = conn.prepareStatement(getDeleteStatement())) { setDeleteStatementParameters(ps); ps.execute(); } catch (SQLException ex) { System.out.println("Unable to delete entity."); ex.printStackTrace(System.out); } } protected abstract String getDeleteStatement(); protected abstract void setDeleteStatementParameters(PreparedStatement ps) throws SQLException; // ... }
Ostale su još metode za učitavanje podatka iz baze podataka u Active Record objekt. Kreirati će se dvije
metode: jedna koja učitava podatak na temelju proslijeđenog ID-a i druga koja učitava podatak iz objekta
ResultSet. Prva metoda se također poslužuje drugom metodom. Druga metoda prepušta podklasama da
implementiraju način učitavanja podataka iz objekta ResultSet. Primjer koda 5.1.5. prikazuje metode za
učitavanje podataka.
Primjer koda 5.1.5. Metoda za učitavanje podataka u objekt
public abstract class ActiveRecord<T> extends Entity { // ... public void loadById(long id) { try (Connection conn = createConnection(); PreparedStatement ps = conn.prepareStatement( getSelectByIdStatement())) { setSelectByIdStatementParameters(ps, id); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { loadFromResultSet(rs); } } } catch (SQLException ex) { System.out.println("Unable to load entity by ID."); ex.printStackTrace(System.out); } } protected abstract String getSelectByIdStatement(); protected abstract void setSelectByIdStatementParameters(
26
PreparedStatement ps, long id) throws SQLException; public void loadFromResultSet(ResultSet rs) { try { doLoadFromResultSet(rs); } catch (SQLException ex) { System.out.println("Unable to load entity from result set."); ex.printStackTrace(System.out); } } protected abstract void doLoadFromResultSet(ResultSet rs) throws SQLException; // ... }
Može se uočiti u prethodnim primjerima koda korištenje metode createConnection() kako bi se kreirala
objekt veze prema bazi podataka. Radi konzistentnosti, primjer 5.1.6. pokazuje moguću implementaciju te
metode.
Primjer koda 5.1.6.Implementacija apstraktne klase ActiveRecord u klasi License
public class ActiveRecord<T> extends Entity { // ... protected Connection createConnection() { try { Class.forName("org.apache.derby.jdbc.ClientDriver").newInstance(); return DriverManager.getConnection( "jdbc:derby://localhost:1527/mydb", "username", "password"); } catch (Exception ex) { throw new RuntimeException("Unable to connect to the database."); } } }
Time je dovršena apstraktna generička klasa ActiveRecord i može se početi implementirati konkretne klase
entiteta. Primjer koda 5.1.6. prikazuje implementaciju napravljene apstraktne klase za entitet License koji se
sastoji od polja ID (definiran u apstraktnoj klasi Entity), naziva license i njenog opisa.
Primjer koda 5.1.7.Implementacija apstraktne klase ActiveRecord u klasi License
public class License extends ActiveRecord<License> { private static final String CREATE_STATEMENT = "INSERT INTO license VALUES (DEFAULT, ?, ?)"; private static final String UPDATE_STATEMENT = "UPDATE license SET name=?, description=? WHERE id=?"; private static final String DELETE_STATEMENT = "DELETE FROM license WHERE id=?"; private static final String SELECT_BY_ID_STATEMENT = "SELECT * FROM license WHERE id=?";
27
private String name; private String description; public String getName() { return name; } public String getDescription() { return description; } public void setName(String name) { this.name = name; } public void setDescription(String description) { this.description = description; } @Override protected String getCreateStatement() { return CREATE_STATEMENT; } @Override protected String getUpdateStatement() { return UPDATE_STATEMENT; } @Override protected String getDeleteStatement() { return DELETE_STATEMENT; } @Override protected String getSelectByIdStatement() { return SELECT_BY_ID_STATEMENT; } @Override protected void setCreateStatementParameters(PreparedStatement ps) throws SQLException { ps.setString(1, getName()); ps.setString(2, getDescription()); } @Override protected void setUpdateStatementParameters(PreparedStatement ps) throws SQLException { ps.setString(1, getName()); ps.setString(2, getDescription()); ps.setLong(3, getId()); } @Override protected void setDeleteStatementParameters(PreparedStatement ps) throws SQLException { ps.setLong(1, getId()); } @Override protected void setSelectByIdStatementParameters(PreparedStatement ps, long id) throws SQLException {
28
ps.setLong(1, id); } @Override protected void doLoadFromResultSet(ResultSet rs) throws SQLException { setId(rs.getLong("id")); setName(rs.getString("name")); setDescription(rs.getString("description")); } }
Nadalje, u klasu License mogu se dodavati dodatne metode po želji. Također se i klasa License smije proširiti
ako bi se htjele napraviti dodatne izmjene ili proširenja.
29
5.2. Data Mapper
5.2.1. Namjera
Predstavlja sloj objekata za preslikavanje podataka između objekata i pripadajućih relacijskih tablica
u bazi podataka, i to tako da su objekti i baza podataka međusobno neovisni i oboje je također neovisno o
objektu preslikavanja (Fowler, 2003:165).
5.2.2. Problem
Opis problema za uzorak dizajna Active Record se također može koristiti kao problem kojeg rješava
ovaj uzorak dizajna. Jedina iznimka je zahtjev gdje su objekti i relacijske tablice međusobno neovisni,
odnosno objekt ne treba znati za postojanost relacijska tablice i relacijska tablice ne treba znati za postojanost
objekta.
Ukratko rečeno, problem vezan za ovaj uzorak dizajna je kako pojednostaviti preslikavanje podataka između
objekta i relacijske tablice, ali tako da jedno ne zna za drugo. Fowler (2003:165) takvu situaciju naziva
odvajanje domene od izvora podataka.
5.2.3. Rješenje
Fowler (2003:165-166) govori o dodavanju razine koja je zaslužna za preslikavanje podataka između
objekata i relacijskih tablica. Na tu razinu se može gledati kao skupina objekata koji su zaslužni za
preslikavanje podataka između pripadajućih objekata i relacijskih tablica. Objekt za preslikavanje poznaje
objekt i relacijsku tablicu s kojima radi, dok isti objekt i tablica ne znaju za objekt za preslikavanje.
Active Record i Data Mapper imaju istu namjenu, ali različite filozofije. Kod uzorka dizajna Active Record
objekti su sami zaslužni za preslikavanje podataka iz odgovarajuće relacijske tablice u sebe i obratno. Kod
uzorka dizajna Data Mapper za preslikavanje podataka između objekta i relacijske tablice odgovoran je
poseban objekt.
5.2.4. Struktura rješenja
Slika 5.2.1. pokazuje primjer strukture rješenja za uzorak dizajna Data Mapper. Za ovaj primjer
zamišljeno je da apstraktna klasa DataMapper definira osnovno ponašanje za kreiranje, čitanje, ažuriranje i
brisanje zapisa, a to ponašanje bi se proširilo kroz njene podklase.
Takvo ponašanje je implementacija uzorka dizajna Template Method, kojeg su opisali Gamma et al. (1995),
a ono je savršen uzorak dizajna s kojim bi se Data Mapper učinio proširivim i fleksibilnim. No, DataMapper
se također može definirati kao sučelje, tada bi podklase u potpunosti implementirale ponašanje za uzorak
Data Mapper.
30
Slika 5.2.1. Primjer strukture rješenja za Data Mapper
5.2.5. Primjer
Primjer Data Mapper uzorka dizajna koji će se ovdje obraditi se također primjenjuje u aplikaciji u
sklopu ovog rada. Koristi se već spomenuti dogovor u kojemu sve klase entiteta nasljeđuju apstraktnu klasu
Entity prikazanoj u primjeru koda 5.2.1.
Primjer koda 5.2.1. Apstraktna klasa Entity
public abstract class Entity { protected long id; public long getId() { return id; } public void setId() { this.id = id; } }
Definirati će se apstraktna i generička klasa DataMapper koja će implementirati temeljne mehanizme
preslikavanja podataka. čije podklase će biti ograničene samo na one tipove podataka koji nasljeđuju klasu
Entity. Također, Data Mapper u ovom primjeru će koristiti Identity Map uzorak kako bi se izbjeglo
instanciranje objekata entiteta s istim podacima, odnosno kako bi se izbjegli duplikati.
Metode koje će implementirati preslikavanja koristite metodu createConnection(), prikazana u primjeru
koda 5.2.2., kojom se uspostavlja veza prema bazi podataka.
Primjer koda 5.2.2. Apstraktna klasa Entity
public abstract class DataMapper<T extends Entity> { // ... protected Connection createConnection() { try { ServletContext sc = ApplicationListener.getServletContext(); DataSource dataSource = (DataSource) sc.getAttribute("DerbyDB"); return dataSource.getConnection(); } catch (SQLException ex) { ex.printStackTrace(System.out); throw new RuntimeException(ex); } } // ... }
31
Prvo, definirati će se metode za kreiranje zapisa u bazu podataka. Njen programski kod je prikazan u primjeru
koda 5.2.3. Metoda će biti slična metodi za kreiranje zapisa u primjeru Active Record uzorka, osim sljedećeg:
• metoda prima objekt entiteta, koji se želi kreirati u bazi podataka, kao parametar,
• metoda poziva zaštićenu metodu doCreate() koja vrši kreiranje.
Razlog zašto metoda za kreiranje zapisa poziva drugu metodu koja vrši kreiranje je zbog mogućnosti
proširenja tih metoda bude nadjačavanja. Ako se originalna metoda proširi nadjačavanjem, ono neće moći
izvršiti dodatne operacije s istim vezom prema bazi podataka. Ako se metoda doCreate() proširi
nadjačavanjem, dodatne operacije mogu koristiti istu vezu prema bazi podataka koja je korištena za kreiranje.
Na takav način i metode za ažuriranje i brisanje svoje konkretne operacije pozivaju iz zaštićenih metoda.
Primjer koda 5.2.3. Metoda za kreiranje zapisa
public abstract class DataMapper<T extends Entity> { public void create(T entity) { try (Connection conn = createConnection()) { doCreate(conn, entity); map().add(entity); } catch (SQLException ex) { System.out.println("Unable to create entity (" + getClass().getSimpleName() + "): " + ex.getMessage()); ex.printStackTrace(System.out); } } protected void doCreate(Connection conn, T entity) throws SQLException { try (PreparedStatement ps = conn.prepareStatement( createStatement(), Statement.RETURN_GENERATED_KEYS)) { setCreateStatementParameters(ps, entity); ps.execute(); try (ResultSet rs = ps.getGeneratedKeys()) { rs.next(); entity.setId(rs.getLong(1)); } } } // ... }
Zatim, metoda za ažuriranje zapisa je definirana kao u primjeru koda 5.2.4.
Primjer koda 5.2.4. Metoda za ažuriranje zapisa
public abstract class DataMapper<T extends Entity> { // ... public void update(T entity) { try (Connection conn = createConnection()) { doUpdate(conn, entity); } catch (SQLException ex) { System.out.println("Unable to update entity (" + getClass().getSimpleName() + "): " + ex.getMessage());
32
ex.printStackTrace(System.out); } } protected void doUpdate(Connection conn, T entity) throws SQLException { try (PreparedStatement ps = conn.prepareStatement(updateStatement())) { setUpdateStatementParameters(ps, entity); ps.execute(); } } // ... }
Metoda za brisanje zapisa definirana je i prikazana u slici 5.2.5.
Primjer koda 5.2.5. Metoda za brisanje zapisa
public abstract class DataMapper<T extends Entity> { // ... public void delete(T entity) { try (Connection conn = createConnection()) { doDelete(conn, entity); map().remove(entity.getId()); } catch (SQLException ex) { System.out.println("Unable to delete entity (" + getClass().getSimpleName() + "): " + ex.getMessage()); ex.printStackTrace(System.out); } } protected void doDelete(Connection conn, T entity) throws SQLException { try (PreparedStatement ps = conn.prepareStatement(deleteStatement())) { setDeleteStatementParameters(ps, entity); ps.execute(); } } // ... }
Metoda za učitavanje podataka iz objekta ResultSet prvo provjerava postoji li ID dohvaćenog podatka u
Identity Map objektu. Ako postoji, vraća taj objekt. Ako ne postoji, poziva metodu doLoad() koju treba
implementirati u podklasi. Metoda je prikazana u primjeru koda 5.2.6.
Primjer koda 5.2.6. Metoda za učitavanje zapisa iz ResultSet objekta
public abstract class DataMapper<T extends Entity> { // ... public T load(ResultSet rs) { try { long id = rs.getLong("id"); if (map().contains(id)) { return map().get(id); } return doLoad(rs); } catch (SQLException ex) { System.out.println("Unable to load entity ("
33
+ getClass().getSimpleName() + "): " + ex.getMessage()); ex.printStackTrace(System.out); return null; } } protected abstract T doLoad(ResultSet rs) throws SQLException; // ... }
Sljedeće, definirati će dvije metode za čitanje podataka: jednu koja očitava samo jedan podatak na temelju
proslijeđenog ID-a u parametru i druga koja očitava sve podatke iz relacijske tablice. Prva metoda na početku
provjerava postoji li objekt s traženim ID-em u Identity Map objektu. Ako postoji, dohvaća i vraća taj objekt.
Obje metode koriste metodu load() kako bi konstruirale konkretni objekt entiteta. Metode za čitanje prikazane
su u primjeru koda 5.2.7.
Primjer koda 5.2.7. Metode za čitanje zapisa
public abstract class DataMapper<T extends Entity> { // ... public T find(long id) { if (map().contains(id)) { return map().get(id); } try (Connection conn = createConnection(); PreparedStatement ps = conn.prepareStatement(findStatement())) { setFindStatementParameters(ps, id); ResultSet rs = ps.executeQuery(); if (rs.next()) { T entity = load(rs); return entity; } else { return null; } } catch (SQLException ex) { System.out.println("Unable to find entity (" + getClass().getSimpleName() + "): " + ex.getMessage()); ex.printStackTrace(System.out); return null; } } public List<T> findAll() { List<T> entities = new ArrayList<>(); try (Connection conn = createConnection(); PreparedStatement ps = conn.prepareStatement(findAllStatement()); ResultSet rs = ps.executeQuery()) { while (rs.next()) { T entity = load(rs); entities.add(entity); } } catch (SQLException ex) { System.out.println("Unable to find all entities (" + getClass().getSimpleName() + "): " + ex.getMessage()); ex.printStackTrace(System.out); } return entities; }
34
// ... }
Primjer koda 5.2.8. sadrži ostale apstraktne metode koje podklase moraju implementirati kako bi se mogli
kreirati konkretni objekti za preslikavanje.
Primjer koda 5.2.8. Metode za čitanje zapisa
public abstract class DataMapper<T extends Entity> { // ... protected abstract IdentityMap<T> map(); protected abstract String createStatement(); protected abstract String updateStatement(); protected abstract String deleteStatement(); protected abstract String findStatement(); protected abstract String findAllStatement(); protected abstract void setCreateStatementParameters( PreparedStatement ps, T entity) throws SQLException; protected abstract void setUpdateStatementParameters( PreparedStatement ps, T entity) throws SQLException; protected abstract void setDeleteStatementParameters( PreparedStatement ps, T entity) throws SQLException; protected abstract void setFindStatementParameters( PreparedStatement ps, long id) throws SQLException; }
Time je završena apstraktna generička klasa DataMapper. Sve što ostaje je implementirati je putem podklasa.
35
5.3. Row Data Gateway
5.3.1. Namjera
Objekt koji učahuruje vezu prema samo jednom zapisu u bazi podataka i za svaki zapis postoji jedna
instanca tog objekta (Fowler, 2003:152).
5.3.2. Problem
Problem koji rješava ovaj uzorak dizajna je isti kao i problem kojeg rješava uzorak dizajna Active
Record: problem višestrukog pisanja koda za povezivanje na bazu podataka za provođenje različitih SQL
upita.
5.3.3. Rješenje
Rješenje je kreirati objekte čija objektna struktura odgovara strukturi pojedinačnog zapisa u bazi
podataka (Fowler, 2003:152). Ti objekti su sami zaslužni za provođenje operacija nad bazom podataka poput
kreiranja, ažuriranja, čitanja i brisanja, a način kako se provode te operacije su učahurene u odgovarajuće
metode.
Može se uočiti kako je ovaj uzorak dizajna veoma sličan uzorku dizajna Active Record u tome što su njihovi
objekti sami zaslužni za vlastito kreiranje, ažuriranje, čitanje i brisanje. Fowler (2003:153) navodi kako se ta
dva uzorka razlikuju po sljedećem: Active Record sadrži dodatnu poslovnu logiku, a Row Data Gateway ne
sadrži. U tom slučaju, ako bi se trebalo dodati dodatna poslovna logika, tada bi se trebala kreirati nova
podatkovna klasa koja koristi Row Data Gateway objekt za dohvaćanje podataka.
5.3.4. Struktura rješenja
Kao i za uzorak dizajna Active Record ne postoji određeni, grafički, način prikaza apstraktne
strukture ovog uzorka dizajna. Sve što je potrebno je implementirati strukturu Row Data Gateway objekta da
odgovara strukturi zapisa u bazi podataka.
Slika 5.3.1. prikazuje dijagram klasa za klasu Entity s metodama potrebnim za provođenjem operacija nad
bazom podataka i čija objektna struktura odgovara strukturi relacijske tablice entity čiji ER model je prikazan
na istoj slici. Objekt smije sadržavati metode za dohvaćanje i postavljanje vrijednosti polja, ali ne smije
sadržavati dodatnu poslovnu logiku. Ako sadrži poslovnu logiku, onda se implementirao uzorak dizajna
Active Record.
36
Slika 5.3.1. Row Data Gateway klasa Entity povezana s relacijskom tablicom "entity"
5.3.5. Primjer
Uzorak dizajna Row Data Gateway se može implementirati na isti način kao i Active Record. Jedina
iznimka je što ovaj uzorak ne sadrži dodatnu poslovnu logiku, a Active Record smije. Prema tome, ovaj
primjer se neće objašnjavati korak po korak već će se odmah prikazati cjelokupan programski kod klasa.
Nastavlja se s prethodnim dogovorom gdje klase entiteta nasljeđuju apstraktnu klasu Entity koja je prikazana
na u primjeru koda 5.3.1.
Primjer koda 5.3.1. Apstraktna klasa Entity
public abstract class Entity { protected long id; public long getId() { return id; } public void setId(long id) { this.id = id; } }
Zatim, za ovaj primjer, kreirati će se apstraktna i generička klasa RowDataGateway koja će implementirati
temeljnu logiku za preslikavanje podataka između objekta i baze podataka. Ta klasa je prikazana u primjeru
koda 5.3.2.
Primjer koda 5.3.2. Apstraktna i generička klasa RowDataGateway
public abstract class RowDataGateway<T> extends Entity { public void create() {
37
try (Connection conn = createConnection(); PreparedStatement ps = conn.prepareStatement( getCreateStatement(), Statement.RETURN_GENERATED_KEYS)) { setCreateStatementParameters(ps); ps.execute(); try (ResultSet rs = ps.getGeneratedKeys()) { if (rs.next()) { setId(rs.getLong(1)); } } } catch (SQLException ex) { System.out.println("Unable to create entity."); ex.printStackTrace(System.out); } } public void update() { try (Connection conn = createConnection(); PreparedStatement ps = conn.prepareStatement(getUpdateStatement())) { setUpdateStatementParameters(ps); ps.execute(); } catch (SQLException ex) { System.out.println("Unable to update entity."); ex.printStackTrace(System.out); } } public void delete() { try (Connection conn = createConnection(); PreparedStatement ps = conn.prepareStatement(getDeleteStatement())) { setDeleteStatementParameters(ps); ps.execute(); } catch (SQLException ex) { System.out.println("Unable to delete entity."); ex.printStackTrace(System.out); } } public void loadById(long id) { try (Connection conn = createConnection(); PreparedStatement ps = conn.prepareStatement( getSelectByIdStatement())) { setSelectByIdStatementParameters(ps, id); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { loadFromResultSet(rs); } } } catch (SQLException ex) { System.out.println("Unable to load entity by ID."); ex.printStackTrace(System.out); } } public void loadFromResultSet(ResultSet rs) { try { doLoadFromResultSet(rs); } catch (SQLException ex) { System.out.println("Unable to load entity from result set."); ex.printStackTrace(System.out); } } protected Connection createConnection() {
38
try { Class.forName("org.apache.derby.jdbc.ClientDriver").newInstance(); return DriverManager.getConnection( "jdbc:derby://localhost:1527/mydb", "username", "password"); } catch (Exception ex) { throw new RuntimeException("Unable to connect to the database."); } } protected abstract String getUpdateStatement(); protected abstract String getCreateStatement(); protected abstract String getDeleteStatement(); protected abstract String getSelectByIdStatement(); protected abstract void setCreateStatementParameters(PreparedStatement ps) throws SQLException; protected abstract void setUpdateStatementParameters(PreparedStatement ps) throws SQLException; protected abstract void setDeleteStatementParameters(PreparedStatement ps) throws SQLException; protected abstract void setSelectByIdStatementParameters( PreparedStatement ps, long id) throws SQLException; protected abstract void doLoadFromResultSet(ResultSet rs) throws SQLException; }
Nakon toga, potrebno je kreirati konkretnu implementaciju apstraktne klase RowDataGateway.
Primjer koda 5.3.3. pokazuje njenu konkretnu implementaciju, nazvanu LicenseGateway, za entitet licence
koji sadrži polje za ID (definirano u klasi Entity), naziv licence i njen opis. Na tu klasu dodana je ključna
riječ final kako bi se onemogućila njena daljnja proširenja putem podklasa.
Primjer koda 5.3.3.Implementacija apstraktne klase RowDataGateway u klasi LicenseGateway
public final class LicenseGateway extends RowDataGateway<License> { private static final String CREATE_STATEMENT = "INSERT INTO license VALUES (DEFAULT, ?, ?)"; private static final String UPDATE_STATEMENT = "UPDATE license SET name=?, description=? WHERE id=?"; private static final String DELETE_STATEMENT = "DELETE FROM license WHERE id=?"; private static final String SELECT_BY_ID_STATEMENT = "SELECT * FROM license WHERE id=?"; private String name; private String description; public String getName() { return name; } public String getDescription() { return description; } public void setName(String name) { this.name = name; } public void setDescription(String description) {
39
this.description = description; } @Override protected String getCreateStatement() { return CREATE_STATEMENT; } @Override protected String getUpdateStatement() { return UPDATE_STATEMENT; } @Override protected String getDeleteStatement() { return DELETE_STATEMENT; } @Override protected String getSelectByIdStatement() { return SELECT_BY_ID_STATEMENT; } @Override protected void setCreateStatementParameters(PreparedStatement ps) throws SQLException { ps.setString(1, getName()); ps.setString(2, getDescription()); } @Override protected void setUpdateStatementParameters(PreparedStatement ps) throws SQLException { ps.setString(1, getName()); ps.setString(2, getDescription()); ps.setLong(3, getId()); } @Override protected void setDeleteStatementParameters(PreparedStatement ps) throws SQLException { ps.setLong(1, getId()); } @Override protected void setSelectByIdStatementParameters(PreparedStatement ps, long id) throws SQLException { ps.setLong(1, id); } @Override protected void doLoadFromResultSet(ResultSet rs) throws SQLException { setId(rs.getLong("id")); setName(rs.getString("name")); setDescription(rs.getString("description")); } }
Nadalje, može se koristiti direktno klasa LicenseGateway za podatake, ali ako se želi odvojiti
podatkovni objekt od objekta za preslikavanje ili se želi dodati poslovna logika za objekt, tada treba kreirati
novu klasa za podatak. Ta klasa će moći koristiti LicenseGateway u konstruktoru kako bi prenijela podatke
iz njega. Primjer koda 5.3.4. prikazuje klasu License koja ima dva konstruktora: jedan prazan, a drugi prima
40
parametar tipa LicenseGateway.
Primjer koda 5.3.4. Klasa License
public class License extends Entity { private String name; private String description; public License() { } public License(LicenseGateway licenseGateway) { this.id = licenseGateway.getId(); this.name = licenseGateway.getName(); this.description = licenseGateway.getDescription(); } public String getName() { return name; } public String getDescription() { return description; } public void setName(String name) { this.name = name; } public void setDescription(String description) { this.description = description; } // Dodatna poslovna logika. }
U tom slučaju, za klasu LicenseGateway će se također morati dodati konstruktori koji će učitavati podatka iz
objekta License kako bi se omogućilo preslikavanje podataka ako su izmjene napravljene upravo u takvom
objektu. Primjer koda 5.3.5. pokazuje ta dva konstruktora za klasu LicenseGateway.
Primjer koda 5.3.5. Konstruktori za klasu LicenseGateway
public class LicenseGateway extends RowDataGateway<License> { // ... public LicenseGateway() { } public LicenseGateway(License license) { this.id = license.getId(); this.name = license.getName(); this.description = license.getDescription(); } // ... }
41
5.4. Query Object
5.4.1. Namjera
Objekt koji predstavlja upit nad bazom podataka (Fowler, 2003:316).
5.4.2. Problem
Pisanje SQL upita u nekom programskom jeziku se uglavnom svodi na pisanjem upita kao String
objekta ili (u slučaju programskog jezika Java) u StringBuffer ili StringBuilder objekte ako se treba dinamički
složiti upit. Ovdje može doći do sljedećih problema:
• programer ne poznaje sintaksu SQL upita (Fowler, 2003:316),
• mogućnost pojave sintaktičkih greški, pogotovo ako se radi o velikim, složenim, upitima ili ako se
radi neki slaže upit putem uvjetovanih naredbi.
5.4.3. Rješenje
Query Object je specijalizirani uzorak dizajna Interpreter koji predstavlja strukturu objekata pomoću
kojih se gradi SQL upit (Fowler, 2003:316). Ovaj uzorak dizajna može biti relativno jednostavan ili relativno
složen, ovisno o tome kako je implementiran. Isto tako se može implementirati na različite načine:
• Jedna mogućnost je implementirati mehanizam koji jednostavno vraća valjan SQL upit na temelju
pridruženih objekata i vrijednosti.
• Druga mogućnost je mehanizam koji, umjesto generiranja SQL upita, služi za dohvat i filtriranje
objekata učitanih u memoriju (Fowler, 2003:317).
• Treća mogućnost je mehanizam koji može generirati SQL upit na temelju proslijeđenog objekta
(Fowler, 2003:317). Na primjer, ako se uzme objekt koji predstavlja osobu, tom objektu se postavi
vrijednost imena na Ivan i sva ostala polja ostaju prazna, mehanizam će generirati SQL upit koji
služi za pretraživanje svih osoba u bazi podataka koja se zovu Ivan (Fowler, 2003:317). Ovakav
mehanizam postoji u nekim NoSQL3 bazama podataka, npr. MongoDB.
• itd.
3NoSQL baze podataka su baze podataka koje ne koriste relacijske tablice već koriste vlastite načine pohrane
podataka. Primjer takvih baza podataka su CouchDB i MongoDB, koje koriste JSON za pohranu.
42
5.4.4. Struktura rješenja
Slika 5.4.1. Primjer strukture rješenja za uzorak dizajna Query Object
Slika 5.4.1. prikazuje jednostavan primjer strukture rješenja za uzorak dizajna Query Object, a
elementi te strukture imaju sljedeće uloge:
• Criteria je sučelje za objekte koji predstavljaju ograničenja koja se nalaze unutar WHERE klauzule
u SELECT upitu.
• Condition predstavlja jednostavna ograničenja poput jednakosti, veće od, manje od, itd.
• Junction predstavlja složenija ograničenja koja se sastoje od više jednostavnih ograničenja koji se
nalaze u konjunkciji ili disjunkciji.
• Query je objekt koji sadrži podatke o tablici nad kojom se želi provesti upit i sadrži strukturu
ograničenja za WHERE klauzulu. Također može sadržavati i ostale klauzule SELECT upita, poput
ORDER BY, GROUP BY, LIMIT, itd.
5.4.5. Primjer
U ovom primjeru napraviti će se nešto drugačije: klase čiji objekti predstavljaju specifične podatke
iz baze podataka (npr. prethodno korišteni Employee, Skill, itd.) nasljeđivati će apstraktnu klasu. Navedena
apstraktna klasa, prikazana u primjeru koda 5.4.1., sadržati će metode za dohvaćanje i postavljanje ID
podatka (pod pretpostavkom da sve tablice u bazi podataka sadrže stupac id kao primarni ključ), apstraktnu
metodu koja vraća atribute (stupce) i apstraktni metodu koja vraća naziv entiteta (tablice).
Primjer koda 5.4.1. Apstraktna klasa Entity
public abstract class Entity {
protected long id;
public long getID() { return id; }
public void setID(long id) { this.id = id; }
public abstract String entityName();
public abstract String[] attributes();
}
Apstraktna klasa Entity omogućit će određenu vrstu fleksibilnosti kod implementacije uzorka dizajna Query
Object jer će se pomoću metoda entityName() i attributes() moći ispitati naziv tablice na koju se objekt
odnosi i strukturu tablice (tj. nazive stupaca od kojih se sastoji tablica). Budući da su te dvije metode
43
apstraktne, za svaku klasu koja nasljeđuje Entity morati će se odrediti koje vrijednosti one vraćaju.
Primjer koda 5.4.2. prikazuje klasu Employee koja nasljeđuje Entity i implementira spomenute apstraktne
metode.
Primjer koda 5.4.2. Klasa Employee koja nasljeđuje Entity
public class Employee extends Entity {
private String firstName;
private String lastName;
// Getter i setter metode
@Override
public String entityName() {
return "employee";
}
@Override
public String[] attributes() {
return new String[] { "id", "first_name", "last_name" };
}
}
Sada, implementirati će se klase koja će predstavljati ograničenja, odnosno uvjete, u WHERE klauzulama
SQL upita. Spomenute klase implementirati će se na temelju uzorka dizajna Interpreter opisan u knjizi od
Gamma et al (1995). Prvo će se definirati sučelje za ograničenja kao u primjeru koda 5.4.3. Fowler (2003:318-
320) je u svojim primjerima koristio Criteria za naziv tih objekata u kodu, pa će se isto koristiti za naziv
sučelja (ovdje prestaju sličnosti s Fowlerovim primjerima).
Primjer koda 5.4.3. Sučelje Criteria
public interface Criteria {
String toSqlString();
}
Sučelje u primjeru koda 5.4.3. definira samo jednu metodu koja će vraćati ograničenja kao String objekt, ali
u formatu u kojem bi se objekt mogao koristiti u sklopu WHERE klauzule. Sljedeće, implementirati će se
klasa Condition koja nasljeđuje sučelje Criteria i predstavlja jednostavno ograničenje u WHERE klauzuli
koje se sastoji od: imena stupca (atributa), operatora i vrijednosti. Implementacija klase Condition prikazana
je u primjeru koda 5.4.4.
Primjer koda 5.4.4. Klasa Condition
public class Condition implements Criteria {
private String attribute;
private String operator;
private Object value;
44
public Condition(String attribute, String operator, Object value) {
this.attribute = attribute;
this.operator = operator;
this.value = value;
}
@Override
public String toSqlString() {
return attribute
+ " " + operator
+ " " + (value instanceof Number ? value : "'" + value + "'");
}
}
Nakon toga, implementirati će se klasa Junction. Navedena klasa predstavljati će niz ograničenja koji su
međusobno povezani operatorom, npr. operatorom disjunkcije (OR) ili operatorom konjunkcije (AND).
Implementacija klase Junction prikazana je u primjeru koda 5.4.5.
Primjer koda 5.4.5. Klasa Junction
public class Junction implements Criteria {
private String operator;
private List<Criteria> criterias = new ArrayList<>();
public Junction(String operator, Criteria... criterias) {
Collections.addAll(this.criterias, criterias);
this.operator = operator;
}
public void add(Criteria criteria) {
criterias.add(criteria);
}
public boolean isEmpty() {
return criterias.isEmpty();
}
@Override
public String toSqlString() {
StringJoiner sj = new StringJoiner(" " + operator + " ");
for (Criteria c : criterias) {
if (c instanceof Junction) {
sj.add("(" + c.toSqlString() + ")");
} else {
sj.add(c.toSqlString());
}
}
return sj.toString();
}
}
Kako bi se olakšalo programeru kreiranje pravilnih objekata ograničenja, može se u klasama Condition i
Junction definirati statičke metode koje bi vraćale predefinirana ograničenja. Primjer koda 5.4.6. prikazuje
45
definiciju nekih od statičkih metoda u klasi Condition, a primjer koda 5.4.7. definiciju statičkih metoda u
klasi Junction.
Primjer koda 5.4.6. Statičke metode koje vraćaju predefinirane objekte Condition
public class Condition implements Criteria {
// ...
public static Condition eq(String attribute, Object value) {
return new Condition(attribute, "=", value);
}
public static Condition notEq(String attribute, Object value) {
return new Condition(attribute, "<>", value);
}
public static Condition gt(String attribute, Object value) {
return new Condition(attribute, ">", value);
}
public static Condition lt(String attribute, Object value) {
return new Condition(attribute, "<", value);
}
public static Condition geq(String attribute, Object value) {
return new Condition(attribute, ">=", value);
}
public static Condition leq(String attribute, Object value) {
return new Condition(attribute, "<=", value);
}
public static Condition like(String attribute, String value) {
return new Condition(attribute, "LIKE", value);
}
}
Primjer koda 5.4.7. Statičke metode koje vraćaju predefinirane objekte Junction
public class Junction implements Criteria {
// ...
public static Junction and(Criteria... criterias) {
return new Junction("AND", criterias);
}
public static Junction or(Criteria... criterias) {
return new Junction("OR", criterias);
}
}
Sve što je ostalo je implementirati klasu Query koja će na temelju danih objekata tipa Entity i ograničenja
generirati SQL upit. Klasa Query sadržat će dva polja, također prikazana u primjeru koda 5.4.8., a oni će biti:
46
• polje entities, mapa objekata tipa Entity s ključem tipa String koji će predstavljati pseudonim (eng.
alias) za tablicu u bazi podataka,
• polje criterias, objekt tipa Junction, odnosno niz ograničenja koji će, u ovom slučaju, biti povezani
logičkim operatorom konjunkcije (AND).
Primjer koda 5.4.8. Polja klase Query
public class Query {
private Map<String, Entity> entities = new HashMap<>();
private Junction criterias = Junction.and();
// ...
}
Sljedeće, potrebne su metode za dodavanje objekata tipa Entity u polje entities, koje se mogu vidjeti u
primjer koda 5.4.9. U istom primjeru također su prikazani konstruktori klase Query koji koriste metode za
dodavanje. Uvođenjem konstruktora za ovu klasu koji zahtijevaju dodavanje objekta tipa Entity sprečava se
kreiranje objekta Query s praznom mapom, što bi dovelo do komplikacija oko generiranja SQL upita.
Jedna metoda za dodavanje kao parametar prima samo objekti tipa Entity kojeg dodaje u mapu, ali za ključ,
odnosno njegov pseudonim, koristi naziv entiteta. Druga metoda prima dva parametra: objekt tipa Entity i
pseudonim za taj entitet. Za ključ se tada koristi proslijeđeni pseudonim.
Primjer koda 5.4.9. Konstruktori klase Query i metode za dodavanje objekata tipa Entity
public class Query {
// ...
public Query(Entity entity) {
addEntity(entity);
}
public Query(Entity entity, String alias) {
addEntity(entity, alias);
}
public void addEntity(Entity entity) {
entities.put(entity.entityName(), entity);
}
public void addEntity(Entity entity, String alias) {
entities.put(alias, entity);
}
// ...
}
Također je potrebna metoda za dodavanje ograničenja za WHERE klauzulu. Za klasu Junction, u primjeru
koda 5.4.5., definirana je operacija dodavanja ograničenja u objekt te klase. Metoda za dodavanje ograničenja
47
u objektu Query će ustvari koristiti metodu dodavanja ograničenja od polja criterias. Primjer koda 5.4.10.
prikazuje tu metodu za dodavanje ograničenja.
Primjer koda 5.4.10. Metoda za dodavanja ograničenja u objekt Query
public class Query {
// ...
public void addCriteria(Criteria criteria) {
criterias.add(criteria);
}
// ...
}
Ostalo je definirati metodu koja bi vratila SQL upit kao objekt tipa String. Za to će se nadjačati metoda
toString() koja će na temelju trenutnog stanja objekta Query, odnosno na temelju stanja polja entities i
criterias od kojih se sastoji spomenuti objekt, generirati SQL upit. U primjeru koda 5.4.11. metoda
toString() koristi dodatne privatne metode od koje svaki generira zasebni klauzule SQL upita (jedan generira
SELECT klauzula, drugi generira FROM klauzula, a treći generira WHERE klauzulu).
Primjer koda 5.4.11. Nadjačavanje toString() metoda koja vraća SQL upit
public class Query {
// ...
@Override
public String toString() {
String select = makeSelect();
String from = makeFrom();
String where = makeWhere();
return select + from + where;
}
private String makeSelect() {
StringJoiner sj = new StringJoiner(", ");
for (String alias : entities.keySet()) {
Entity entity = entities.get(alias);
for (String attr : entity.attributes()) {
sj.add(alias + "." + attr);
}
}
return "SELECT " + sj.toString();
}
private String makeFrom() {
StringJoiner sj = new StringJoiner(", ");
for (String alias : entities.keySet()) {
sj.add(entities.get(alias).entityName() + " " + alias);
}
return " FROM " + sj.toString();
}
48
private String makeWhere() {
return criterias.isEmpty() ? "" : " WHERE " + criterias.toSqlString();
}
}
Primjer koda 5.4.12. prikazuje primjer korištenja klase Query.
Primjer koda 5.4.12. Primjer korištenja klase Query
Query query = new Query(new Employee(), "e");
Criteria c1 = Condition.eq("e.last_name", "Horvat")
Criteria c2 = Junction.or(
Condition.eq("e.first_name", "Ivan"),
Condition.eq("e.first_name", "Ivana"));
query.addCriteria(c1);
query.addCriteria(c2);
System.out.println(query.toString()); // SELECT e.id, e.first_name, e.last_name
// FROM employee e
// WHERE e.last_name = 'Horvat'
// AND (e.first_name = 'Ivan' OR
// e.first_name = 'Ivana')
49
5.5. Table Data Gateway
5.5.1. Namjera
Objekt koji učahuruje komunikaciju sa specifičnom relacijskom tablicom u bazi podataka (Fowler,
2003:144).
5.5.2. Problem
Glavni problem kojeg Fowler (2003:114) opisuje, a kojega rješava ovaj uzorak dizajna, je što neki
programeri nisu upoznati sa SQL sintaksom, a oni koji jesu ponekad ne napišu dobre upite.
5.5.3. Rješenje
Rješenje ovog problema je učahuriti svu komunikaciju s relacijskom tablicom u jedan objekt. Jedan
takav objekt se podudara s jednom relacijskom tablicom, što znači da ako se ovaj uzorak dizajna želi koristiti
za sve tablice u bazi podataka (ne računajući tablice koje se koriste za ostvarenje veze više-više između
tablica), onda će postojati i toliko objekata.
Objekt Table Data Gateway bi imao nekoliko metoda za dohvat podataka iz relacijske tablice (na programeru
je da odredi metode koje su potrebne ili doda nove metode po potrebi) i metoda za unošenje podataka u
tablicu (INSERT), ažuriranje (UPDATE) i brisanje (DELETE) podataka (Fowler, 2003:144).
Treba napomenuti da ovaj uzorak dizajna služi samo kao zamjena za konstantnim pisanjem istih SQL upita.
5.5.4. Struktura rješenja
Slika 5.5.1. Odnos između tablice "entity" i Row Data Gateway klase EntityGateway
Struktura ovog uzorka dizajna je jednostavna jer samo dva elementa sudjeluju u vezi: objekt koji komunicira
s bazom podataka, odnosno pristupa specifičnoj relacijskoj tablici, i sama baza podataka. Naravno, tu još
postoje podatkovni objekti koji predstavljaju jedan zapis iz baze podataka. Primjer na slici 5.5.1. prikazuje
odnos između klase EntityGateway relacijske tablice entity u tome kako bi otprilike izgledala implementacija
uzorka dizajna Table Row Gateway.
5.5.5. Primjer
Neka postoji relacijska tablica customer koja je kreirana na temelju SQL upita u primjeru koda 5.5.1.
(SQL kod koristi sintaksu za postavljanje inkrementalnog primarnog ključa kojeg koristi Apache Derby).
Primjer koda 5.5.1. SQL upit za kreiranje tablice "customer"
CREATE TABLE customer (
50
id INT GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
name VARCHAR(64),
address VARCHAR(64),
email VARCHAR(64),
phone VARCHAR(16),
PRIMARY KEY (id)
)
Također, neka postoji klasa entiteta Customer čija struktura i metode su prikazani u primjeru koda 5.5.2.
Primjer koda 5.5.2. Klasa Customer
public class Customer {
private long id;
private String name;
private String address;
private String email;
private String phoneNumber;
// Getter i setter metode.
}
Nadalje, kreirat će se metoda CustomerGateway koja će implementirati uzorak dizajna Table Data Gateway.
Za razliku od uzoraka Active Record, Data Mapper i Row Data Gateway, u ovom primjeru se neće napraviti
apstraktna generička klasa jer ovaj uzorak dizajna strogo ovisi o strukturi tablice u bazi podataka.
Prvo, definirati će se metoda za kreiranje novog zapisa u bazu podataka. Metoda će primati sve parametre
koji odgovaraju stupcima u bazi podataka osim stupca za ID, koji se sam generira. Primjer koda 5.5.3.
prikazuje metodu za kreiranje zapisa i također uključuje metodu za kreiranja veze prema bazi podataka koju
klasa koristi.
Primjer koda 5.5.3. Metoda za kreiranje zapisa + metoda za kreiranje veze prema bazi podataka
public class CustomerGateway {
protected Connection createConnection() {
try { Class.forName("org.apache.derby.jdbc.ClientDriver").newInstance(); return DriverManager.getConnection( "jdbc:derby://localhost:1527/mydb", "username", "password"); } catch (Exception ex) { throw new RuntimeException("Unable to connect to the database."); }
}
public void create(String name,
String address,
String email,
String phoneNumber)
{
51
String sql = "INSERT INTO customer VALUES (DEFAULT, ?, ?, ?, ?)";
try (Connection conn = createConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, name);
ps.setString(2, address);
ps.setString(3, email);
ps.setString(4, phoneNumber);
ps.execute();
} catch (SQLException ex) {
System.out.println("Unable to create Customer entity.");
ex.printStrackTrace(System.out);
}
}
// ...
}
Zatim, slijedi metoda za ažuriranje zapisa koja će za parametre primati sva polja koja čine entitet u bazi
podataka, uključujući ID koji je potreban za ažuriranje. Primjer koda 5.5.4. pokazuje tu metodu.
Primjer koda 5.5.4. Metoda za ažuriranje zapisa
public class CustomerGateway {
// ....
public void create(long id,
String name,
String address,
String email,
String phoneNumber)
{
String sql = "UPDATE customer SET name=?, address=?, email=?, phone=? "
+ "WHERE id=?";
try (Connection conn = createConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, name);
ps.setString(2, address);
ps.setString(3, email);
ps.setString(4, phoneNumber);
ps.setLong(5, id);
ps.execute();
} catch (SQLException ex) {
System.out.println("Unable to update Customer entity.");
ex.printStrackTrace(System.out);
}
}
// ...
}
Sljedeće, napraviti će se metoda za brisanje zapisa iz baze podataka. Za ovu metodu dovoljno je proslijediti
ID kao parametar. Metoda za brisanja prikazana je u primjeru koda 5.5.5.
Primjer koda 5.5.5. Metoda za brisanje zapisa
public class CustomerGateway {
// ....
public void create(long id) {
52
String sql = "DELETE FROM customer WHERE id=?";
try (Connection conn = createConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(5, id);
ps.execute();
} catch (SQLException ex) {
System.out.println("Unable to update Customer entity.");
ex.printStrackTrace(System.out);
}
}
// ...
}
Na kraju, ostalo je još implementirati metode za čitanje podataka. Programer ima slobodu dodavanja metoda
za čitanje koliko je potrebno. Primjer koda 5.5.6. definira dvije metode za čitanje podataka: prema njihovom
ID-u i prema njihovom nazivu. No, lagano se može dodati i čitanje prema ostalim poljima relacijske tablice.
Primjer koda 5.5.6. Metoda za čitanje podataka prema ID-u i nazivu
public class CustomerGateway {
// ....
private createCustomer(ResultSet rs) {
Customer customer = new Customer();
customer.setId(rs.getLong("id"));
customer.setName(rs.getString("name"));
customer.setAddress(rs.getString("address"));
customer.setEmail(rs.getString("email"));
customer.setPhoneNumber(rs.getString("phone"));
return customer;
}
public Customer findByID(long id) {
String sql = "SELECT * FROM customer WHERE id=?";
try (Connection conn = createConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, id);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return createCustomer(rs);
} else {
return null;
}
}
} catch (SQLException ex) {
System.out.println("Unable to update Customer entity.");
ex.printStrackTrace(System.out);
return null;
}
}
public List<Customer> findByName(String name) {
String sql = "SELECT * FROM customer WHERE name=?";
List<Customer> customers = new ArrayList<>();
try (Connection conn = createConnection();
53
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, "%" + name + "%");
ps.execute();
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
customers.add(createCustomer);
}
}
} catch (SQLException ex) {
System.out.println("Unable to update Customer entity.");
ex.printStrackTrace(System.out);
}
return customers;
}
// ...
}
54
5.6. Unit of Work
5.6.1. Namjera
Održava popis objekata nad koje je utjecala poslovna transakcija i koordinira upisivanje njihovih
izmjena i rješavanje konkurentnih problema kod višedretvenog rada (Fowler, 2003:184).
5.6.2. Problem
Fowler (2003:189) kao problem vezan za ovaj uzorak je praćenje izmjena podataka unutar neke
poslovne interakcije u aplikaciji koji se moraju sinkronizirati s podacima u bazi podataka.
Drugi problem kojeg Fowler spominje je broj poziva prema bazi podataka. Ako bi se nakon svake operacija
kreiranja, čitanja, ažuriranje ili brisanja promjene sinkronizirale s bazom podataka, baza podataka bi primila
veliki broj poziva prema njoj.
5.6.3. Rješenje
Rješenje opisanog problema je kreirati mehanizam koji bi pratio promjene u objektima i zahtjeve za
kreiranje novih objekata ili brisanje postojećih. Taj mehanizam bi, nakon što je poslovna transakcija od strane
aplikacije završila, otvorila transakciju s bazom podataka i provela zabilježene promjene u bazi podataka.
Fowler (2003:185-186) navodi dva načina kako da taj mehanizam zabilježi promjene koje se provode nad
objektom:
1. pozivatelj bilježi napravljene izmjene,
2. objekt bilježi napravljene izmjene nad njime.
U prvom slučaju, korisnik objekta mora se sjetiti zabilježiti objekt s Unit of Work mehanizmom kako bi se
provele izmjene (Fowler, 2003:185). Ako promjene nisu zabilježene unutar Unit of Work, onda te promjene
neće biti provedene u bazi podataka.
U drugom slučaju, objekt sam bilježi kod Unit of Work mehanizma izmjene provedene nad njime. Ako se
izmijenila vrijednost nekog polja u objektu, taj objekt bilježi tu izmjenu kod Unit of Work mehanizma. Ako
se objekt u aplikaciji označio za brisanje, objekt će zabilježiti kod zahtjev za brisanjem kod Unit of Work
mehanizma. I tako dalje. Naravno, ovakav način može jedino funkcionirati ako se Unit of Work objekt
proslijedi objektu ili Unit of Work objekt mora sadržavati globalni pristup do njega (Fowler, 2003:186).
5.6.4. Struktura rješenja
Ne postoji neka apstraktna struktura rješenja koja bi grafički prikazala arhitekturu uzorka dizajna
Unit of Work. Ono što je važno kod ovog uzorka dizajna je mogućnost bilježenja promjena nad objektima,
bilježenje kreiranje novih i brisanje postojećih objekata, te provođenja svih zabilježenih promjena unutar
transakcije u bazi podataka. Način implementacije ovog uzorka dizajna ovisi o programeru.
5.6.5. Primjer
Fowler (2003:190) je u svom primjeru za Unit of Work koristio nekoliko objekata liste, svaka bilježi
objekte za specifičan zahtjev (jedna lista je za novokreirane objekte, druga lista je za objekte koje treba
55
ažurirati, itd.). U ovom primjeru, svi objekti će se bilježiti unutar jedne liste, ali će svaki objekt biti označen
za specifičan zahtjev.
Unutar klase UnitOfWork, definirati će se interno pobrojenje (eng. enumeration) kojim će se bilježiti vrsta
zahtjeva za objekt i definirati će se interna klasa koja će omotati objekt entiteta i pobrojenje u jedan objekt
čime bi se bilježila vrsta zahtjeva prema tom objektu. Spomenuto interno pobrojenje i interna klasa prikazani
su u primjeru koda 5.6.1.
Primjer koda 5.6.1. Interno probrojenje OperationMarker i interna klasa EntityMarkerWrapper
public class UnitOfWork {
private enum OperationMarker {
CREATION,
UPDATE,
REMOVAL
}
private class EntityMarkerWrapper<T extends Entity> {
final T entity;
final OperationMarker operationMarker;
public EntityMarkerWrapper(T entity, OperationMarker operationMarker) {
this.entity = entity;
this.operationMarker = operationMarker;
}
}
// ...
}
Ovaj primjer uzorka UnitOfWork koristiti će Data Mapper objekte koji bi proveli tražene operacije nad
objektima. No, za ovaj primjer neka su Data Mapper klase modificirane tako da se dodaju alternativne
metode create(), update() i delete() koje primaju objekt veze kao parametar. Te metode su dodane u
apstraktnoj klasu DataMapper koja je opisana u primjeru tog uzorka u poglavlju 5.2.5. Primjer koda 5.6.2.
pokazuje te metode.
Primjer koda 5.6.2. Metode za kreiranje, ažuriranje i brisanje zapisa
koje prihvaćaju objekt veze kao parametar
public abstract class DataMapper<T extends Entity> {
// ...
public void create(Connection conn, T entity) throws SQLException {
try (PreparedStatement ps = conn.prepareStatement(
createStatement(),
Statement.RETURN_GENERATED_KEYS)) {
setCreateStatementParameters(ps, entity);
ps.execute();
try (ResultSet rs = ps.getGeneratedKeys()) {
56
rs.next();
entity.setId(rs.getLong(1));
}
}
}
public void update(Connection conn, T entity) throws SQLException {
try (PreparedStatement ps = conn.prepareStatement(updateStatement())) {
setUpdateStatementParameters(ps, entity);
ps.execute();
}
}
public void delete(Connection conn, T entity) throws SQLException {
try (PreparedStatement ps = conn.prepareStatement(deleteStatement())) {
setDeleteStatementParameters(ps, entity);
ps.execute();
}
}
// ...
}
Klasa UnitOfWork će sadržavati listu objekata, omotani u objektu EntityMarkerWrapper, nad kojima se
provodi zahtjevi za kreiranjem, ažuriranjem ili brisanje, i sadržati će mapu Data Mapper objekata koji će
provoditi tražena preslikavanja. U konstruktoru će se dodavati objekti preslikavanja. Mapa će koristiti klasu
entiteta kao ključ kojim bi se pretraživali objekti za preslikavanje Primjer koda 5.6.3. prikazuje listu objekata,
mapu objekata preslikavanja i konstruktor.
Primjer koda 5.6.3. Lista objekata, mapa objekata preslikavanje i konstruktor klase UnitOfWork
public class UnitOfWork {
// ...
private final Map<
Class<? extends Entity>,
DataMapper<? extends Entity>> mappers;
private final List<EntityMarkerWrapper<? extends Entity>> markedEntities;
public UnitOfWork() {
this.mappers = new HashMap<>();
// Dodavanje objekata preslikavanje u mapu mappers
this.markedEntities = new ArrayList<>();
}
// ...
}
Sljedeće, definirati će se metode kojima bi se označavale vrste zahtjeva koja se želi provesti nad objekt. Te
metode će onda omotati objekte entiteta s odgovarajućim pobrojenjem iz definiranim u OperationMarker u
objekt EntityMarkerWrapper i dodati u listu označenih objekata. Primjer koda 5.6.4. prikazuje te metode.
57
Primjer koda 5.6.4.Metode za bilježenje objekata entiteta za određenu operaciju
public class UnitOfWork {
// ...
public <T extends Entity> void markForCreation(T entity) {
markedEntities.add(
new EntityMarkerWrapper<>(entity, OperationMarker.CREATION));
}
public <T extends Entity> void markForUpdate(T entity) {
markedEntities.add(
new EntityMarkerWrapper<>(entity, OperationMarker.UPDATE));
}
public <T extends Entity> void markForRemoval(T entity) {
markedEntities.add(
new EntityMarkerWrapper<>(entity, OperationMarker.REMOVAL));
}
// ...
}
Ono što nedostaje u primjeru koda 5.6.4. je provjera je li se objekt već zabilježio za kreiranje ili brisanje. No,
samo za primjer i ovo je dovoljno.
Ostaje još definirati metodu koja će provesti označene operacije nad objektima. Ta metoda izvršava operacije
po FIFO (First In First Out) principu – u svakoj iteraciji izvlači jedan objekt iz liste i provodi zabilježenu
operaciju pomoću odgovarajućeg Data Mapper objekta. Sve operacije će se provesti u jednoj transakciji. U
ovom slučaju objekt UnitOfWork se brine o kreiranju vlastite veze prema bazi podataka. Primjer metode za
provođenje zabilježenih operacija nad objektima (i metoda za kreiranje veze prema bazi podataka) dane su u
primjeru koda 5.6.5.
Primjer koda 5.6.5.Metode za provođenje zabilježenih operacija nad objektima
(i metoda za kreiranje veze prema bazi podataka)
public class UnitOfWork {
// ...
public void save() {
Connection conn = null;
try {
conn = createConnection();
conn.setAutoCommit(false);
while (!markedEntities.isEmpty()) {
EntityMarkerWrapper emw = markedEntities.remove(0);
DataMapper dm = mappers.get(emw.entity.getClass());
switch (emw.operationMarker) {
case CREATION: dm.create(conn, emw.entity); break;
case UPDATE: dm.update(conn, emw.entity); break;
case REMOVAL: dm.delete(conn, emw.entity); break;
}
}
58
conn.commit();
} catch (SQLException ex) {
System.out.println("Transaction error.");
ex.printStackTrace(System.out);
if (conn != null) {
try {
conn.rollback();
} catch (SQLException ex1) {
ex.printStackTrace(System.out);
}
}
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException ex) {
ex.printStackTrace(System.out);
}
}
}
}
private Connection createConnection() {
try {
Class.forName("org.apache.derby.jdbc.ClientDriver").newInstance();
return DriverManager.getConnection(
"jdbc:derby://localhost:1527/mydb",
"username",
"password");
} catch (Exception ex) {
ex.printStackTrace(System.out);
throw new RuntimeException(ex);
}
}
}
Primjer koda 5.6.6. prikazuje primjer kako bi se objekt klase UnitOfWork mogla primjenjivati u programskom
kodu neke aplikacije.
Primjer koda 5.6.6.Primjer korištenja objekta klase UnitOfWork
UnitOfWork uow = new UnitOfWork();
// ...
uow.markForCreation(entityToCreate1);
uow.markForCreation(entityToCreate2);
uow.markForRemoval(entityToRemove);
uow.markForUpdate(entityToUpdate);
uow.save();
59
6. Uzorci dizajna za ORM sustave
Ovo poglavlje će biti drugačije od poglavlja 4. Uzorci dizajna za podatkovni sloj i poglavlja 5. Uzorci
dizajna za rad s bazom podataka u tome što se neće opisivati novi uzorci dizajna. Nego, uzeti će se određeni
uzorci dizajna opisani u prethodno spomenutim poglavljima i opisati zašto bi oni bili korisni u izgradnji ORM
sustava.
Kao pomoć za pisanje ovog poglavlja korištene je korisnička dokumentacija od tvrtke Oracle (2013) za Java
EE 6. Specifično, koristiti će se poglavlje o Java Persistence API (nadalje JPA). Navedeni API pruža
programerima funkcionalnost preslikavanja podataka između objekata i relacijskih tablica za Java aplikacije
(Oracle, 2013:579). Na temelju opisa pojedinih elemenata JPA odrediti će se koji od opisanih uzoraka, iz
četvrtog i petog poglavlja, bi bio primijenjiv za izgradnju vlastitog ORM sustava.
Spomenuta korisnička dokumentacija se koristi jer, uz to što je dobro napisana, dobro opisuje elemente API
koji bi se koristili u vlastitim ORM sustavima. Kao dodatak koristi se i korisnička dokumentacija za Hibernate
ORM (Mihalcea et al., 2017), poznati ORM radni okvir otvorenog koda. Navedeni radni okvir koristi
elemente JPA i sama dokumentacija napisana slično poglavlju u Oracle-ovoj korisničkoj dokumentaciji za
JPA.
Nadalje, uzorci koji će se navesti neće koristiti poseban predložak za njihov opis, kao što je korišteno u
četvrtom i petom poglavlju. Umjesto toga, jednostavno će se opisati kako bi identificrani uzorci dizajna bili
korisni za igradnju vlastitog ORM sustava. Naziv potpoglavlja sadržavati će naziv uzorka dizajna koji je
identificiran, ali ako je više uzoraka dizajna primjenjivo na isti ili sličan način tada će se ti uzorci dizajna
grupirati pod odgovarajućim nazivom za poglavlje (npr. uzorci dizajna Active Record, Data Mapper, Row
Data Gateway i Metadata Mapping će se grupirati pod nazivom "Uzorci dizajna za preslikavanje").
6.1. Uzorci dizajna za preslikavanje
Kako ORM sustavi služe za preslikavanje podataka između objekata u objektnom-orijentiranom
sustavu i tablica u relacijskoj bazi podataka, logično je zaključiti da bi se u ORM sustav mogao
implementirati jedan od uzoraka dizajna koji se bave preslikavanjem. Ti uzorci dizajna su: Active Record,
Data Mapper, Row Data Gateway i Metadata Mapping. Uzorak dizajna Table Data Gateway nije uključen
pod uzorke dizajna za preslikavanje jer ono ne vrši preslikavanje, nego služi samo kao zamjena za
konstantnim pisanjem sirovih SQL upita.
Fowler (2003:153) predlaže korištenje uzoraka dizajna Active Record ili Row Data Gateway ako radi o
jednostavnom preslikavanju, a za kompleksnija preslikavanja koristiti uzorak dizajna Data Mapper. No,
kompleksnost preslikavanja nije toliko važno koliko je važna fleksibilnost preslikavanja. ORM sustav bi
trebao znati, za bilo koji objekt entiteta, u koju relacijsku tablicu preslikavati podatke, koji stupac iz relacijske
tablice odgovara nekom polju iz objekta, itd. Bez načina kako da ORM sustav očita te informacije, teško će
se moći kreirati fleksibilan sustav za preslikavanje podataka.
No, zato na scenu stupa uzorak dizajna Metadata Mapping. Metadata Mapping koristi određenu vrstu
60
metapodataka na temelju kojih bi aplikacija, odnosno sustav za preslikavanje, mogao dokučiti kako
preslikavati podatke. Metapodaci mogu biti implementirani pomoću: 1) refleksije i 2) posebne datoteke. JPA
opisuje korištenje refleksije, odnosno anotacija, kao metapodatka, dok Hibernate ORM programerima na
raspolaganju omogućuje korištenje metapodataka kao anotacija i kao datoteke.
Moglo bi se reći da je uzorak dizajna Metadata Mapping najbolji izbor za implementaciju funkcije
preslikavanja u vlastitom ORM sustavu. Ali, Metadata Mapping se može kombinirati i s drugim uzorcima
dizajna za preslikavanje kako bi se ostvarila funkcionalnost fleksibilnog preslikavanja. Kratkim
pretraživanjem na Google Web tražilici pronađen je ActiveJPA radni okvir - ORM radni okvir koji se temelji
na Active Record uzorku dizajna i koristi anotacije kao metapodatke.
6.2. Uzorci dizajna hijerarhije nasljeđivanja tablica
JPA (Oracle, 2003:592) podržava razne strategije za preslikavanje nasljeđivanja (eng. inheritance
mapping strategies) neke hijerarhije klasa. Radi se o mehanizmu koji preslikava podatke iz hijerarhije klasa
u odgovarajuću hijerarhiju tablica iz relacijske baze podataka. Svaka od tih strategija odgovara jednom od
uzoraka dizajna hijerarhije nasljeđivanja tablica, a to su: Single Table Inheritance, Class Table Inheritance i
Concrete Table Inheritance. Hibernate ORM (Mihalcea et al., 2017, 2.11. Inheritance) koristi isti mehanizam
preslikavanja podataka na temelju hijerarhije tablica.
Strategije za preslikavanje nasljeđivanja su (Oracle, 2003:593-594):
• Jedna tablica po hijerarhiji klasa (eng. Single Tablce per Class Hierarchy; odgovara uzorku dizajna
Single Table Inheritance),
• Jedna tablica po konkretnoj klasi (eng. Table per Concrete Class; odgovara uzorku dizajna Concrete
Table Inheritance),
• Spojene podklase (eng. Joined Subclass; odgovara uzorku dizajna Class Table Inheritance).
Kako ti mehanizmi postoje u spomenutim JPA i Hibernate ORM-u, navedeni uzorci dizajna hijerarhije
nasljeđivanja tablica mogli bi se razmotriti za korištenje u vlastitom ORM sustavu.
6.3. Uzorci dizajna za mnogostrukost veza između relacijskih
tablica
JPA (Oracle, 2003:587) navodi podršku za mnogostrukost veza između entiteta, odnosno asocijacije.
To su veze jedan-jedan, jedan-više, više-jedan i više-više. U JPA (Oracle, 2003:587) te veze se označavaju s
posebnim anotacijama nad odgovarajućem polju u klasi entiteta.
No, da bi te anotacije funkcionirale, tablice u bazi podataka moraju biti strukturirane na određen način.Ako
se neko polje u objektu entiteta označi s anotacijom s vrstom veze jedan-jedan, jedan-više i više-jedan, onda
treba postojati vanjski ključ u jednoj ili objem tablicama. Ako se neko polje u objektu entiteta označi s
anotacijom za vrstu veze više-više, onda treba postojati dodatna tablica koja spaja dvije tablice koje su u
takvoj vezi.
61
Moglo bi se reći da je potrebno ORM sustavima dati do znanja jesu nad tablicama u relacijskoj bazi podataka
primijenjeni uzorci dizajna Foreign Key Mapping i Association Table Mapping.
6.4. Embedded Value
Oracle (2003:590) navodi podršku za ugrađene klase (eng. Embeddable Classes) za JPA. To su klase
čiji objekti odgovaraju dva ili više stupaca neke tablice. To odgovara uzorku dizajna Embedded Value.
Prednosti tog uzorka dizajna su jasne: omogućava grupiranje stupaca u jedan objekt koji predstavlja smislenu
cjelinu. Programer vlastitog ORM sustava može odlučiti želi li omogućiti preslikavanje podataka iz više
stupaca u jedan objekt unutar objekta entiteta.
6.5. Identity Field
Primarni ključevi su korišteni kao jedinstvene identifikacijske oznake redaka u nekoj relacijskoj
tablici. Logično je zaključiti da će ORM sustav često zahtijevati poznavanje primarni ključ podatka kako bi
izvršio operacije poput ažuriranja i brisanja, pa čak i čitanja podataka. Prema tome, Identity Field je uzorak
dizajna koji će se definitivno implementirati za ORM sustav, ali treba razmisliti kako ga treba implementirati.
Korisnička dokumentacija za Java EE 6 (Oracle, 2003:585-586) za JPA navodi neka pravila za vezana uz
primarne ključeve koji bi se mogli gledati i kao prijedlozi u implementaciji vlastitog ORM sustava. Na
primjer, jednostavni primitivni ključevi se označavaju s anotacijom @javax.persistence.Id, za složene
ključeve treba se kreirati posebna klasa i oni se označavaju s anotacijama @javax.persistence.EmbeddedId
i @javax.persistence.IdClass Oracle, 2003:585).
6.6. Identity Map
Korisnička dokumentacija za Hibernate ORM (Mihalcea et al., 2017, 2.5.7. Implementing equals()
and hashCode()) objašnjava kako Hibernate osigurava da višestruko učitavanje istog entiteta rezultira
vraćanjem iste instance tog entiteta. Time se rješava problem duplikata entiteta u kojima može doći do
problema sinkronizacije, odnosno ako je jedan objekt entiteta ažuriran, ostali nisu. To također rješava i
problem prevelikog broja objekata učitanih u memoriju.
Takav problem rješava i uzorak dizajna Identity Map. Svaki put kada se učita entitet iz baze podataka, koji
prethodno nije bio učitan, taj objekt će se pohraniti u odgovarajući Identity Map objekt. Tada, svaki sljedeći
put kada se ćeli učitati isti entitet, učitati će se iz objekta Identity Map. Prema tome, uzorak dizajna Identity
Map je preporučljivo implementirati u vlastitom ORM sustavu.
Također, trebalo bi dobro razmisliti kako implementirati uzorak dizajna Identity Map u ORM sustavu. ORM
sustavi ne znaju unaprijed s kojim entitetima rade. Jedno moguće rješenje bi bilo kreirati mali podsustav koji
dinamički kreira i registrira odgovarajuće Identity Map objekte i taj podsustav omotati s jednostavnijim
sučeljem [uzorak dizajna Facade (Gamma et al., 1995:185)].
62
6.7. Query Object
Uzorak dizajna Query Object nije toliko obavezan za ORM sustav, no njegova implementacija je
korisna. Pogotovo ako korisnik tog ORM sustava ne poznaje sintaksu SQL upita, ali zna raditi s objektima.
Razlog njegovog navođenja kao uzorka dizajna primjeren za ORM sustave je što JPA daje na raspolaganje
sličan API - Criteria API.
Pomoću Criteria API formiraju se upiti kao objekti, a ti upitni objekti se onda koriste kao upiti prema bazi
podataka (Oracle, 2013:661). U ovom radu za primjer implementirao se relativno jednostavan primjer uzorka
dizajna Query Object, no za vlastiti ORM sustav implementacije može biti znatno komplicirana. Na temelju
primjera za Criteria API (Oracle, 2013:661-671) može se vidjeti koliko komplicirano, ali učinkovito, Query
Object može biti implementiran.
6.8. Unit of Work
Glavni razlog navođenja ovog uzorka dizajna kao uzorka koji bi bio primjenjiv u nekom vlastitom
ORM sustavu je što se u korisničkoj dokumentaciji za Hibernate ORM (Mihalcea et al., 2017, 1. Architecture)
navodi da je objekt Session, koji je u JPA ekvivalentan objektu EntityManager, konceptualno modeliran
prema uzorku dizajna Unit of Work kojeg Fowler opisuje.
Ti objekti služe za bilježenje učitanih i novokreiranih objekata entiteta i omogućuju brisanje tih objekata kao
i njihovo ažuriranje. U neku ruku, ti objekti implementiraju i uzorak dizajna Facade (Gamma et al., 1995:185)
tako da pruža sučelje samo za osnovne operacije.
63
7. Refaktoriranje podatkovnog sloja
Fowler (1999:53-54) koristi dvije definicije za definiranja pojma refaktoriranja. Jedna definicija
opisuje pojam refaktoriranja kao imenice, a druga definicija opisuje pojam refaktoriranja kao glagola. Kao
imenica, refaktoriranje je izmjena u unutrašnjoj strukturi programa (moglo bi se reći i "izmjena u
programskom kodu") koja ne mijenja njegovo vanjsko ponašanje, ali je čini razumljivijom i lakšom za
izmijeniti. Kao glagol, refaktoriranje znači restrukturirati program uvođenjem niza refaktoriranja (imenica)
bez mijenjanja njegovog vanjskog ponašanja.
Na temelju tih dviju definicija mogu se izvući dva moguća ključna pojma: 1) "izmjena unutarnje strukture" i
2) "neizmijenjeno vanjsko ponašanje." Prvi ključni pojam bi bila aktivnost koja se provodi refaktoriranjem,
a drugi pojam bi bio ograničenje na tu aktivnost. Odnosno, na temelju toga refaktoriranje bi bila aktivnost
izmjene programskog koda aplikacije, ali tako da se održi njeno izvorno ponašanje. Pitanje je: zašto mijenjati
programski kod ako ponašanje ostaje isto? Odgovor na to pitanje sadržano je u definiciji pojma refaktoriranja
kao imenice i Fowler (1999:54) također to naglašuje u svojoj knjizi: kako bi programski kod bio čitljiviji za
druge programere i kako bi se lakše mogao izmijeniti, ili čak nadograditi, u budućnosti. Ako je "izmjena
unutarnje strukture" aktivnost i "neizmijenjeno vanjsko ponašanj" ograničenje, onda bi se moglo reći da je
"čitljiviji i lakše izmjenjivi koda" rezultat refaktoriranja.
Treba napomenuti da se refaktoriranje ne poistovjećuje s uzorcima dizajna. Uzorci dizajna rješavaju probleme
oko dizajna arhitekture računalnog sustava. Na primjer, uzorak dizajna Singleton, kojeg opisuju Gamma et
al. (1995), omogućuje da se neki objekti instanciraju samo jednom u životnom ciklusu aplikacije i omogućava
globalni pristup do tih objekata. Refaktoriranja se primjenjuju kako bi se programski kod računalnog sustava
učinio čitljivijim i lakše izmjenjivim. Svako refaktoriranje opisano u Fowlerovoj knjizi (1999) sadrži listu
koraka kako da se sigurno provedu. Na primjer, refaktoriranje Extract Method služi za izvlačenje fragmenta
programskog koda u zasebnu metodu.
Refaktoriranje podatkovnog sloja može se podijeliti u dva dijela: refaktoriranje programskog koda na razini
podatkovnog sloja i refaktoriranje baze podataka. U kontekstu refaktoriranja programskog koda,
refaktoriranje bi se provodilo nad objektima koji predstavljaju podatke, odnosno podatkovne objekte, i na
druge, ne-podatkovne, objekte koji na neki način doprinose u radu s podatkovnim objektima (npr. Data
Mapper objekti koji olakšavaju preslikavanje objekata između aplikacije i baze podataka). U kontekstu
refaktoriranja baze podataka, refaktoriranje se provodi uglavnom nad samom bazom podataka, Ono ne
obuhvaća samo tablice, već i refaktoriranje drugih aspekata baza podataka, poput procedura, okidača, itd.
7.1. Refaktoriranje programskog koda
Kao što je rečeno, refaktoriranje programskog koda u podatkovnom sloju uglavnom se odnosi na
podatkovne objekte te pomoćne objekte koji na neki način doprinose podatkovnom sloju aplikacije. Klase
tih podatkovnih objekata uglavnom se sastoje od privatnih polja, te za svako polje postoji metoda koja
dohvaća vrijednost tog polja i metoda koja postavlja vrijednost tog polja. No, Fowler (1999:86-87) takve
klase smatra "glupim nositeljima vrijednosti" (eng. dumb data holders) koje bi trebalo izmijeniti kako bi im
64
se dodala vlastita odgovornost.
U četvrtom i petom poglavlju često su se koristili "glupi nositelji vrijednosti" kako bi se prikazao primjer
implementacije raznih uzoraka dizajna vezanih za temu ovog rada. Kao primjer provođenja refaktoriranja na
jednom takvom podatkovnom objektu, uzet će se primjer napisan za uzorak dizajna Data Mapper i
refaktorirati. Primjer refaktoriranja će biti jednostavan i pokazati će se refaktoriranje samo na metodama koje
postavljaju vrijednosti parametara za upit. Također, kako bi se skratila priča prikazati će se samo programski
kod klasa prije refaktoriranja i programski kod klasa nakon refaktorianja.
Primjer koda 7.1.1. pokazuje klasu Employee koja predstavlja podatkovnu klasu i primjer koda 7.1.2.
prikazuje klasu EmployeeMapper, odnosno njene metode za postavljanje vrijednosti upita.
Primjer koda 7.1.1. Klasa Employee prije refaktoriranja
public class Employee {
private long id;
private String name;
private String surname;
// Getter i setter metode
}
Primjer koda 7.1.2. Klasa EmployeeMapper prije refaktoriranja
public class EmployeeMapper extends AbtractMapper<Employee> {
// ...
@Override
protected void setCreateStatementValues(Employee obj, PreparedStatement ps)
throws SQLException {
ps.setString(1, obj.getName());
ps.setString(2, obj.getSurname());
}
@Override
protected void setUpdateStatementValues(Employee obj, PreparedStatement ps)
throws SQLException {
ps.setString(1, obj.getName());
ps.setString(2, obj.getSurname());
ps.setLong(3, obj.getID());
}
@Override
protected void setDeleteStatementValues(Employee obj, PreparedStatement ps)
throws SQLException {
ps.setLong(1, obj.getID());
}
// ...
}
65
Kao što se vidi u primjeru koda 7.1.2., klasa EmployeeMapper često koristi metode za dohvaćanje vrijednosti
polja iz objekta Employee kako bi se postavili vrijednosti upita. Odgovornost postavljanja vrijednosti upita
bi se lagano mogla prebaciti u klasu Employee. Kako bi se to napravilo primijeniti će se refaktoriranje Extract
Method (Fowler et al., 1999:110) za svaku metodu iz primjera 7.1.2. i onda će se primijeniti refaktoriranje
Move Method (Fowler et al., 1999:142) kako bi se te metode prebacila u klasu Employee. Postojeće metode
iz klase EmployeeMapper će se prilagoditi tako da pozivaju nove metode iz objekata klase Employee.
Primjer koda 7.1.3. pokazuje klasu Employee i primjer koda 7.1.4. prikazuje klasu EmployeeMapper, nakon
provođenog refaktoriranja.
Primjer koda 7.1.3. Klasa Employee nakon refaktoriranja
public class Employee {
private long id;
private String name;
private String surname;
// Getter i setter metode
public void setCreateStatementValues(PreparedStatement ps)
throws SQLException {
ps.setString(1, getName());
ps.setString(2, getSurname());
}
public void setUpdateStatementValues(PreparedStatement ps)
throws SQLException {
ps.setString(1, getName());
ps.setString(2, getSurname());
ps.setLong(3, getID());
}
public void setDeleteStatementValues(PreparedStatement ps)
throws SQLException {
ps.setLong(1, getID());
}
}
Primjer koda 7.1.4. Klasa EmployeeMapper nakon refaktoriranja
public class EmployeeMapper extends AbtractMapper<Employee> {
// ...
@Override
protected void setCreateStatementValues(Employee obj, PreparedStatement ps)
throws SQLException {
obj.setCreateStatementValues(ps);
}
@Override
protected void setUpdateStatementValues(Employee obj, PreparedStatement ps)
throws SQLException {
66
obj.setUpdateStatementValues(ps);
}
@Override
protected void setDeleteStatementValues(Employee obj, PreparedStatement ps)
throws SQLException {
obj.setDeleteStatementValues(ps);
}
// ...
}
I time je završeno predviđeno refaktoriranje. Sada, umjesto da objekt klase EmployeeMapper "traži pojedine
informacije od zaposlenika", ono "traži zaposlenika da ispuni formular" i time klasa Employee više nije "glupi
nositelj vrijednosti."
Naravno, treba dobro razmisliti koja refaktoriranja se mogu primijeniti, a koja ne. Odnosno, treba imati na
umu da u određenim slučajevima računalni sustav ne može podržati određena refaktoriranja, Na primjer, ako
računalni sustav koristi postojeći ORM sustav, npr. Hibernate ili EclipseLink, ne može se provesti
refaktoriranje Encapsulate Collection (Fowler, 1999:208). Razlog je što spomenuti ORM sustavi ovise o
metodama za postavljanje i dohvaćanje vrijednosti polja nekog entiteta, a spomenuto refaktorianje
zamjenjuje metodu za postavljanje kolekcije s metodama za dodavanje i brisanje pojedinačnog elementa
kolekcije (npr. metoda setItems() bi bila zamijenjena s metodama addItem() i removeItem()).
7.2. Refaktoriranje baze podataka
Refaktoriranje baza podataka je refaktoriranje koje se provodi nad bazom podataka. Ambler i
Sadalage (2006:14) refaktoriranje baze podataka definiraju kao jednostavna izmjena nad shemom baze
podataka koja poboljšava njen dizajn dok održava njenu bihevioralnu i informacijsku semantiku. U ovoj
definiciji, Ambler i Sadalage (2006:14) podrazumijevaju da "shema baze podataka" uključuje strukturne
aspekte (tablice i pogledi) te funkcionalne aspekte (procedure i okidači) baze podataka.
Jedno od bitnijih razlika između refaktoriranja baze podataka i refaktoriranja programskog koda je u tome
što ovo drugo ne mijenja vanjsko ponašanje aplikacije, dok ovo prvo ne mijenja i ponašanje baze podataka i
mora održavati informacije konzistentnim (Ambler i Sadalage, 2006:15).
Refaktoriranje baze podataka dodatno komplicira stvari u tome što čak i manja refaktoriranja zahtijevaju
provođenje izmjena u aplikaciji koja koristi tu bazu podataka tako da aplikacija može nastaviti normalno
raditi s ažuriranom shemom baze podataka. Koliko refaktoriranje baze podataka komplicira stvari ovisi o
"okolini" u kojoj je smještena. Amber i Sadalage (2006:15) navode sljedeće okoline u kojoj baza podataka
može biti smještena:
1. jedno-aplikacijska okolina za bazu podataka (eng. single-application database environment) i
2. više-aplikacijska okolina za bazu podataka (eng. multi-application database environment).
Jedno-aplikacijska okolina za bazu podataka, prikazana na slici 7.2.1., je najjednostavnija. U ovakvim
67
okolinama podrazumijeva se da su jedinu aplikaciju koja koristi bazu podataka napravili pojedinci koji i
posjeduju bazu podataka. U tom slučaju, i aplikacija i baza podataka se mogu paralelno refaktorirati i
primijeniti (Amber i Sadalage, 2006).
Slika 7.2.1. Jedno aplikacija okolina za bazu podataka [vlastiti uradak temeljen
na Ambler i Sadalage (2006:16)]
Kod više-aplikacijskih okolina za bazu podataka, prikazano na slici 7.2.2., paralelno refaktoriranje baze
podataka i aplikacija je skoro pa nemoguće. U ovoj okolini postoje razne druge aplikacije koje koriste bazu
podataka nad kojima vlasnik baze podataka nema kontrolu i ne može odmah od njih zatražiti da sinkroniziraju
izmjene koje su nastale refaktoriranjem. U tom slučaju, tijekom refaktoriranja uvodi se "prijelazno razdoblje"
(eng. transition period).
Slika 7.2.2. Više-aplikacijska okolina za bazu podataka [vlastiti uradak temeljen
na Ambler i Sadalage (2006:16)]
Pretpostavimo da se shema baze podataka prije refaktoriranja nalazi u početnom stanju, a nakon
refaktoriranja nalazi se željenom stanju. Tijekom prijelaznog razdoblja shema baze podataka nalazi se
68
istovremeno i u početnom i u željenom stanju. Na taj način aplikacije koje jesu refaktorirane zajedno s bazom
podataka mogu koristiti željeno stanje baze podataka, a aplikacije koje nisu refaktorirane mogu koristiti
početno stanje baze podataka. Na prvi pogled, ovo se može činiti zbunjujuće, ali je u stvari veoma
jednostavno za shvatiti. Najbolje bi bilo prikazati kako funkcionira prijelazno razdoblje uz primjer.
Primjer na slici 7.2.3. prikazuje usporedni ER dijagrami za tablicu client kada se nad njom primijeni
refaktoriranje Rename Table (Ambler i Sadalage, 2006:113) kako bi se tablica preimenovala u customer. Za
ovo refaktoriranje potrebno je kreirati novu tablicu s željenim imenom i s istom shemom kao tablica client.
Nakon toga, potrebno je sve podatke prebaciti iz tablice client u drugu tablicu. Na kraju refaktoriranja briše
se stara tablica. U prijelaznom razdoblju obje tablice će biti prisutne u bazi podataka.
Slika 7.2.3. Provođenje Rename Table refaktoriranja
Jedan problem kod provođenja refaktoriranja Rename Table je sinkroniziranje tih dviju tablica tako da se
69
operacija kreiranja, ažuriranja i brisanja redova iz jedne tablice preslika u drugu tablicu. Rješenje takvog
problema je uvođenje okidača na obje tablice koja će tu operaciju ponoviti u drugoj.
Još jedan važan aspekt prijelaznog razdoblja je određivanje duljine trajanja tog razdoblja. Nakon što prođe
to razdoblje, sve što je ostalo od stare baze podataka se briše. Ambler i Sadalage (2006:18) navode kako bi
prijelazno razdoblje trebalo trajati minimalno jednu i pol godine kako bi ostali razvojni timovi imali vremena
ažurirati svoje aplikacije tako da mogu koristiti novu shemu baze podataka.
Postoji i način kojim bi se omogućilo drugim aplikacijama korištenje refaktorirane sheme baze podataka bez
potrebe za uvođenjem prijelaznog razdoblja. Učahuri se pristup bazi podataka (Ambler i Sadalage, 2006:27).
Ambler i Sadalage (2006:27) da takvo učahurivanje može implementirati pomoću posebnim objektima za
pristup podacima (eng. data access objects), procedurama iz baze podataka ili čak s pomoću Web servisa.
70
8. Aplikacija
U sklopu ovog diplomskog rada napravljena je i aplikacija. Glavni cilj za izradu ove aplikacije nije
razvoj potpune aplikacije koja bi bila spremna za korištenje u produkciji, već je razvoj primjera aplikacije
koja koristi neke od uzoraka dizajna koji su opisani u poglavljima 4. Uzorci dizajna za podatkovni sloj i 5.
Uzorci dizajna za rad s bazom podataka.
U ovom poglavlju opisati će se što aplikacija radi, spomenuti će se tehnologije koje su korištene za njen
razvoj, prikazati struktura baze podataka u obliku ER dijagrama i objasniti, prikazati će se klase entiteta koji
se koriste u aplikaciji u obliku dijagrama klasa i objasniti i navesti će se korišteni uzorci dizajna te prikazati
će se njihove implementacija putem dijagrama klasa i/ili programskog koda. Opis aplikacije neće biti znatno
detaljan jer, u kontekstu njenog korištenja, radi se o jednostavnoj aplikaciji.
8.1. Opis aplikacije
Radi se o minimalističkoj Web aplikaciji za upravljanje ljudskim resursima. Kao "uzor" i "inspiracija
za ideju" bila je OrangeHRM4, besplatna Web aplikacija s istom namjenom, ali znatno potpunija u kontekstu
funkcionalnosti.
Ovakva vrsta aplikacije je odabrana jer pretežito radi s podacima: kreira nove podatke, ažurira i briše
postojeće podatke, povezuje manje podatke (status zaposlenja, vještine, posao, itd.) u jedan veći podatak
(zaposlenik). Zato je takva vrsta aplikacije savršena za prikaz primjene uzoraka dizajna za podatkovni sloj.
Za ovu aplikaciju definirani su sljedeći entiteti za koje korisnik ima slobodu kreirati, brisati te ažurirati:
• zaposlenik,
• organizacijska jedinica tvrtke,
• posao,
• status zaposlenja (zaposlen, nezaposlen, itd.),
• vještina zaposlenika,
• licenca (npr. licenca za korištenje IntelliJ IDEA Ultimate razvojno okruženje),
• stručno osposobljavanje (u aplikaciji skraćeno na "trening", tj. training),
• kupac/klijent i
• projekt.
Većina navedenih entiteta služe kao atributi za entitet zaposlenika, osim entiteta kupca/klijenta koji se
uglavnom koristi kao informacija o tome tko je vlasnik projekta. No, korisnik ima slobodu kreiranja, brisanja
i ažuriranja svakog od navedenih entiteta.
4https://www.orangehrm.com/
71
8.2. Korištene tehnologije
Ova sekcija poglavlja dijeli se u tri podređene sekcije: integrirano razvojno okruženje (IDE), Web
poslužitelj, razvojni okvir i baza podataka.
8.2.1. Integrirano razvojno okruženje (IDE)
Za integrirano razvojno okruženje (eng. integrated development environment, IDE) korišten je
Netbeans 8.2. To razvojno okruženje može se koristiti za razvoj raznovrsnih stolnih i Web aplikacija u
programskom jeziku Java i Java EE, respektivno. Također sadrži module za razvoj C/C++ aplikacija, podršku
za HTML, CSS i JavaScript te module za razvoj Web aplikacija u PHP jeziku.
Za aplikaciju u sklopu diplomskog rada koristio se samo modul za razvoj Java EE Web aplikacija.
8.2.2. Razvojni okvir
Za razvojni okvir (eng. framework) korištena je JavaServer Faces (JSF) tehnologija za Java EE. JSF
je razvojni okvir za razvoj Web aplikacija čiji je glavni fokus razvoj korisničkog sučelja (Oracle, 2013:59).
Ona omogućuje korištenje Facelets tehnologije za generiranje Web stranica (Oracle, 2013:60) i posebnih
vrsta zrna zvanih Managed Bean kojom se implementiraju operacije na strani poslužitelja koje poziva klijent
(preko stranica generiranih s Facelets).
JSF je primarno korišten za razvoj prezentacijskog sloja aplikacije koja poziva određene poslovne metode
kojima se kreiraju, ažuriraju i brišu podaci iz podatkovnog sloja..
8.2.3. Web poslužitelj
Za ovu aplikaciju potreban je Web poslužitelj koji podržava JSF tehnologiju prikaza Web stranica.
Postoje tri, besplatna, kandidata: Oracle Glassfish, Apache TomEE i Wildfly. Sva tri kandidata imaju svoje
prednosti i mane, a napravljena aplikacija bi lagano radila sa sva tri poslužitelja.
Na početku razvoja aplikacije bio je odabran Oracle Glassfish. No, pred krajem razvoja poslužitelj je počeo
povremeno izbacivati greške koje nisu mogle biti riješene sa strane razvoja aplikacije. Pod povremeno misli
se da bi ponekad izbacio tu specifičnu grešku, a ponekad bi aplikacija normalno radila.
Zbog toga, odlučilo se Web aplikaciju prenijeti na drugi Web poslužitelj: Apache TomEE. Netbeans 8.2. IDE
nema podršku za kreiranje EJB modula za Apache TomEE, pa se cijela Web aplikacija spojila u jedan projekt.
Greške se više nisu pojavljivale i razvoj se znatno ubrzao zbog toga što je Apache TomEE veoma brzo mogao
kompilirati kod Web aplikacije i pokrenuti je.
Ukratko, za Web poslužitelj koristi se Apache TomEE.
8.2.4. Sustav za upravljanje bazom podataka
Za ovu Web aplikaciju nije bila zamišljena mogućnost fleksibilne komunikacije s različitim
sustavima za upravljanje bazama podataka (SUBP). Naravno, to može dovesti do problema gdje treba
prilagoditi "rukom pisane" upite na specifičan način ako korišteni SUBP koristi neki svoj žargon.
No, to nije problem u ovoj aplikaciji jer svi "rukom pisani" upiti u njoj su oni upiti čija je sintaksa jednaka
72
kod svih ostalih baza podataka (to se ugkavnom misli na SELECT, UPDATE i DELETE upite). Također, sve
tablice moraju biti unaprijed pripremljene za ovu aplikaciju, inače neće funkcionirati.
Odlučilo se na korištenje Apache Derbi sustava za upravljanje bazom podataka.
8.3. Struktura baze podataka
Struktura baze podataka korištena u aplikaciji prikazana je u obliku ER dijagrama na slici 8.3.1.
Sastoji se od osam relacijskih tablica koje predstavljaju konkretne entitete i četiri pomoćnih tablica koje
postoje samo radi implementacije veze više-više između određenih entiteta.
Nadalje, svaka zasebni entitet (osim onih koje implementiraju vezu više-više) iz baze podataka biti će opisan
u zasebnom, podređenom, poglavlju. Svaki opis entiteta opisivati će što taj entitet predstavlja te pojasniti
njegovu vezu s onim entitetima s kojima je povezan.
73
Slika 8.3.1. ER dijagram baze podataka
Također, treba spomenuti da svaka relacijska tablica, koja predstavlja konkretni entitet, sadrži atribut id kao
cjelobrojni primarni ključ.
8.3.1. Entitet employee
Entitet employee prestavlja zaposlenika i ono se može smatrati glavnim podatkom koji se koristi u
aplikaciji. Osnovni podaci koji opisuju ovaj entitet su ime i prezime zaposlenika (atributi first_name i
last_name), iznos i valuta njegove plaće (atributi salary_amount i salary_currency) te početak i kraj radnog
vremena (atributi working_hour_start i working_hour_end).
74
Iduća tri atributa su vanjski ključevi koji referenciraju retke iz tablica employment_status,
organizational_unit i job. Atribut employment_status referencira redak iz relacijske tablice
employment_status i opisuje trenutni status zaposlenja zaposlenika (zaposlen, nezaposlen, itd.). Atribut
organizational_unit referencira redak iz relacijske tablice organizational_unit i opisuje kojoj organizacijskoj
jedinici pripada zaposlenik. Atribut job referencura redak iz relacijske tablice job i predstavlja naziv posla,
odnosno poslovnu titulu koja je dodijeljena zaposleniku.
Entitet employee nalazi se u vezi više-više sa sljedećim relacijskim tablicama: skill, training, license i project.
S relacijskom tablicom, odnosno entitetom, skill opisuju se koje vještine posjeduje zaposlenik. S entitetom
training opisuje se koja stručna osposobljavanja je zaposlenik položio i može se navesti i datum početka
polaganja te datum završetka polaganja. S entitetom license opisuje se koje licence su dodijeljene zaposleniku
i može se navesti datum dodijele licence. S entitetom project opisuje se kojim projektima je zaposlenik
dodijeljen.
8.3.2. Entitet organizational_unit
Entitet organizational_unit predstavlja organizacijsku jedinicu tvrtke, odnosno organizacije. Osim
primarnog ključa, atribut sadrži još tri atributa. Prvi atribut, name, predstavlja naziv organizacijske jedinice.
Drugi atribut, description, predstavlja opis te organizacijske jedinice. Treći atribut, superordinate, je
rekurzivna veza jedan-više entiteta organizational_unit sa samim sobom i ono predstavlja nadređenu
organizacijsku jedinicu koju bilo koja organizacijska jedinica može imati.
Osim rekurzivne veze sa samim sobom, entitet organizational_unit se također nalazi u vezi jedan-više s
entitetom employee, gdje se entitet zaposlenika na strani više. To znači da employee sadrži vanjski ključ koji
referencira zapis iz entiteta organizational_unit.
8.3.3. Entitet job
Entitet job predstavlja posao, odnosno poslovnu titulu, koja može biti dodijeljena bilo kojem
zaposleniku. Uz atribut primarnog ključa, sadrži i još dva atributa. Prvi atribut, name, predstavlja naziv posla,
odnosno naziv poslovne titule. Drugi atribut, description, predstavlja opis tog posla.
Nalazi se u vezi jedan-više s entitetom employee, gdje se entitet zaposlenika na strani više. To znači da
employee sadrži vanjski ključ koji referencira jedan podatak iz entiteta job.
8.3.4. Entitet employment_status
Entitet employment_status predstavlja status zaposlenja koje može biti dodijeljeno bilo kojem
zaposleniku. Osim atributa primarnog ključa, sadrži i još dva atributa. Prvi atribut, name, predstavlja naziv
status zaposlenja, a drugi atribut, description, služi za opis statusa zaposlenja.
Nalazi se u vezi jedan-više s entitetom employee, gdje se entitet zaposlenika na strani više. To znači da
employee sadrži vanjski ključ koji referencira zapis iz entiteta organizational_unit.
8.3.5. Entitet skill
Entitet skill predstavlja vještinu koju može posjedovati bilo koji zaposlenik. Slično kao i većini
75
entiteta, entitet skill sadrži atribut primarnog ključa i još dva dodatna atributa. Prvi dodatni atribut, name,
predstavlja naziv vještine, a drugi atribut, description, predstavlja opis te vještine.
Nalazi se u vezi više-više s entitetom employee i ta veza je realizirana pomoću relacijske tablice
employee_skill.
8.3.6. Entitet training
Entitet training predstavlja stručno osposobljavanje koje može biti dodijeljeno bilo kojem
zaposleniku da je položio. Osim atributa primarnog ključa, sadrži i atribut name koje predstavlja naziv
stručnog osposobljavanja i atribut description koje opisuje to stručno osposobljavanje.
Nalazi se u vezi više-više s entitetom employee i tu vezu realizira u relacijskoj tablici employee_training.
Spomenuta tablica sadrži dodatne atribute koji opisuju kojeg datuma je započeto stručno osposobljavanje
koje je pridruženo zaposleniku te kojeg datuma je isto stručno osposobljavanje završilo.
8.3.7. Entitet license
Entitet license predstavlja licencu koja može biti dodijeljena bilo kojem zaposleniku. Uz atribut
primarnog ključa, entitet sadrži i atribut name koji predstavlja naziv licence i atribut description koja služi
kao kratak opis licence.
Ovaj entitet se nalazi u vezi više-više s entitetom employee i ona je realizirana pomoću tablice
employee_license. Ta tablica također sadrži atribut koji opisuje kada je licenca dodijeljena zaposleniku.
8.3.8. Entitet customer
Entitet customer predstavlja nekog kupca ili klijenta. Uz primarni ključ sadrži i atribut name koji
predstavlja naziv klijenta (može biti osoba ili tvrtka), atribut address koji predstavlja adresu klijenta, atribut
email koji predstavlja e-mail adresu i atribut phone koji sadrži broj telefona od klijenta.
Ovaj entitet se nalazi u vezi jedan-više s entitetom project, gdje se project nalazi na strani više i sadrži vanjski
ključ koji referencira zapisa iz relacijske tablice customer.
8.3.9. Entitet project
Entitet project predstavlja projekt koji je pokrenut na zahtjev nekog klijenta ili vlastite organizacije.
Uz atribut primarnog ključa, sadrži atribut name koji projektu daje naziv i atribut customer koji povezuje
projekt s klijentom koji ga je pokrenuo na zahtjev.
Ovaj entitet se nalazi u vezi više-više s entitetom employee i ta veza je realizirana pomoću tablice
employee_project.
Također, ovaj entitet se nalazi i u vezi jedan-više s entitetom customer, gdje se ono nalazi na strani više. Ta
veza je realizirana pomoću vanjskog ključa u atributu customer, koji pokazuje na zapis iz relacijske tablice
customer.
76
8.4. Klase entiteta
Slika 8.4.1. prikazuje dijagram klasa koji prikazuje klase entiteta, apstraktnu klasu Entity te dodatne,
pomoćne, klase koje su korištene od strane klasa entiteta. Svaka od prikazanih klasa entiteta odgovara jednoj
relacijskoj tablici (ne uključujući tablice koje postoje samo da se implementira veza vuše-više između drugih
tablica) prikazanoj na slici 8.3.1. u poglavlju 8.3. Struktura baze podataka.
U ovom dijelu poglavlja opisati će se apstraktna klasa Entity, odnosno kakvu ulogu ona ima u Web aplikaciji,
i opisati će se svaka pojedina klasa entiteta. Opis klase entiteta također će uključivati opis dodatnih, pomoćnih,
klasa, tj. opisati će se za što i zašto se one koriste u klasama entiteta koje se opisuju.
77
Slika 8.4.1. Dijagram klasa za klase entiteta i pomoćnih klasa
Dodatne i pomoćne klase su na slici 8.4.1. označene svijetlom sivom bojom kako bi se razlikovale od ostalih
klasa koje će biti opisane u ovom poglavlju. Također treba napomenuti da u dijagramu klasa nisu prikazane
metode za dohvaćanje i postavljanje vrijednosti polja jer bi te metode učinile dijagram klasa prevelikim.
8.4.1. Apstraktna klasa Entity
Apstraktna klasa Entity sadrži polje koje sadrži vrijednost primarnog ključa. Spomenuto je u
poglavlju 8.3. Struktura baze podataka kako su svi primarni ključevi cjelobrojni, prema tome je i polje u
klasu Entity cjelobrojnog tipa. Glavni razlog postojanje ove klase je dodjeljivanje svim podatkovnim klasama
zajednički tip podatka. Ova apstraktna klasa omogućuje fleksibilnu implementaciju uzoraka dizajna kao što
su Data Mapper i Identity Map koji ovise o klasi objekata s kojima rade.
8.4.2. Klasa Employee
Klasa Employee predstavlja entitet zaposlenika i tijekom preslikavanja objekti te klase rade s
relacijskom tablicom employee. Kada bi se usporedila relacijska tablica employee s klasom Employee
primijetilo bi se nekoliko razlika. Jedna od tih razlika je što su polja salary_amount i salary_currency iz
tablice zamijenjeni objektom tipa Money, razlog tome što je klasa Money u stvari je implementacija uzorka
dizajna Embedded Value.
Također, klasa Employee sadrži dva polja tipa CustomDate. Klasa CustomDate nije implementacija ikojeg
opisanog uzorka dizajna, već je samo pomoćna klasa koja pomaže u radu s datumima. Specifično, pomaže
da se datum može učitati kao String ili java.sql.Date objekt i da se može pretvoriti opet ili u String ili u
java.sql.Date objekt.
Još jedna razlika se može primijetiti u tome što klasa Employee ne koristi direktno objekte klasa License i
Training nego preko klasa AcquiredLicense i ScheduledTraining. Razlog tome je što je zamišljeno da svako
objekt klase entiteta sadrži samo podatke koji se nalaze u odgovarajućoj relacijskoj tablici. No, to znači da
podaci koji su definirani u asocijativnim tablicama employee_license i employee_training nemaju gdje biti u
aplikaciji. Zato su definirane spomenute pomoćne klase: objekt AcquiredLicense omotava jedan objekt
License s objektom datuma dodjele to licence, a objekt ScheduledTraining omotava objekt Training s
datumskim objektima koji predstavljaju početak polaganja stručnog osposobljavanja i kraj polaganja.
8.4.3. Klasa EmploymentStatus
Klasa EmploymentStatus predstavlja entitet statusa zaposlenja i njeni objektirade s relacijskom
tablicom employment_status. Instance ove klase se dodjeljuju objektima zaposlenika kako bi se opisale
njihov pojedinačan status zaposlenja, poput "zaposlen", "nezaposlen", "na odmoru", itd.
8.4.4. Klasa OrganizationalUnit
Klasa OrganizationalUnit predstavlja organizacijske jedinice neke tvrtke, odnosno organizacije.
Objekti ove klase rade s relacijskom tablicom organizational_unit. Ova klasa se dodjeljuje entitetu
78
zaposlenika kako bi se dala informacija u kojoj organizacijskoj jedinici je zaposlenik uvršten.
Može se primijetiti da klasa sadrži jedan dodatno polje u usporedbi s relacijskom tablicom, a to je polje
subordinates (hrv. podređeni). Kako polje superordinate (hrv. nadređeni) daje informaciju o tome koja je
nadređena organizacijska jedinica, polje subordinates je obratno – daje popis podređenih organizacijskih
jedinica.
8.4.5. Klasa Job
Klasa Job predstavlja entitet posla, odnosno poslovne titule. Objekti te klase rade s relacijskom
tablicom job. Dodjeljuje se objektima entiteta zaposlenika kako bi se dala informacija koju poslovnu titulu
ima zaposlenik.
8.4.6. Klasa Skill
Klasa Skill predstavlja entitet vještine i njeni objekti rade s relacijskom tablicom skill. Objekt
zaposlenika sadrži popis vještina. Budući da su entiteti zaposlenika i vještina u vezi više-više, tijekom
preslikavanja podataka o zaposleniku mora se gledati koje vještine su pridružene zaposleniku u relacijskoj
tablici employee_skill u bazi podataka.
8.4.7. Klasa License
Klasa License predstavlja entitet licence i njeni objekti rade s relacijskom tablicom license. Entiteti
zaposlenika i licenci nalaze se u vezi više-više, što znači da aplikacija tijekom preslikavanja podataka o
zaposleniku mora gledati koje licence su dodijeljene tom zaposleniku u relacijskoj tablici employee_license
iz baze podataka.
No, objekti klase Employee ne sadrže direktno objektno klase License, već ih drže omotane u objektima klasa
AcquiredLicense. Razlog tome je što objekti klase License ne sadrže informacije o datumu dodijele licence
koja se nalazi u tablici employee_license jer oni sadrže podatke samo iz tablice License. Zato je definirana
klasa AqcuiredLicense čiji objekti će omotati objekt klase License s datumom dodijele te klase i dodijeliti ih
zaposlenicima.
8.4.8. Klasa Training
Klasa Training predstavlja entitet stručnog osposobljavanja. Objekti te klase rade s relacijskom
tablicom training. Entiteti stručnog osposobljavanja i zaposlenika nalaze se u vezi više-više, što znači da
aplikacija tijekom preslikavanja podataka o zaposleniku mora gledati koja stručna osposobljavanja su
pridružena tom zaposleniku u relacijskoj tablici employee_training.
Kao i u slučaju klase License, klasa Employee također ne sadrži direktno objekte klase Training, već ih drže
omotane u objektima klase ScheduledTraining. Razlog tome je što objekti klase Training drže samo one
informacije koje se nalaze u relacijskoj tablici training, a što dovodi do toga da se gube informacije koje se
nalaze u dodatnim poljima asocijativne tablice employee_training. Zato je definirana klasa
ScheduledTraining čiji objekti omotavaju objekte klase Training s datumskim objektima koji predstavljaju
početak i kraj polaganja stručnog osposobljavanja, i takve omotane objekte dodijeliti zaposlenicima.
79
8.4.9. Klasa Customer
Klasa Customer predstavlja entitet kupca/klijenta i njeni objekti rade s relacijskoj tablicom customer.
Objekti ove klase se dodjeljuju objektima klase Project kako bi se naznačilo koji klijent je razlog pokretanja
projekta.
8.4.10. Klasa Project
Klasa Project predstavlja entitet projekta kojeg je pokrenuo neki klijent i njeni objekti rade s
relacijskom tablicom project. Entiteti projekta i zaposlenika nalaze se u vezi više-više, što znači da tijekom
preslikavanja podataka u objekt zaposlenika aplikacija treba gledati koji projekti su dodijeljeni tom
zaposleniku u relacijskoj tablici employee_project.
8.5. Korišteni uzorci dizajna
U ovom dijelu poglavlja opisati će se uzorci dizajna koji su korišteni u aplikaciji. Za svaki uzorak
dizajna ukratko će se objasniti zašto su oni korišteni te će se prikazati njihova implementacija pomoću
dijagrama klasa i/ili programskog koda. Od opisnih uzoraka dizajna u poglavljima 4. Uzorci dizajna za
podatkovni sloj i 5. Uzorci dizajna za rad s bazom podataka, u aplikaciji se koriste sljedeći:
• Data Mapper,
• Identity Map,
• Embedded Value,
• Value Object i
• Lazy Load.
Također, korišteni su uzorci dizajna Foreign Key Mapping i Association Table Mapping, ali oni neće biti
opisani jer se smatra da su ti uzorci dizajna "opća praksa" u radu s bazom podataka.
8.5.1. Uzorak dizajna Data Mapper
Uzorak dizajna Data Mapper korišten je kako bi se implementirao mehanizam preslikavanja podataka
između objekata i baze podataka. Sav temeljni mehanizam je implementiran je u apstraktnoj klasi
DataMapper, koji je također korišten za primjer uzorka Data Mapper u poglavlju 5.2. Data Mapper. Slika
8.5.1. prikazuje dijagram klasa koji prikazuje implementaciju uzorka Data Mapper u aplikaciji.
Apstraktna klasa DataMapper je također generička klasa, ali sve njene implementacije ograničene su klase
koje nasljeđuju apstraktnu klasu Entity. Jedan razlog za to je osiguravanje da implementacija te apstraktne
klase koristi isključivo klasu entiteta. Drugi razlog tome je što metode create(), odnosno doCreate(),
koristi metodu setId() iz klase Entity kako bi se odmah nakon kreiranja podatka u bazi podataka postavila
vrijednost njegovog primarnog ključa koju je generirala baza podataka.
80
Slika 8.5.1. Dijagram klasa za implementirani uzorak dizajna Data Mapper
Također, primijeti se postojanje metode map() koja vraća Identity Map objekt za odgovarajući tip entiteta.
Taj Identity Map objekt se primjenjuje u tri slučaja:
• tijekom kreiranja gdje dodaje novi objekt entiteta u mapu,
• tijekom brisanja gdje se briše objekt entiteta iz mape,
• tijekom čitanja podatka gdje se provjerava postoji li već traženi objekt entiteta unutar mape.
8.5.2. Uzorak dizajna Identity Map
Uzorak dizajna Identity Map se koristi kako bi se spriječilo učitavanje dupliciranih objekata entiteta.
Data Mapper objekti korištenu u aplikaciji koriste Identity Map objekte kako bi se provjerilo postoji li već
instanca objekta entiteta u sustavu. Isti Data Mapper objekti također koriste Identity Map objekte tijekom
kreiranja (gdje novo kreirani objekt odmah ulazi u mapu) i brisanja (gdje se objekt koji se želi obrisati miče
iz mape).
U aplikaciji postoji dretva koja, u određenom vremenskom intervalu, briše sadržaj svih Identity Map objekata
kako bi se privremeno oslobodio prostor u memoriji.
Primjer koda 8.5.1. pokazuje implementaciju uzorka dizajna Identity Map u aplikaciji. Ista implementacija
81
je prikazana u primjeru tog uzorka u poglavlju 4.2. Identity Map.
Primjer koda 8.5.1. Klasa koja implementira uzorak dizajna Identity Map
public class IdentityMap<T extends Entity> {
protected Map<Long, T> entities = new HashMap<>();
public void add(T entity) {
if (entity.getId() != null)
entities.put(entity.getId(), entity);
}
public void remove(Long id) {
entities.remove(id);
}
public boolean contains(Long id) {
return entities.containsKey(id);
}
public void clear() {
entities.clear();
}
public List<T> filter(Predicate<T> predicate) {
List<T> filteredEntities = new ArrayList<>();
entities.forEach((id, entity) -> {
if (predicate.test(entity)) {
filteredEntities.add(entity);
}
});
return filteredEntities;
}
}
8.5.3. Uzorak dizajna Embedded Value
Uzorak dizajna Embedded Value korišten je samo u jednom slučaju: da polja salary_amount i
salary_currency spoji u jedan, smislen, objekt. Klasa Money, koja se na ER dijagramu na slici 8.4.1. može
vidjeti da ju koristi klasa Employee.
Tijekom preslikavanja iz baze podataka u objekt ili obratno, objekt koji vrši preslikavanje, objekt klase
EmployeeMapper, je implementiran da vodi računa o tome da spomenuta dva polja spoji u jedan objekt kada
učitava podatke o zaposleniku iz baze podataka i da rastavi objekt u dva polja kada preslikava podatke iz
objekta u bazu podataka.
Klasa Money, koja implementira uzorak dizajna Embedded Value, prikazana je u primjeru koda 8.5.2.
Primjer koda 8.5.2. Klasa Money koja implementira uzorak dizajna Embedded Value
public class Money {
82
private BigDecimal amount;
private String currency;
public Money(BigDecimal amount, String currency) {
this.amount = amount.setScale(2, RoundingMode.HALF_EVEN);
this.currency = currency;
}
public BigDecimal getAmount() { return amount; }
public String getCurrency() { return currency; }
public void setAmount(BigDecimal amount) {
this.amount = amount.setScale(2, RoundingMode.HALF_EVEN);
}
public void setCurrency(String currency) { this.currency = currency; }
@Override
public String toString() {
return amount.toString() + " " + currency;
}
}
8.5.4. Uzorak dizajna Value Object
Uzorak dizajna Value Object implementiran u apstraktnoj klasi Entity tako da ju koriste svi objekti
entiteta. U aplikaciji, svi objekti entiteta svode svoj identitet na temelju klase kojoj pripadaju i na temelju
vrijednosti primarnog ključa u objektu. U slučaju da dva objekta entiteta pripadaju istoj klasi, imaju istu
vrijednost primarnog ključa, ali se razlikuju prema vrijednosti ostalih polja, onda se pretpostavlja da jedan
od tih objekata predstavlja ažuriranu verziju drugog objekta.
Primjer koda 8.5.3. prikazuje implementaciju uzorka dizajna Value Object u apstraktnoj klasi Entity.
Podsjetnik, Value Object se implementira nadjačavanjem metode equals() i hashCode().
Primjer koda 8.5.2. Klasa Money koja implementira uzorak dizajna Embedded Value
public abstract class Entity {
protected long id;
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public boolean equals(Entity entity) {
return this.getClass().equals(entity.getClass())
&& this.id == entity.id;
}
@Override
public boolean equals(Object obj) {
return obj instanceof Entity && equals((Entity) obj);
}
@Override
83
public int hashCode() {
int hash = 7;
hash = 53 * hash + (int) (this.id ^ (this.id >>> 32));
hash = 53 * hash + Objects.hashCode(this.getClass());
return hash;
}
}
8.5.5. Uzorak dizajna Lazy Load
Uzorak dizajna Lazy Load primjenjuje se samo u klasama Employee i Organizational
Unit. Kod klase Employee, uzorak dizajna se koristi u metodama koje dohvaćaju listu objekata Skill, listu
objekata Project, listu objekata License omotanih u objektima Acquired
License i listu objekata Training omotanih u objektima ScheduledTraining. Kod klase OrganizationalUnit,
uzorak dizajna se koristi u metodi koja dohvaća popis objekata koji predstavljaju podređene organizacijske
jedinicie.
Razlog primjene uzorka dizajna Lazy Load u klasu Organizational Unit je drugačiji od razloga njegove
primjene u klasi Employee. Za klasu Employee uzorak je primijenjen kako bi se dohvatile liste objekata tek
kada bude potrebno ih prikazati u aplikaciji. Za klasu OrganizationalUnit uzorak se primjenjuje jer je objekt
preslikavanja, OrganizationalMapper, tijekom učitavanja podataka konkretnog objekta ulazio u beskonačnu
rekurziju kada je učitavao njegove podređene organizacijske jedinice. No, ako se učitavanje podređenih
organizacijskih jedinica odgodilo primjenom uzorka Lazy Load, aplikacija nije ulazila u beskonačnu
rekurziju.
Primjer koda 8.5.4. pokazuje primjer primjene uzorka ditajna Lazy Load u metodi getSkills() klase
Employee. U toj metodi koristi se metoda findFormEmployee() iz klase SkillMapper koja pronalazi i
vraća sve objekte klase Skill koji su pridruženi zaposleniku u bazi podataka. Programski kod te metode u
klasi SkillMapper prikazan je u primjeru koda 8.5.5.
Može se primijetiti u primjeru koda 8.5.4. korištenje objekta klase EntityMapper. Ta klasa je zrno sesije bez
stanja (eng. Stateless Session Bean) koji sadrži mapu svih korištenih Data Mapper objekata. Ono će biti
objašnjeno u poglavlju 8.6. EntityMapper i EntityMap.
Primjer koda 8.5.4. Primjena uzorka dizajna Lazy Load u metodi getSkills() klase Employee
public class Employee extends Entity {
// ...
private final EntityMapper entityMapper;
public List<Skill> getSkills() {
if (skills == null) {
SkillMapper skillMapper =
(SkillMapper) entityMapper.getMapper(Skill.class);
skills = skillMapper.findForEmployee(this);
}
84
return skills;
}
// ...
}
Primjer koda 8.5.5. Metoda za prinalazk i dohvaćanje objekata klase Skill
koji su pridruženi zaposleniku u bazi podataka
public class SkillMapper extends DataMapper<Skill> {
// ...
private static final String FIND_FOR_EMPLOYEE_STATEMENT =
"SELECT s.* FROM skill s, employee_skill es "
+ "WHERE s.id=es.skill AND es.employee=?";
public List<Skill> findForEmployee(Employee employee) {
List<Skill> skills = new ArrayList<>();
try (Connection conn = createConnection();
PreparedStatement ps = conn.prepareStatement(
FIND_FOR_EMPLOYEE_STATEMENT)) {
ps.setLong(1, employee.getId());
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
Skill skill = load(rs);
skills.add(skill);
}
}
} catch (SQLException ex) {
System.out.println("Unable to find skills for an employee ("
+ getClass().getSimpleName() + "): " + ex.getMessage());
ex.printStackTrace(System.out);
}
return skills;
}
// ...
}
8.6. EntityMapper i EntityMap
EntityMapper i EntityMap su zrna sesije (eng. Session Bean) koji se koriste unutar aplikacije. Entity
Mapper je zrno sesije bez stanja, a EntityMap je zrno sesije za kojeg postoji samo jedna instanca u cijeloj
aplikaciji (eng. Singleton Session Bean).
EntityMapper sadrži mapu koja se sastoji objekata koji vrše preslikavanje između objekata i baze podataka,
odnosno sastoji se od različitih objekata čije klase nasljeđuju apstraktnu klasu DataMapper. Glavni naum
ovo zrna sesije je omogućiti aplikaciji da koristi unaprijed instancirane objekte preslikavanja kada je potrebno.
Primjer koda 8.6.1. prikazuje programski kod EntityMapper klase.
Primjer koda 8.6.1. Klasa EntityMapper
85
@Stateless
@Startup
@LocalBean
public class EntityMapper {
private final Map<Class<? extends Entity>, DataMapper> mappers;
public EntityMapper() {
mappers = new HashMap();
mappers.put(Skill.class, new SkillMapper());
mappers.put(License.class, new LicenseMapper());
mappers.put(EmploymentStatus.class, new EmploymentStatusMapper());
mappers.put(OrganizationalUnit.class, new OrganizationalUnitMapper());
mappers.put(Job.class, new JobMapper());
mappers.put(Customer.class, new CustomerMapper());
mappers.put(Project.class, new ProjectMapper());
mappers.put(Training.class, new TrainingMapper());
mappers.put(Employee.class, new EmployeeMapper());
}
public <T extends Entity> DataMapper getMapper(Class<T> entityClass) {
return mappers.get(entityClass);
}
public <T extends Entity> void create(T entity) {
mappers.get(entity.getClass()).create(entity);
}
public <T extends Entity> void update(T entity) {
mappers.get(entity.getClass()).update(entity);
}
public <T extends Entity> void delete(T entity) {
mappers.get(entity.getClass()).delete(entity);
}
public <T extends Entity> T find(Class<T> entityClass, long id) {
return (T) mappers.get(entityClass).find(id);
}
public <T extends Entity> List<T> findAll(Class<T> entityClass) {
return (List<T>) mappers.get(entityClass).findAll();
}
}
EntityMap sadrži mapu u kojoj se nalaze instance IdentityMap objekata za svaku klasu entiteta. Ovo
omogućuje postojanje samo jednog objekta određene klase entiteta u cijeloj aplikaciji. Također, aplikacija
tijekom pokretanja pokreće i dretvu koja periodički čisti sadržaj svi IdentityMap objekata. Primjer koda 8.6.2.
pokazuje programski kod za klasu EntityMap. Zbog mogućnosti konkurentnog pristupa ovom zrnu sesije
umjesto, običnog HashMap objekta koristi se ConcurrentHashMap i na svim definiranim metodama
pridružena je ključna riječ synchronized.
86
Primjer koda 8.6.2. Klasa EntityMap
@Singleton
@Startup
@LocalBean
public class EntityMap {
private final Map<Class<? extends Entity>, IdentityMap<?>> maps;
public EntityMap() {
maps = new ConcurrentHashMap<>();
maps.put(Skill.class, new IdentityMap<>());
maps.put(License.class, new IdentityMap<>());
maps.put(EmploymentStatus.class, new IdentityMap<>());
maps.put(OrganizationalUnit.class, new IdentityMap<>());
maps.put(Job.class, new IdentityMap<>());
maps.put(Customer.class, new IdentityMap<>());
maps.put(Project.class, new IdentityMap<>());
maps.put(Training.class, new IdentityMap<>());
maps.put(Employee.class, new IdentityMap<>());
}
public synchronized <T extends Entity>
IdentityMap<T> getMap(Class<T> entityClass) {
return (IdentityMap<T>) maps.get(entityClass);
}
public synchronized <T extends Entity>
List<T> filter(Class<T> entityClass, Predicate<T> predicate) {
return ((IdentityMap<T>) maps.get(entityClass)).filter(predicate);
}
public synchronized void clearAll() {
maps.forEach((entityClass, identityMap) -> identityMap.clear());
}
}
87
9. Zaključak
U ovom diplomskom radu prošlo se kroz osnovne ideje primjene uzorka dizajna, uključujući zašto
se koriste i kako oni rješavaju probleme u dizajnu. Nakon toga provela se usporedna analiza predložaka za
opis uzoraka dizajna koje su koristili Gamma et al. (1995), Buschmann et al. (1996) i Fowler (2003) u svojim
knjigama o uzorcima dizajna, nakon čega se predstavio vlastiti predložak koji je korišten za opis uzoraka
dizajna u ovom radu. Zatim, iduća dva poglavlja bila su posvećena opisom uzoraka dizajna koji bi se mogli
primijeniti na podatkovni sloj i koji bi se mogli koristiti u radu s bazom podataka. Na temelju opisanih
uzoraka, odredilo koji bi mogli korišteni u razvoju ORM sustava.Nakon toga, govorilo se refaktoriranju
podatkovnoj sloja u dva konteksta: u kontekstu refaktoriranja programskog koda i refaktoriranja baze
podataka. I, na kraju, opisala se aplikacija koja je rađena u sklopu ovog rada, uključujući prikaz i opis
strukture njene baze podataka, prikaz i opis klasa entiteta te opisali su korišteni uzorci dizajna.
Kao i "originalni" uzorci dizajna koje su opisali Gamma i njegovi suradnici (1995) i Fowlerovi uzorci, koji
su opisani u ovom radu i imaju primjenu u podatkovnom sloju aplikacije, mogu olakšati i/ili obogatiti
aplikaciju. Uzorci u ovom radu također daju dobar pogled na razne načine kako se mogu koristiti podaci ili
provoditi operacije s njima. Od svih opisanih uzoraka, za najznačajnije bih odabrao uzorke Data Mapper,
Active Record i IdentityMap. Prva dva uzorka dizajna su uzorci za preslikavanje podataka i oni lijepo
pokazuju kako pojednostaviti provođenje CRUD upita nad bazom podataka. Identity Map pak ukazuje na
problem koji neiskusni programeri često ignoriraju – problem duplikata objekata entiteta – i kako ga riješiti.
Za kraj, izraziti će se isto mišljenje koje imam i za sve ostale vrste uzoraka dizajna: isplati ih se proučiti. Ne
samo kao rješenja problema u dizajnu, već i za bolje razumijevanje objektno-orijentiranog programiranja.
Posebice za pojedince koji se prvi put susreću s paradigmom objektno-orijentiranog programiranja.
88
Literatura
[1] Ambler S. W., Sadalage P. J. (2006) Refactoring Databases: Evolutionary Database Design. Boston:
Addison-Wesley.
[2] Buschmann F., Meunier R., Rohnert H., Sommerlad P., Stal M. (1996) Pattern-Oriented Software
Architecture Volume 1: A System of Patterns. Chichester: John Wiley & Sons.
[3] Fowler M., Beck K., Brant J., Opdyke W., Roberts D. (1999) Refactoring: Improving the Design of
Existing Code. Boston: Addison-Wesley.
[4] Fowler M. (2003) Patterns of Enterprise Application Architecture. Boston: Addison-Wesley.
[5] Gamma E., Helm R., Johnson R., Vlissides J. (1995) Design Patterns: Elements of Reusable Object-
Oriented Software. Boston: Addison-Wesley.
[6] Mihalcea V., Ebersole S., Boriero A., Morling G., Badner G., Cranford C., Bernard E., Grinovero S.,
Meyer B., Ferentschik H., King G., Bauer C., Andersen M. R., Maesen K., Vansa R., Jacomet L. (2017)
Hibernate ORM 5.2.10.Final User Guide. [Online] Dostupno na:
http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_
User_Guide.html (Pristupljeno: 03.08.2017.)
[7] Oracle (2013) The Java EE 6 Tutorial. [Online] Dostupno na: http://docs.oracle.com/
javaee/6/tutorial/doc/javaeetutorial6.pdf (Pristupljeno: 03.08.2017.)