SVEUČILIŠTE U ZAGREBU FAKULTET ORGANIZACIJE I INFORMATIKE V A R A Ţ D I N Senko Pušec ZODB ZAVRŠNI RAD Varaţdin, 2016.
SVEUČILIŠTE U ZAGREBU
FAKULTET ORGANIZACIJE I INFORMATIKE
V A R A Ţ D I N
Senko Pušec
ZODB
ZAVRŠNI RAD
Varaţdin, 2016.
SVEUČILIŠTE U ZAGREBU
FAKULTET ORGANIZACIJE I INFORMATIKE
V A R A Ţ D I N
Senko Pušec
Matični broj: 39216/11–I
Studij: Poslovni sustavi
ZODB
ZAVRŠNI RAD
Mentor:
Doc.dr.sc Markus Schatten
Varaţdin, rujan 2016.
1
Sadrţaj
1. Uvod..................................................................................................................................... 2
2. Objektne baze podataka ....................................................................................................... 3
2.1. Povijesni razvoj objektno-orijentiranih baza ................................................................. 3
2.2. Osnovni koncepti ........................................................................................................... 4
2.2.1. Usporedba OID-a sa Vanjskim ključem .................................................................. 5
2.3. Objektno-orijentirana baza podataka ............................................................................. 5
2.3.1. Usporedba sa relacijskim bazama podataka ............................................................ 6
2.4. Objektno-relacijske baze podataka ................................................................................ 7
3. ZODB ................................................................................................................................... 7
3.1. Svojstva ZODB-a .......................................................................................................... 7
3.2. Vrste pohrane u ZODB-u .............................................................................................. 9
3.2.1. Pickling .................................................................................................................... 9
3.2.2. Binarna stabla ......................................................................................................... 10
3.2.3. Buckets ................................................................................................................... 10
3.2.4. Pohrana kao atribut/pohrana kao BTree ................................................................. 11
3.2.5. BinaryLargeOBject ................................................................................................ 11
4. Phone Book aplikacija ........................................................................................................ 12
4.1. Dijagram klasa za bazu ................................................................................................ 13
4.1.1. Klasa User ............................................................................................................. 14
4.1.2. Klasa Contact ....................................................................................................... 14
4.1.3. Klasa PersonalContact .......................................................................................... 15
4.2. Prijava i rad u aplikaciji ............................................................................................... 15
4.2.1. Upiti prema bazi te rad s listama ............................................................................ 16
5. Zaključak ............................................................................................................................. 19
6. Literatura ............................................................................................................................. 20
7. Prilog - programski kod ...................................................................................................... 21
2
1. Uvod
Objektno-orijentirane baze iako već dugo postoje nisu u često spominjane, barem ne
čisto objektno-orijentirane baze podataka. Iako smo se za vrijeme obrazovanja često sretali s
principima objektno-orijentirane paradigme u programskim jezicima, rješenja koja smo
razvijali često su za spremanje podataka imale obične relacijske baze podataka. Prvo
susretanje s objektno-orijentiranim bazama podataka zapravo su bili ORM alati bilo u Javi
(Persistence) ili u C# (Entity Framework) koje su nam pokazali kako je puno lakše razvijati
aplikaciju bez SQL upita već putem LINQ-a i sl.
MeĎutim ORM nije isto što i objektno-orijentirana baza podataka jer ORM zapravo
sakriva relacijsku bazu podataka i jednostavno mapira tablice u bazi podataka u klase u
programskom jeziku, dakle sve je ipak u pozadini SQL upit. Stoga smo odlučili provjeriti
čistu objektno-orijentiranu bazu podataka, a to je bio nativna python objektno-orijentirana
baza podataka ZODB, koja je nastala iz Zope Web Framework-a i koja omogućuje spremanje
čistih objekata u bazu. Postoji velik broj komercijalnih i besplatnih sustava za upravljanje
objektno-orijentiranim bazama meĎutim ZODB se čini najjednostavnijim i najlakšim za
korištenje zbog manjka dodatnog koda za integraciju u aplikacije.
3
2. Objektne baze podataka
Objektna baza (eng. object database) ili objektno orijentirana baza podataka, je sustav
upravljanja bazama podataka u kojoj su informacije predstavljene u obliku objekata.
Relacijske baze podataka koje predstavljaju jedan od stupova informatike su jedno vrijeme
bile dostatne za tradicionalne aplikativne domene kao što je procesiranje administrativnih
podataka itd, meĎutim daljnjim razvojem informatike u mnogim drugim aplikativnim
domenama dolazi do obrade puno kompleksnijih vrsta podataka. Takvi podaci su uglavnom
bili pohranjivani u podatkovnom podsustavu operacijskog sustava ili i u specijaliziranim
podatkovnim strukturama prije nego u relacijskim bazama podataka. Jedan od primjera
kompleksnih podataka koji su se na takav ili sličan način obraĎivali je CAD/CAM sustav
(eng. computer aided modeling / design). Porastom količine podataka, mnoge značajke
DBMS sustava su postajale sve zanimljive je i štoviše potrebne. MeĎutim kako bi se te
značajke DBMS sustava mogle upotrebljavati u gore navedenim slučajevima korištenja bilo je
potrebito uvesti podršku kompleksnih tipova podataka u DBMS-ove. Koncepti objektno-
orijentiranog programiranja su uvelike utjecali na zahtjev DBMS podrške kompleksnih tipova
podataka i eventualno dovele do stvaranja objektno orijentiranih baza podataka odnosno
sustava za upravljanje objektno orijentiranim bazama podataka. Objektne baze podataka su se
kasnije razvijale u dva različita smjera: objektno-orijentirane baze te objektno-relacijske
baze.[2]
2.1. Povijesni razvoj objektno-orijentiranih baza
Objektne baze podataka ili objektno-orijentirane baze podataka su nastale na temelju
istraživanja za unutrašnju podršku strukturiranih podataka (graf) u sustavima za upravljanje
bazama podataka. Od tih istraživanja najvažnije je spomenuti sljedeća koja su imali utjecaj na
razvoj objektno orijentiranih baza: IRIS (Hewlett-Packard), ODE (Bell Labs), ORION (MCC
- Microelectronics and Computer Technology Corporation) i Zeitgeist (TI - Texas
Instruments). Rani komercijalni proizvodi su bili Gemstone (Gemstone Systems), Gbase
(Graphael) i VBase (Ontologic). Na razvoj objektno-orijentiranih baza za vrijeme 80-ih
godina 20. stoljeća veliki utjecaj je imala sve veća zainteresiranost u objektno-orijentirane
programske jezike. Objektno-orijentirani programski jezici kao što su C++ i Simula su se tek
pojavili na programskoj sceni i pružali su potpuno novi pristup razvoju software-a koji je
naglašavao korištenje objekata, te enkapsulacije objektne strukture i ponašanja korištenjem
4
apstraktnih tipova podataka. Spajanje perzistentnih, na objektima temeljenih, podataka sa
objektno orijentiranim programskim jezicima obećavalo je novu paradigmu za baze podataka
koja bi zajedno sa fleksibilnošću objektno orijentiranih programskih jezika omogućila
efikasnu reprezentaciju velikih i kompleksnih setova podataka. Najveća prednost paradigme
objektno orijentiranih baza podataka je bilo rješenje impendance mismatch problema što joj je
omogućavalo jednostavnu integraciju baze podataka sa programskim jezikom. Impendance
mismatch problem je problem koji se odnosi na razlike izmeĎu deklarativnog pristupa bazama
podataka i relacijskog, imperativnog odnosno pristupa programskih jezika prema podacima.[]
2.2. Osnovni koncepti
Jedan od glavnih ciljeva objektnih baza podataka je podrška za perzistentnost objekata
zajedno značajkama koje se uobičajeno očekuju od sustava baza podataka, a neke od tih
značajki su: učinkovito upravljanje perzistentnim podacima, transakcije, istodobnost (eng.
concurrency) i kontrola oporavka. Izazov za objektno orijentirane baze podataka je podrška
gore navedenih značajki u kontekstu objektno orijentiranog programiranja i njegove
složenosti.
Sljedeći koncepti objektno orijentiranog programiranja su tipično vezani uz objektno
orijentirane baze podataka (Dietrich i Urban, 2014) :
složeni odnosno kompleksni objekti
identitet objekta
enkapsulacija ili učahurivanje
ekstenzibilnost
hijerarhija klasa i nasljeĎivanje
preopterećenje (overloading), polimorfizam, i late binding
Mogućnost definiranja kompleksnih objekata iz jednostavnijih je važno svojstvo
objektno-orijentiranog pristupa. Kompleksni objekti su definirani korištenje konstruktora, kao
što je npr. tuple (par) konstruktor koji kombinira jednostavne objekte kako bi napravio
kompleksniji objekt. Jedan od primjera gore navedene značajke objektno-orijentiranog
pristupa bi bio avion, za koji se može reći da je kompleksni objekt koji se sastoji od drugih
jednostavnijih objekata kao što krila, trup i motori.[1]
5
Objekti isto tako imaju objektni identitet putem interno dodijeljenog objektnog
identifikatora (oid). Za razliku od ključeva u relacijskom modelu, oid je nepromjenjiv
(immutable) što znači da kada je objekt jednom kreiran oid ostaje isti za vrijeme trajanja
objekta. Usporedno s tim stanje objekta je promjenjivo (mutable) što znači da se vrijednosti
svojstava (eng. property) mogu mijenjati. Sustav baze podataka koristi oid kao referencu
izmeĎu objekata kako bi stvorio kompleksne objekte. Vrijednosti svojstava se stoga ne koriste
kao jedinstveni identifikatori za objekt u bazi podataka. To je zasebnost po kojoj se razlikuj
objektno-orijentirani model od modela relacijskih baza podataka, koji koristi vrijednosti
atributa (primarni - vanjski ključ).[2]
Enkapsulacija ili učahurivanje se odnosi na mogućnost stvaranja klase kao apstraktnog
tipa podatka, koji ima sučelje i implementaciju. Sučelje definira ponašanje apstraktnog tipa
podatka na konceptualnoj razini dok implementacija definira realizaciju tog ponašanja na
razini programskog jezika. Korištenjem koncepta učahurivanja, implementacija klase se može
promijeniti bez utjecanja na sučelje koje klasa pruža ostatku aplikacije. Objektno-orijentirane
baze podataka podržavaju učahurivanje specificiranjem tipova definiranih od strane korisnika.
Sa ekstenzibilnošću nema razlike izmeĎu korištenje tipova koji su definirani od strane
sustava i onih koje definira korisnik. Odnosno korisnik može stvoriti nove tipove koji
odgovaraju semantici aplikacije i koristiti ih na isti način kao i tipove koje je definirao sustav.
2.2.1. Usporedba OID-a sa Vanjskim ključem
Kao što je gore navedeno, korištenje oid kao referencu za objekt je vrlo slično
korištenju vanjskog ključa za referencu para (eng. tuple) ali s mali i bitnim razlikama. OID
može pokazivati na objekt koji je spremljen bilo gdje u bazi podataka, čak i ako se nalazi u
polju, dok je vanjski ključ ograničen na pokazivanje objekta koji se nalazi u specifično
referenciranoj relaciji. (Ramakrishnan Gehrke, 2003)
2.3. Objektno-orijentirane baze podataka
Objektno-orijentirane baze podataka ili sustavi za upravljanje objektno-orijentiranim
bazama podataka su baze podataka u kojima se spremaju objekti umjesto podataka kao što su
integeri ili string-ovi itd. Radi lakšeg pojašnjenja koncepta objektno-orijentiranih baza
podataka ukratko ćemo objasniti objekte. Objekti se prema konceptima objektno-orijentiranog
pristupa sastoje od atributa i metoda. Atributi su podaci koji odreĎuju karakteristike objekta,
6
oni mogu biti jednostavni tipovi podataka (integer, string) ili reference na složenije tipove
podataka. Metode odreĎuju ponašanje objekta odnosno to su procedure i funkcije.
2.3.1. Usporedba sa relacijskim bazama podataka
Relacijske baze podataka spremaju podatke u tablice koje su dvodimenzionalne.
Tablice imaju stupce i retke i normalizirane su kako se podaci sadržani u njima ne ponavljaju
više nego što je potrebno. Svi stupci u tablici ovise o primarnom ključu, koji je jedinstvena
vrijednost u stupcu, radi identifikacije. Kada je specifični stupac identificiran, podaci iz
jednog ili više redaka povezanih s tim stupcem, mogu se dohvatiti ili izmijeniti. Kompleksni
tipovi podataka se ne mogu lagano spremati u relacijske baze podataka jer to zahtijeva
razbijanje kompleksnih informacija u jednostavne podatke i zahtjeva puno vremena i u većini
slučajeva puno koda. Prednosti objektno-orijentiranih baza podataka naspram relacijskih baza
podataka su:
Objekti se ne moraju sastavljati i rastavljati time štedeći vrijeme implementacije tj
kodiranja i izvršavanja
smanjeno straničenje
lakša navigacija
Bolja kontrola istodobnosti (eng. concurrency)
Model podataka se temelji na stvarnoj pojavi tj stvarnom svijetu
Dobro funkcionira za distribuirane arhitekture
Manje programskog koda ako se aplikacije pridržavaju objektno-orijentiranog
pristupa
MeĎutim u nekim slučajevima su objektno-orijentirane baze loš odabir. Ukoliko se
radi o jednostavnim aplikacijama učinkovitost te brzina pristupa su bolje u relacijskim
bazama podataka.[1] Stoga možemo reći da objektno-orijentirane baze podataka funkcioniraju
bolje sa:
CAS aplikacijama (CASE - computer aided software engineering, CAD - computer
aided design, CAM - computer aided manufacture)
Multimedijskim aplikacijama
Trgovinske aplikacije (eng. commerce)
7
2.4. Objektno-relacijske baze podataka
Objektno-relacijske baze podataka su vrlo slične relacijskim bazama podataka ali
sadrže objektno-orijentirani model baze podataka. Objekti, klase i nasljeĎivanje su direktno
podržane i shemama baze podataka i jeziku za upite. Za objektno-relacijske baze podataka se
može reći kako predstavljaju kompromis izmeĎu objektno-orijentiranih baza i relacijskih.[1]
3. ZODB
ZODB ili Zope Object DataBase je nativna objektno-orijentirana baza podataka za
python programski jezik. jedne od glavnih značajki koje razlikuju ovu objektno-orijentiranu
bazu podataka od ostalih rješenja su: za operacije na bazi podataka se koristi python a ne neki
zasebni programski jezik, potrebne su jako male promjene koda kako bi se objekti napravili
perzistentnima, nepostojanje mapper-a za mapiranja baze podataka koji zapravo samo skriva
jedan dio baze (ORM nije isto što objektno-orijentirana baza podataka iako se na prvi pogled
možda čini tako), nije potrebna velika količina dodatnog koda za integraciju s bazom.[6]
3.1. Svojstva ZODB-a
Jedno od glavnih svojstava koje najviše privlači developere ZODB-u je njegov
minimalizam. On je zapravo ogoljena baza podataka koji sadrži samo osnovne značajke kao
što je perzistentnost i podršku za transakcije, ali to ga ne čini slabijim ili lošijim od ostalih
objektno-orijentiranih baza podataka jer, kao i većina modula u python ekosustavu, postoji
pregršt ekstenzija za nj. Jezgru samog ZODB-a čine sljedeće značajke: familijarnost - kao
što
je prethodno rečeno, ne koristi se zasebni programski jezik za operacije na bazi podataka, u
bazu se spremaju nativni python objekti te se koristi pickle modul koji je većini python
developera poznat. Budući da je cijelio ZODB pisan u pythonu moguće je vidjeti unutrašnje
mehanizme i operacije samog ZODB-a te, iako vrlo rijetko, napraviti preinake na njemu.
Jednostavnost ZODB-a je isto tako vrlo bitna značajka. Sama baza podataka je hijerarhijska
baza podataka što znači da imam korijenski object ili root koji se inicijalizira prilikom
stvaranje same baze. Taj se objekt potom koristi na isti način kao i dictionary u pythonu te
može sadržavati druge objekte koji i sami mogu biti dictionary tipa, iako postoje neke stvari
koje se trebaju uzeti u obzir i koje su kasnije opisane. Transparentnost - da bismo napravili
instancu klase perzistentnom potreban nam je modul persistence odnosno njegov dio a to je
8
klasa Persistence. Nakon što definiramo klasu koja nasljeĎuje od klase Persistence ostali dio
spremanja objekata i ažuriranja istih, ukoliko doĎe do njihove promjene, obavlja ZODB. Isto
tako mogu se spremati objekti koji nisu perzistentni kao što je običan dictionary ili list
meĎutim tada ZODB ne može detektirati promjene na njima i potrebno ga je obavijestiti kada
doĎe do promjene. ZODB podržava ACID odnosno svoja ACID-a. Transakcijski sustavi se
brinu o stanju baze podataka odnosno sprječavaju da baza podataka doĎe u nekonzistentno
stanje, a to uspijevaju podrškom četiri svojstva koji se poznatiji pod akronimom ACID. ACID
akronim predstavlja:
Atomičnost (eng. atomicity) - ili će se sve izmjene u transakciji izvršiti odnosno zapisati na
bazu podataka ili ukoliko doĎe do pogreške i sl. cijela transakcija će biti otkazana. Upravo to
omogućuje održavanja konzistentnog stanja baze podataka u slučaju hardverske greške ili
greške pisanja.
Konzistentnost (eng. consistency) - nijedna transakcija koja zapisuje podatke na bazu neće biti
dozvoljena ukoliko bi ona ostavila bazu podataka u nekonzistentnom stanju, transakcija koja
čita odnosno dohvaća podatke će vidjeti bazu podataka u konzistentnom stanju u kojem je bila
na početku transakcije bez obzira na druge transakcije koje se istovremeno izvršavaju.
Izoliranost (eng. isolation) - prilikom izvršavanje promjena na bazi podataka od strane dva
različita programa, nijedan od njih neće moći vidjeti transakcije od drugoga dok ne izvrše
vlastite transakcije (eng. commit)
Postojanost (eng. durability) - podaci će biti spremljeni nakon što je transakcija izvršena.
Softverski ili hardverski kvar neće uzrokovati gubitak podataka nakon što je transakcija
izvršena.[7]
U slučaju velikih promjena na bazi, odnosno u slučaju izvršavanje jedne transakcije
kojim se modificira veliki broj objekata istovremeno dolazi do velikog zauzeća memorije koje
usporava rad, a razlog usporavanje leži u načinu funkcioniranja ZODB-a, naime sve promjene
koje se izvršavaju u jednoj transakciji se zadržavaju u memoriji dok se transakcija ne izvrši.
Kako bi se poboljšale performanse i smanjilo zauzeće memorije, ZODB sadrži svojstvo Save
Point koje omogućuje developeru da izvrši jedan dio transakcije prije nego je transakcija
gotova kako bi se zapisale promjene na bazi podataka i oslobodio dio memorije koju je
transakcija zauzela. Ukoliko transakcija koja je izvršena nije trebala biti izvršena ili se
greškom prebrisao dio baze ZODB sadrži jednostavan mehanizam za vraćanje baze u
prethodno stanje odnosno za poništavanje (eng. roll back) transakcije koji se zove Undo. Ova
značajka postoji i funkcionira zato što ZODB pamti stanje baze podataka prije i poslije svake
transakcije. Upravo to omogućava poništavanje bilo koje transakcije tj. promjena koje je
9
transakcija izvela na bazi. Treba uzeti u obzir kako je ovo jako jednostavan mehanizam te
ukoliko je objekt nakon transakcije koju želimo poništiti ponovno promijenjen od strane
druge transakcije, poništavanje neće biti moguće zbog gore navedenih ACID svojstava
odnosno zbog konzistentnosti. S obzirom da, kao što je gore navedeno, ZODB pamti svaku
promjenu na bazi odnosno pamti svaku transakciju, moguće je vidjeti stanje objekta prije i
poslije promjene (transakcije) te ih usporediti što omogućuje implementaciju jednostavnog
verzioniranja. ZODB dakle može spremati skoro pa sve vrste objekata koji postoje u python
programskom jeziku, meĎutim ukoliko želimo spremiti posebne vrste podataka kao što su
multimedijski podaci itd. potrebno je koristiti ZODB-ov Blob. Veliki binarni objekti kao što
su slike i dokumenti bi se trebali spremati u Blob objekt jer bi njihovo spremanje kao običnih
svojstava objekta znatno usporilo bazu podataka te značajno povećalo veličinu baze. ZODB
potom ima specijalnu vrstu Storage-a koje se zove Blob Storage pomoću kojeg je moguće
lako rukovati velikim datotekama bez degradiranja performansi. [7]
3.2. Vrste pohrane u ZODB-u
Vrste različitih pohrana (storing backends) u ZODB:
Pickling
Binarna stabla (engl. Binarytrees)
Buckets
Pohrana kao atribut / pohrana kao BTree
BLOBs (BinaryLargeOBject)
U nastavku je objašnjeno kako se podatci spremaju u ZODB što je bitno za razumijevanje
ponašanja i optimizacije Plone baze podataka.
3.2.1. Pickling
ZODB je objektno orijentirana baza podataka. Svi podaci u ZODB se pohranjuju kao pickled
Python objekti. Pickle je objektni serializacijski modul Python standardne biblioteke.
10
Svaki put kada je objekt pročitan i nije spremljen u meĎuspremnik (not cached),
objekt se čita iz ZODB baze podataka i unpickled je
Svaki put kad je objekt napisan, on je pickled i pomoću sustava za transakcije ga se
dodaje na ZODB bazu podataka
Pickle format je niz bajtova. Na donjem primjeru možemo vidjeti kako to izgleda:
>>> import pickle
>>>data = { "key" : "value" }
>>>pickled = pickle.dumps(data)
>>>printpickled
(dp0
S'key'
p1
S'value'
p2
s.
To nije format optimalan za čitanje. Čak i ako koristimo SQL temeljeni RealStorage ZODB
backend, objekti su i dalje pickled na bazu podataka, SQL ne podržava varirajući tabličnu
shemu po redu i Python objekti nemaju fiksnu shmenu za format.
3.2.2. Binarna stabla
Podatci su obično organizirani u binarna stabla ili Bstabla (engl. BTrees). Točnije, podatci se
obično pohranjuju kao objektno orijentirana binarna stabla (engl. OOBtree) koja daju Python
objekt kao ključ i Python object value mapping. Ključ je objekt id u parentcontainer-u kao
string, a vrijednost je bilo koji pickleable Python objekt ili primitive koju spremamo u bazu
podataka.[7]
3.2.3. Buckets
BTree pophranjuje podatke u buckets (OOBucket).
Bucket je najmanja jedinica podataka koje se jednom upisuje u bazu podataka. Buckets se
pune sporo: BTree učitava jedino bucket-e koji spremaju vrijednosti ključeva kojima se
pristupa. BTree pokušava staviti što je više moguće podataka u jedan bucket. Kada se jedna
vrijednost u bucket-u promijeni, cijeli bucket mora biti prepisan na disk. Zadana veličina
bucket-a je 30 objekata.[7]
11
3.2.4. Pohrana kao atribut / pohrana kao BTree
Plone ima dva temeljna načina za pohranu podataka:
Attributestorage (pohranjuje vrijednosti direktno u pickled objekte)
Annotationstorage (OOBTreebased) – plone objekti imaju atribut _annotations_ koji je
OOBTree za pohranu objekata bez konflikta prilikom imenovanja.
Kada su objekti pohranjeni u annotation storage obliku, čitanje vrijednosti objekta zahtjeva
barem jedan dodatni upit u bazi podataka, za učitavanje prvog bucket-a iz OOBTree.
Ako će se vrijednost koristiti često, posebice ako se čita kod pregleda sadržaja objekta,
pohranjivanje u attribute je efikasnije nego pohranjivanje u annotation. Razlog tome je što
BTree _annotation_ je zasebni perzistentni objekt koji mora biti učitan u memoriju i može
izbaciti nešto drugo iz ZODB meĎuspremnika (engl .ZODB cache).
Ako je u atributu pohranjena velika vrijednost povećati će se upotreba memorije, jer će se
učitati u memoriju svaki put kada je objekt dohvaćen iz ZODB.[7]
3.2.5. BinaryLargeOBject
BLOBs su veliki binarni objekti poput datoteka ili slika. Podržani su od verzije ZODB 3.8.x.
Kada koristimo BLOB sučelje za pohranu i dohvaćanje podataka, oni su spremljeni fizički
kao datoteke na datotečnom sustavu. Datotečni sustav, kao što mu ime govori, je osmišljen
kako bi upravljao podatcima i ima daleko bolje performanse sa velikim binarnim podatcima
nego pohranjivanje podataka u ZODB.
BLOBs su streamable što znači da možemo početi posluživati datoteku od početka te datoteke
na HTTP, bez potrebe da se učita cijela datoteka u memoriju(što je sporo).[7]
12
4. Phone Book aplikacija
Primjer korištenja ZODB-a u napravljen za ovaj završni rad je jednostavna aplikacija
za upravljanje kontaktima. Korišteni su sljedeći moduli:
tkinter - GUI za aplikaciju
flask_bcrypt - za enkripciju lozinke
ZODB - objektno-orijentirana baza podataka
persistent - generička implementacija perzistentnosti
ZEO - Zope Enterprise Objects, client-server sistem za dijeljenje spremnika (storage)
izmeĎu više klijenata
transaction - generička implementacija transakcija
logging - standardni mehanizam za logiranje
Jedan od uvjeta postavljenih za ovu temu je bilo korištenje Client Storage-a odosno mrežnog
spremnika i zbog toga je korišten modul ZEO koji unutar sebe sadrži runzeo.py skriptu koja
pokreće mrežnog poslužitelja na kojem se nalazi spremnik i koji se potom dijeli izmeĎu
korisnika
13
4.1. Dijagram klasa za bazu
Class diagram koji je korišten u ovoj aplikaciji je vrlo jednostavan i sastoji se od klase user
,contact te personalContact. Klasa user sadrži svojstva (eng. property) username ili korisničko
ime, password ili lozinka te PersistenList object Contacts u koji se spremaju kontakti nakon
što se pozove funkcija add_contacts().
1. Dijagram klasa koje se spremaju u bazu
Sve klase koje se nalaze na dijagramu nasljeĎuju klasu Persistent koja je
implementacija generičke perzistencije za python. Korisnik može imati više osobnih
kontakata ali kontakt može imati samo jednog autora koji je objekt tipa User.
Budući da sve klase nasljeĎuju baznu klasu Persistent, ZODB može detektirati promjene na
njima i tako obavijestiti ostale klijente koje koriste istu bazu odnosno Client Storage.
14
4.1.1. Klasa user
Klasa user kao što je vidljivo na dijagramu klasa ima svojstva USERNAME,
PASSWORD te CONTACTS.
2. Isječak koda koji prikazuje implementaciju klase user
Prilikom instanciranja klase odnosno poziva se ugraĎena python funkcija __init__()
odnosno u slučaju pythona konstruktor. On prihvaća dva argumenta, username i password,
password se pomoću modula flask_bcrypt hashira radi sigurnosti jer spremanje lozinki u bazi
u obliku čistog texta je primjer loše prakse.
Drugi zanimljivi dio je dodavanje kontakata pomoću funkcije add_contact() koja instancira
klasu PersonalContact koja nasljeĎuje klasu Contact. Kao parametre prima name,
contact_number te nasljeĎivanjem i parametar autor gdje se stavlja trenutni user koji
instancira klasu.
4.1.2. Klasa Contact
Sadrži svojstva:
NAME - ime kontakta koji se dodaje
CONTACT-NUMBER - telefonski broj/mobitel kontakta
AUTOR - korisnik koji je kreirao ovaj kontakt
15
3. Isječak koda koji prikazuje implementaciju klase Contact
4.1.3. Klasa PersonalContact
NasljeĎuje sva svoja svojstva od klase Contact.
4. Isječak koda koji prikazuje implementaciju klase PersonalContact
4.2. Prijava i rad u aplikaciji
Aplikacija je vrlo jednostavna i sastoji se od samo dva prozora, prvi prozor je logiranje
korisnika odnosno provjera njegovih podataka u bazi podataka.
5. Prozor za prijavu korisnika
Nakon uspješne prijave korisnika, korisniku se otvara prozor tk2 koji predstavlja sve njegove
kontakte i omogućuje mu CRUD nad svojim kontaktima. Korisnik vidi isključivo kontakte
16
koje je on kreirao i ne druge. Dakle nakon uspješne prijava na sustav se pokreće upit koji
dohvaća sve kontakte kojima je svojstvo autor jednako autoru koji se uspješno prijavio na
sustav.
6. Prozor za rad sa kontaktima
U listbox-u se nalaze svi kontakti koje je napravio trenutno prijavljeni korisnik. Klikom na
neki od njih te pritiskom na tipku Load dohvaćaju se dodatni podaci o trenutno odabranom
kontaktu i ispisuju se u textbox-ove. Za brisanje kontakta opet se mora odabrati jedan kontakt
iz listobox te potom pritisnuti dugme Delete. Za dodavanje novog korisnika potrebno je
upisati podatke u polja name i phone te potom pritisnuti na tipku Add. Za ažuriranje korisnika
potrebno je odabrati korisnika iz listbox-a te potom obaviti promjene u poljima name i/ili
phone te stisnuti dugme Update.
4.2.1. Upiti prema bazi te rad s listama
Većina upita dolje opisana i prikazana su vrlo jednostavna i zahvaljujući
jednostavnosti pythona vrlo kratka. Slični upiti u drugim programskim jezicima bi vjerojatno
zahtijevali veći broj iteracija i veću količinu koda.
Inicijalizacija baze te dodavanje početnih korisnika se izvršava u tri koraka, prvi korak je
samo spajanje na ClientStorage, otvaranje konekcije te dohvaćanje root elementa iz baze.
Potom se dodaju dva nova ključa 'korisnici' te 'kontakti' koji su tipa persistent.PersistentList().
Nakon dodavanja ključeva dolazi do instanciranje user klase gdje dodajemo par probnih
korisnika te probnih kontakata koji se potom dodaju u bazu.
17
7. Isječak koda koji prikazuje implementaciju klase database
8. Isječak koda koji prikazuje dodavanje početnih korisnika i njihovih kontakata
9. Isječak koda koji prikazuje pražnjenje tablice sa korisnicima
Nakon početnog postavljanja baze podataka, možemo početi raditi sa Persistent listama kod
kojih su operacije u načelu jednake kao i operacije kod običnih python listi. Dakle elementi u
listi imaju svoje indekse i sl
Dohvaćanje korisnika putem aplikacije funkcionira na sljedeći način:
18
[x for x in root['kontakti'] if x.AUTOR.NAME == 'senko']
Nakon što je upit izvršen vraća nam se lista koja ovisno o tome postoji li kontakt kojeg je
kreirao autor 'senko' vraća jedan ili više elemenata, ako ne postoje kontakti koje je kreirao
korisnik 'senko' vraća se prazna lista. Ukoliko želimo dobiti samo jednog korisnika iz liste
tada koristimo standardnu python sintaksu za liste:
[x for x in root['kontakti'] if x.AUTOR.NAME == 'senko'][0]
Iako je ovo tehnički loš primjer s obzirom da će dohvatiti prvog korisnika u listi, meĎutim
možemo još dodatno sačuvati gornju listu u varijabli i onda po njoj na isti način izvršiti upit:
[x for x in rezultat if x.NAME == 'marko'][0]
S ovim upitom vraća nam se prvi element u listi koji odgovara argumentima postavljenima u
upitu. Dakle glavni predložak za rad s listama izgleda sljedeće:
[predlozak for varijabla in lista if uvjet]
I uvijek kao rezultat vraća objekt tipa lista, osim ako ne odredimo indeksu koji želimo vratiti
kao što je opisano gore.[8] Možemo se još dodatno poigrati s listama i tražiti vraćanje
isključivo imena kontakata koji odgovoraju upitu:
[x.NAME for x in root['kontakti'] if x.AUTOR.NAME == 'senko']
Dakle u ovom slučaju upit vraća listu korisničkih imena koji su povezani s autorom 'senko'.
Brisanje podataka iz liste je isto tako jednostavno:
del root['kontakti'][index_zapisa]
del root['kontakti'][:]
del root['kontakti'][:3]
Prva operacija briše specifični indeks u listi, druga operacija briše sve elemente u listi, treća
operacije briše sve elemente u listi do elementa s indeksom tri.[8]
19
5. Zaključak
Zope Object DataBase je jedan od boljih primjera jednostavnosti i lakoće rada s objektnim-
bazama. Pisana u potpunosti u python programskom jeziku, omogućuje lako korištenje svih
python operacija nad objektima i time omogućuje lako izvršavanje programske logike te brzo
dohvaćanje podataka iz baze. MeĎutim za osobe koje dolaze bez predznanja o opčenitom
načinu rada s objektnim bazama može predstavljati poneke poteškoće, usprkos tome možemo
reći kako je rad sa ZODB-om te njegova integracija bilo u desktop aplikacije ili web
aplikacije vrlo jednostavna.
ZODB je vrlo jednostavna i zbog toga moćna objektno-orijentirana baza podataka ali
kao i svaka objektno-orijentirana baza podataka i on je namijenjen za posebne aplikativne
domene iako se može koristi u svakoj domeni. ZODB je prvenstveno namijenjen aplikacijama
u kojima nema velikog broja upisivanja u bazu, ali ima dohvaćanja podataka te pretraga po
bazi. Najbolji primjeri korištenja ZODB-a su wiki sustav izgraĎen uz pomoć Pyramid python-
a, te zodb-browser, koji su sve aplikacije u kojima nema velikog broj upisa. Stoga možemo
reći kako je ZODB, iako jako jednostavan i zbog toga privlačan, namijenjen za veće
aplikacije, distribuirane aplikacije dok je za jednostavne aplikacije kao što je gore opisana
aplikacija jednostavno neiskorišten do svog punog potencijala. Najbolji primjeri prave
aplikativne primjene ZODB-a se nalaze u plone CMS-u te Zope Web Frameworku gdje
ZODB dolazi do svog punog potencijala.
20
6. Literatura
[1] Suzanne W D,Susan D U (2011) Fundamentals of Object Databases:Object-Oriented
and Object-Relational Design San Rafael: Morgan & Claypool.
[2] Ramakrishnan R, Gehrke J (2001) Database Management Systems (3. izd.). Boston:
McGraw-Hill.
[3] Weitershausen P (2009) Web Component Development with Zope (3. izd.). Boston:
Springer.
[4] Grinberg M (2013) Flask Web Development (2. izd.). Sebastopol: O'Reilly.
[5] Chaudhary B (2015)Tkinter GUI Application Development Blueprints(1. izd.).
Birmingham: Packt Publishing.
[6] Lott F S (2015) Mastering Object-oriented Python (2. izd.). Birmingham: Packt
Publishing.
[7] ZODB - Native object database for python - Documentation. (2016). Preuzeto 2. rujna
2016. s http://www.zodb.org/en/latest/
[8] Schatten Markus, Objektno-orijentirane baze podataka prezentacija (2015)
21
7. Prilog - programski kod
import transaction
from ZEO import ClientStorage
from ZODB import DB
import tkinter
from flask_bcrypt import check_password_hash
ADDRESS = (('188.166.166.155', 8090))
ZODB = {}
TRENUTNI = None
def create_connection():
global root
ZODB['storage'] = ClientStorage.ClientStorage(ADDRESS)
ZODB['db'] = DB(ZODB['storage'])
ZODB['connection'] = ZODB['db'].open()
root = ZODB['connection'].root()
def close_connection():
ZODB['connection'].close()
ZODB['db'].close()
ZODB['storage'].close()
def commit(thing):
try:
root['korisnici'].extend(thing)
transaction.commit()
except Exception as ex:
print ex.message
def get_korisnic():
print root['korisnici']
def callback(user,passw):
global tr
try:
a = [x for x in root['korisnici']
if x.USERNAME == user][0]
if check_password_hash(a.PASSWORD, passw):
tr = a
create_gui2()
else:
return None
except IndexError:
return None
22
###########################################################################
############################################
#### LOGIN
###########################################################################
############################################
def create_gui():
window = tkinter.Tk()
window.title("Python Games Login")
window.geometry("270x210")
window.configure(bg="#39d972")
title1 = tkinter.Label(window, text="--Log in to play the Python Games--\n",
bg="#39d972")
usertitle = tkinter.Label(window, text="---Username---", bg="#39d972")
passtitle = tkinter.Label(window, text="---Password---", bg="#39d972") message = tkinter.Label(window, bg="#39d972")
user = tkinter.Entry(window)
passw = tkinter.Entry(window, show='*')
go = tkinter.Button(window, text="Log in!",command=lambda:
callback(user.get(),passw.get()), bg="#93ff00")
title1.pack()
usertitle.pack()
user.pack()
passtitle.pack()
passw.pack()
go.pack()
message.pack()
window.mainloop()
###########################################################################
############################################
#### LOGIN
###########################################################################
############################################
###########################################################################
############################################
#### PBOOK
###########################################################################
############################################
def whichSelected () :
print "At %s of %d" % (select.curselection(), len(phonelist))
return int(select.curselection()[0])
23
def addEntry () :
s = tr.add_contact(name.get(),phone.get())
root['kontakti'].extend([s])
transaction.commit()
setSelect()
def updateEntry() :
rez = [x for x in root['kontakti'] if x.NAME == select.get(select.curselection()[0])][0]
ind = root['kontakti'].index(rez)
rez.NAME = name.get()
rez.CONTACT_NUMBER = phone.get()
root['kontakti'][ind] = rez
transaction.commit()
setSelect()
def deleteEntry() :
rm = [x for x in root['kontakti'] if x.NAME == select.get(select.curselection()) ][0]
print rm.NAME
root['kontakti'].remove(rm)
transaction.commit()
setSelect()
def loadEntry () :
name.delete(0, tkinter.END)
phone.delete(0,tkinter.END)
rez = [x for x in root['kontakti'] if x.NAME == select.get(select.curselection()[0])][0]
name.insert(tkinter.END,rez.NAME)
phone.insert(tkinter.END, rez.CONTACT_NUMBER)
def makeWindow () :
global name,nameVar,phoneVar, select, phone
win = tkinter.Tk()
nameVar = tkinter.StringVar()
frame1 = tkinter.Frame(win)
frame1.pack()
phoneVar = tkinter.StringVar()
tkinter.Label(frame1, text="Name").grid(row=0, column=0, sticky=tkinter.W)
name = tkinter.Entry(frame1, textvariable=nameVar)
name.grid(row=0, column=1, sticky=tkinter.W)
tkinter.Label(frame1, text="Phone").grid(row=1, column=0, sticky=tkinter.W)
phone= tkinter.Entry(frame1, textvariable=phoneVar)
phone.grid(row=1, column=1, sticky=tkinter.W)
frame2 = tkinter.Frame(win) # Row of buttons
frame2.pack()
24
b1 = tkinter.Button(frame2, text=" Add ", command=addEntry)
b2 = tkinter.Button(frame2, text="Update", command=updateEntry)
b3 = tkinter.Button(frame2, text="Delete", command=deleteEntry)
b4 = tkinter.Button(frame2, text=" Load ", command=loadEntry)
b1.pack(side=tkinter.LEFT); b2.pack(side=tkinter.LEFT)
b3.pack(side=tkinter.LEFT); b4.pack(side=tkinter.LEFT)
frame3 = tkinter.Frame(win) # select of names
frame3.pack()
scroll = tkinter.Scrollbar(frame3, orient=tkinter.VERTICAL)
select = tkinter.Listbox(frame3, yscrollcommand=scroll.set, height=6)
scroll.config (command=select.yview)
scroll.pack(side=tkinter.RIGHT, fill=tkinter.Y)
select.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=1)
return win
def setSelect () :
select.delete(0,tkinter.END)
rezultat = [x for x in root['kontakti'] if x.AUTOR.USERNAME == tr.USERNAME]
print rezultat
for x in rezultat:
select.insert(tkinter.END, x.NAME)
def create_gui2():
win = makeWindow()
setSelect ()
win.mainloop()
if __name__ == '__main__':
create_connection()
create_gui()
from persistent import Persistent
from flask_bcrypt import generate_password_hash
from persistent.list import PersistentList
class user(Persistent):
USERNAME = None
PASSWORD = None
CONTACTS = []
def __init__(self,username,password):
self.USERNAME = username
self.PASSWORD = generate_password_hash(password=password)
self.CONTACTS = PersistentList()
25
def add_contact(self,name,contact_number):
contact = PersonalContact(name=name,contact_number=contact_number,autor=self)
self.CONTACTS.append(contact)
return contact
class Contact(Persistent):
NAME = None
CONTACT_NUMBER = None
AUTOR = None
def __init__(self,name, contact_number,autor):
self.NAME = name
self.CONTACT_NUMBER = contact_number
self.AUTOR = autor
class PersonalContact(Contact):
def __init__(self,name,contact_number,autor):
Contact.__init__(self,name,contact_number,autor)
class Singleton:
"""
A non-thread-safe helper class to ease implementing singletons.
This should be used as a decorator -- not a metaclass -- to the
class that should be a singleton.
The decorated class can define one `__init__` function that
takes only the `self` argument. Other than that, there are
no restrictions that apply to the decorated class.
To get the singleton instance, use the `Instance` method. Trying
to use `__call__` will result in a `TypeError` being raised.
Limitations: The decorated class cannot be inherited from.
"""
def __init__(self, decorated):
self._decorated = decorated
def Instance(self):
"""
Returns the singleton instance. Upon its first call, it creates a
new instance of the decorated class and calls its `__init__` method.
On all subsequent calls, the already created instance is returned.
"""
try:
return self._instance
26
except AttributeError:
self._instance = self._decorated()
return self._instance
def __call__(self):
raise TypeError('Singletons must be accessed through `Instance()`.')
def __instancecheck__(self, inst):
return isinstance(inst, self._decorated)