java persistence api (jpa) ter hibernate predmetno … · 2017-11-27 · hibernate 3.0 is presented...
TRANSCRIPT
Bojan Brumen
JAVA PERSISTENCE API (JPA) TER
HIBERNATE PREDMETNO-RELACIJSKI
PRESLIKOVALNI MEHANIZEM (ORM)
diplomsko delo
Maribor, marec 2011
I
Diplomsko delo visokošolskega strokovnega študijskega programa
JAVA PERSISTENCE API (JPA) TER
HIBERNATE PREDMETNO-RELACIJSKI
PRESLIKOVALNI MEHANIZEM (ORM)
Študent: Bojan Brumen
Študijski program: Računalništvo in informatika VS
Smer: Programska oprema
Mentor: prof. dr. Matjaž. B. Jurič
Lektorica: Anita Leskovar, prof. slo.
Maribor, marec 2011
II
III
ZAHVALA
Mentorju za pomoč pri izdelavi diplomske naloge ter
vsem sošolcem in prijateljem, ki so naredili ta študijska
leta nepozabna.
Posebna zahvala gre staršem za vztrajnost in
potrpežljivost med študijem.
IV
KAZALO
KAZALO ................................................................................................................................... IV
KAZALO SLIK.......................................................................................................................... VIII
KAZALO TABEL ........................................................................................................................ IX
JAVA PERSISTENCE API (JPA) TER HIBERNATE PREDMETNO-RELACIJSKI PRESLIKOVALNI
MEHANIZEM (ORM) ................................................................................................................. X
JAVA PERSISTENCE API (JPA) AND HIBERNATE OBJECT- RELATIONAL MECHANISM(ORM) ........ XI
UPORABLJENE KRATICE ...........................................................................................................XII
1 UVOD ..............................................................................................................................13
2 OBJEKTNO-RELACIJSKO MAPIRANJE V SPLOŠNEM ...........................................................16
2.1 JDBC v primerjavi z objektno-relacijskim mapirnim mehanizmom ............................17
2.1.1 Primerjava JDBC in ORM ..................................................................................19
3 JPA – JAVA PERSISTENCE API ...........................................................................................20
3.1 Značilnosti JPA .........................................................................................................23
3.2 JAVA ter zagotovitev trajnosti ..................................................................................24
3.2.1 JPA kratek pregled specifikacij ..........................................................................25
3.2.2 Zgodovina ........................................................................................................26
3.3 Razumevanje entitet ................................................................................................27
3.3.1 Objektno-relacijsko mapiranje znotraj JPA .......................................................27
3.3.2 Povpraševanje po entitetah .............................................................................29
3.3.3 Življenjski cikel entitet ......................................................................................31
3.4 Objektno-relacijsko mapiranje .................................................................................32
3.4.1 Mapiranje entitet .............................................................................................32
3.4.2 Koncept nastavitve izjem (configuration by exception) .....................................34
3.4.3 Elementarno mapiranje....................................................................................35
3.5 Tabele .....................................................................................................................35
3.5.1 @Table ............................................................................................................35
3.5.2 @SecondaryTable ............................................................................................36
3.6 Primarni ključi ..........................................................................................................38
3.6.1 @Id ter @GeneratedValue ...............................................................................38
3.7 Sestavljeni primarni ključi ........................................................................................39
3.7.1 @EmbeededId .................................................................................................39
V
3.7.2 @IdClass ..........................................................................................................40
3.8 Atributi ....................................................................................................................41
3.8.1 @Basic .............................................................................................................41
3.8.2 @Column .........................................................................................................42
3.8.3 @Temporal ......................................................................................................43
3.8.4 @Transient ......................................................................................................44
3.8.5 @Enumerated..................................................................................................45
3.9 Tipi dostopanj ..........................................................................................................46
3.9.1 Kolekcije osnovnih tipov...................................................................................47
3.10 Relacije entitete.......................................................................................................48
3.10.1 Enosmerna in dvosmerna usmerjenost ............................................................49
3.10.2 Nalaganje relacij...............................................................................................50
3.10.3 Razvrščanje relacij ............................................................................................52
3.10.4 @OrderBy ........................................................................................................52
3.10.5 @OrderColumn ................................................................................................53
3.11 Mapiranje dedovanja ...............................................................................................54
3.11.1 Strategije dedovanja ........................................................................................54
3.11.2 Prepisovanje atributov .....................................................................................59
3.12 Upravljanje s trajnimi objekti ...................................................................................60
3.12.1 Dostopanje do entitet ......................................................................................61
3.12.2 Upravljalec entitet ...........................................................................................62
3.12.3 Trajna vsebina ..................................................................................................63
3.12.4 API predpomnilnik ...........................................................................................65
3.13 JPQL ........................................................................................................................66
3.13.1 SELECT .............................................................................................................66
3.13.2 FROM...............................................................................................................67
3.13.3 WHERE.............................................................................................................68
3.13.4 Vgnezdeni stavki ..............................................................................................68
3.13.5 ORDER BY ........................................................................................................69
3.13.6 GROUP BY ter HAVING .....................................................................................69
3.13.7 Bulk DELETE .....................................................................................................70
3.13.8 Bulk UPDATE ....................................................................................................70
3.13.9 Povpraševanja..................................................................................................71
VI
3.14 Vzporednost ............................................................................................................73
3.14.1 Verzioniranje....................................................................................................75
3.14.2 Optimistično zaklepanje ...................................................................................75
3.14.3 Pesimistično zaklepanje ...................................................................................76
4 HIBERNATE 3.0 ................................................................................................................77
4.1 Hibernate arhitektura ..............................................................................................79
4.1.1 Razumevanje arhitekture .................................................................................81
4.2 Hibernate povpraševalni jezik (HQL) ........................................................................84
4.3 Hibernate Criteria Query..........................................................................................86
4.3.1 Vmesnik Criteria ter razred Restrictions ...........................................................87
4.4 Hibernate Native SQL...............................................................................................90
4.5 Asociacije ter združevanja ........................................................................................90
4.5.1 HQL inner join ..................................................................................................90
4.5.2 Agregatne funkcije pri uporabi join ..................................................................92
4.6 Vgnezdeni povpraševalni stavki ...............................................................................93
4.7 Anotacije .................................................................................................................94
4.7.1 Primer uporabe anotacij...................................................................................95
4.8 Uporaba Hibernatea v aplikaciji Kino........................................................................97
4.9 Zbiranje zahtev ........................................................................................................97
4.10 Analiza .....................................................................................................................98
4.10.1 Osnovne informacije o Kinu .............................................................................98
4.10.2 Vnos dvorane ...................................................................................................98
4.10.3 Brisanje dvorane ..............................................................................................98
4.10.4 Vnos filma ........................................................................................................98
4.10.5 Brisanje filma ...................................................................................................99
4.10.6 Vnos predstave ................................................................................................99
4.10.7 Brisanje predstave ...........................................................................................99
4.10.8 Prodaja vstopnic ..............................................................................................99
4.11 Načrtovanje ........................................................................................................... 100
4.11.1 Razredni diagram ........................................................................................... 101
4.11.2 Entitetno-relacijski diagram ........................................................................... 101
4.12 Implementacija ...................................................................................................... 102
4.12.1 Izseki kode iz POJO objekta ............................................................................ 104
VII
4.12.2 Izseki kode iz poslovnega nivoja ..................................................................... 108
4.13 Grafični vmesnik .................................................................................................... 110
4.13.1 Kino glavno okno............................................................................................ 111
4.13.2 Vnos filma okno ............................................................................................. 112
4.13.3 Vnos dvorane okno ........................................................................................ 113
4.13.4 Vnos predstave okno...................................................................................... 114
4.13.5 Prodaja kart okno........................................................................................... 115
4.14 Testiranje .............................................................................................................. 116
4.14.1 Testiranje enot ............................................................................................... 116
4.14.2 Testi sprejemljivosti ....................................................................................... 118
4.15 Uporabljena orodja ................................................................................................ 119
4.16 Predlogi za izboljšave/dopolnitve ........................................................................... 120
5 SKLEP ............................................................................................................................ 121
6 VIRI IN LITERATURA ....................................................................................................... 123
7 PRILOGE ........................................................................................................................ 124
VIII
KAZALO SLIK
Slika 1: Prikaz uporabe JDBC gonilnika za pridobivanje podatkov iz podatkovne baze ..............18
Slika 2: JPA arhitektura s primarnimi komponentami...............................................................22
Slika 3: Mapiranje atributev iz entitete v stolpce iz tabele ......................................................29
Slika 4: Interakcija upravljalca entitet med entiteto ter podatkovno bazo ................................30
Slika 5: Življenjski cikel entitete ...............................................................................................32
Slika 6: Entiteta Naslov je mapirana v tri tabele .......................................................................37
Slika 7: Relacija med KNJIGA in ZAZNAMEK tabelo...................................................................48
Slika 8: Enosmerna asociacija med entitetama Customer ter Address .....................................50
Slika 9: Dvosmerna asociacija med entitetama Stranka ter Naslov ...........................................50
Slika 10: Štiri entitete s takojšnjim nalaganjem ........................................................................51
Slika 11: Štiri entitete z odloženim nalaganjem........................................................................51
Slika 12: Privzete nalagalne strategije......................................................................................51
Slika 13: Struktura tabele Predmet ..........................................................................................55
Slika 14 : Izsek zapisov iz podatkovne tabele PREDMET ...........................................................56
Slika 15: Zapisi v tabeli PREDMET po spremenjenih diskriminator stolpcih ter imenih entitet ..57
Slika 16: Mapiranje dedovanja z uporabo strategije pridruženja ..............................................58
Slika 17: Tabeli KNJIGA ter CD s podvojenimi stolpci iz tabele PREDMET ..................................59
Slika 18: Transakciji tx1 ter tx2 vzporedno posodabljata ceno knjige. ......................................74
Slika 19: OptimisticLockException izjema nad transakcijo tx2 ..................................................76
Slika 20: Hibernate arhitektura [6] ..........................................................................................79
Slika 21: Pregled Hibernate API, razdeljene po slojih [4] ..........................................................82
Slika 22: Usmerjena asociacija ............................................................................................... 100
Slika 23: Kompozicija ............................................................................................................. 100
Slika 24: Razredni diagram aplikacije Kino ............................................................................. 101
Slika 25: Entitetno-relacijski diagram aplikacije Kino.............................................................. 102
IX
Slika 26: Troslojna arhitektura .............................................................................................. 102
Slika 27: Organizacija paketov ............................................................................................... 103
Slika 28: Uporabniški vmesnik kino glavno okno .................................................................... 111
Slika 29: Uporabniški vmesnik za vnos ter brisanje filma........................................................ 112
Slika 30: Uporabniški vmesnik za vnos ter brisanje dvorane................................................... 113
Slika 31: Uporabniški vmesnik za vnos ter brisanje predstave ................................................ 114
Slika 32: Uporabniški vmesnik prodaja kart ........................................................................... 115
Slika 33: Rezultat testa za razred HelperUtils ......................................................................... 117
Slika 34: Rezultat testa za razred DvoranaImpl ...................................................................... 119
KAZALO TABEL
Tabela 1: JDBC v primerjavi z ORM ..........................................................................................19
Tabela 2: Možne kombinacije med kardinalnostjo in usmerjenostjo ........................................49
Tabela 3: Vmesnik Criteria ......................................................................................................87
Tabela 4: Razred Restrictions ..................................................................................................88
Tabela 5: Dobljen rezultat ob uporabi Restrictions.like ............................................................89
Tabela 6: Dobljen rezultat ob uporabi inner join določila .........................................................92
Tabela 7: Dobljen rezultat agregatne funkcije ob uporabi join .................................................93
Tabela 8: Dobljen rezultat vgnezdenega povpraševanja ..........................................................93
X
JAVA PERSISTENCE API (JPA) TER HIBERNATE
PREDMETNO-RELACIJSKI PRESLIKOVALNI MEHANIZEM
(ORM)
Ključne besede: Java persistance API, Hibernate, objektno-relacijsko mapiranje,
trajnost podatkov
UDK : 004.652.4.052(043.2)
POVZETEK :
Diplomsko delo opisuje razvoj JPA-ja skozi zgodovino, s pomočjo primerov nato
teoretično prikazuje vse osnovne prijeme, kot so razumevanje entitet ter objektno-
relacijsko mapiranje entitet znotraj JPA-ja. Podrobneje so predstavljene najpogostejše
anotacije znotraj javax.persistence paketa, značilne za verzijo JPA 2.0. Diplomsko delo
se dotakne relacij med entitetami, strategij dedovanja in se natančneje osredotoča na
delo upravlajlca entitet ter pomen trajne vsebine. V nadaljevanju je poglobljeno
predstavljen povpraševalni jezik JPQL, ki omogoča razširitev ter večjo fleksibilnost pri
podatkovnem povpraševanju v primerjavi s samim upravljalcem entitet.
V drugem sklopu diplomskega dela je predstavljen Hibernate 3.0 kot eden izmed
ponudnikov zagotavljanja trajnosti podatkov, katerega naloga je preslikovanje
javanskih objektov v tabele relacijske podatkovne baze. Opisani in praktično prikazani
so HQL (Hibernateov povpraševalni jezik), vmesnik Criteria in razred Restriction, ki
vsak s svojimi značilnostmi znatno olajšajo delo samemu razvijalcu.
V zadnjem aplikativnem sklopu diplomskega dela je s pomočjo Hibernatea
implementirana aplikacija Kino. Prva faza je vključevala postavitev zahtev in analizo
morebitnih rešitev. V fazi načrtovanja je bila aplikacija Kino modelirana s pomočjo
UML (Unified Modeling Language) jezika ter v nadaljevanju tudi implementirana. V
zadnji fazi so bili uspešno izvedeni tudi testi posameznih enot ter testi sprejemljivosti.
Diplomsko delo na nazoren način prikazuje uporabnost in enostavnost Hibernatea in
ponuja bazično osnovo za nadaljnje poglobljeno raziskovanje tematike.
XI
JAVA PERSISTENCE API (JPA) AND HIBERNATE OBJECT-
RELATIONAL MECHANISM(ORM)
Key words: Java persistance API, Hibernate, object-relational mapping, persistent
data
UDK: 004.652.4.052(043.2)
Abstract:
This diploma work is about JPA's development through it's history. All of the basic
approaches, such as entity understanding and object-relational entity mapping within
JPA, are shown and explained by using different examples. Furthermore, the most
common annotation inside javax.persistence package (specific for JPA 2.0 version) are
presented specifically. The diploma work touches some relations between entities,
strategies of inharitence and it specifies on entity manager's work and the meaning of
persistent context. Some more details about JPQL Query Language are discussed in
diploma work's continuation, which allows larger expansion and adjustability by data
demanding compared to the actual entity manager.
Hibernate 3.0 is presented in the second part of diploma works as one of the vendors
of ensuring data persistence, which maps Java objects into relational database tables.
Furthermore, HQL (Hibernate Query Language), Criteria interface and Restriction
class are described and practically shown. Each of them, with it's own characteristics,
facilitates the work of a developer.
In the practical part of diploma work the Kino application is implemented by using
Hibernate. The first phase includes the installation requirements and analyzing
potential solutions. The planning phase is made of modeling Kino application using
UML (Unified Modeling Language) language and implementing it. In the final phase
tests of individual units and acceptance tests are carried out.
The diploma work shows in a clear way the usefulness and simplicity of Hibernate
and offers a basic foundations for further research topics.
XII
UPORABLJENE KRATICE
API Application programming interface
DBCP Database connection pooling services
DBMS Database management system
DDL Data definition language
EIS Enterprise information systems
EJB Enterprise JavaBeans
EJB QL Enterprise JavaBeans query language
HQL Hibernate query language
j2EE Java 2 platform, enterprise edition
JDBC Java database conectivity
JDK Java SE development kit
JNDI Java naming and directory interface
JSE The Java syntactic extender
JPA Java persistence API
LGPL Lesser general public license
ORM Object related mapping
POJO Plain old Java object
SQL Structured query language
SQL DBMS Structured query language database management system
XML Extensible markup language
13
1 UVOD
Pri razvoju informacijskih sistemov danes uporabljamo objektno usmerjene jezike. Med
najbolj priljubljene sodita Java in C#. Za shranjevanje podatkov uporabljamo skoraj izključno
relacijske baze podatkov. Pri uporabi objektno usmerjenih jezikov in relacijskih baz podatkov
prihaja do konceptualnih razlik pri pretvorbi podatkov iz ene oblike v drugo.
Pri objektno usmerjenih programskih jezikih so podatki predstavljeni v obliki razredov.
Razred poleg podatkov vsebuje tudi metode, ki predstavljajo moţne operacije. Razredi lahko
vsebujejo druge razrede ali so povezani z njimi. Predstavitev problema na tak način je zelo
naravna, saj razredi oz. njihove instance, ki jim pravimo objekti, zelo dobro predstavljajo
resnični svet. Izkoriščamo lahko prednosti, kot so dedovanje, mnogoličnost, vmesniki itd.
V nasprotju z objektnim modelom imamo v relacijski bazi podatkov podatke v tabelah. Do
podatkov pridemo s pomočjo poizvedbenih stavkov v jeziku SQL. Za vsak objekt, ki ga
uporabljamo, moramo načeloma napisati štiri poizvedbe, in sicer za branje, spreminjanje,
vstavljanje in brisanje. Pisanje takih enostavnih poizvedb, pretvorba podatkov v objekte in
shranjevanje nazaj je sorazmerno rutinsko delo. Kljub temu zavzema razmeroma velik del časa
razvoja informacijskega sistema.
Kljub razlikam med modeloma je moţna avtomatska pretvorba iz ene oblike v drugo. Tako
pretvorbo imenujemo objektno-relacijska preslikava. Le-ta avtomatsko generira vse potrebne
poizvedbe in objekte v času izvajanja. Razvijalcu informacijskih sistemov se načeloma ni več
potrebno ukvarjati s tem, kako se podatki berejo in shranjujejo.
14
Objektno-relacijska preslikava je postala priljubljena, ko je Sun skupaj z J2EE predstavil
tehnologijo Enterprise JavaBeans (EJB). Tehnologija je bila revolucionarna novost, zato so ji
napovedovali svetlo prihodnost. Ţal je bila za praktično uporabo veliko preveč zapletena in je
razvoj, namesto da bi ga pospeševala, zavirala. Zaradi kompleksnosti tehnologije entitetnih zrn
in njenih slabosti je nastalo veliko novih tehnologij, ki pa so obdrţale osnovno zamisel –
pretvorbo relacijske podatkovne sheme v objektno. Tako je Sun predstavil JDO, Oracle TopLink
itd. Na voljo pa je tudi veliko število odprtokodnih rešitev. Med njimi ima glavno vlogo
Hibernate. Tega vsak mesec posname z interneta kar 13.000 uporabnikov [1]. Tako je postal
najresnejša alternativa dostopa do podatkov v okolju Jave.
Hibernate je objektno-relacijski preslikovalni mehanizem za Javo, kar pomeni, da podatke,
zapisane v relacijski bazi podatkov, pretvori v javanske objekte. To so popolnoma običajni
javanski predmeti (POJO – Plain Old Java Objects oz. Plain Ordinary Java Objects) in zato ne
vsebujejo mehanizma, ki bi skrbel za shranjevanje. Hibernate je odprtokodni projekt, izdan pod
licenco LGPL (Lesser General Public License), ki omogoča brezplačno uporabo tudi v
komercialne namene.
Cilj diplomske naloge je predstavitev Hibernata in JPA kot dveh najresnejših kandidatov pri
uporabi v predmetno-relacijske namene.
V drugem poglavju podajam osnovno terminologijo, razvoj in značilnosti objektno-
relacijskega mehanizma ter podajam primerjavo z JDBC.
Tretje poglavje nato predstavlja koncept JPA, ki je samo specifikacija, napisana s strani Suna,
in je izdana pod okriljem JEE 6 specifikacij. JPA standardizira objektno-relacijsko – mapirno
(ORM) tehnologijo za Java razvijalce. JPA ni produkt in ga ne moremo uporabiti kot mehanizem
za obstojnost javanskih objektov. Zato potrebujemo ORM implementacijo. ORM ogrodja, v
katerih je dejansko implementirana JPA specifikacija, so npr. Hibernate, TopLink, OpenJPA itd.
15
Četrto poglavje predstavlja koncept Hibernatea. Hibernate je objektno-relacijski preslikovalni
mehanizem, ki preslika javanske razrede v tabele relacijsko-podatkovne tabele. Prav tako ponuja
podporo za povpraševalne stavke ter orodja za povpraševanja po podatkih, ki znatno zmanjšajo
čas razvoja. Hibernate ni najboljša rešitev za podatkovne usmerjene aplikacije, ki uporabljajo
izključno bazne procedure (store procedure). Najbolj uporaben je v objektno orientiranih
domenskih načinih ter za poslovno logiko v srednjem sloju. V tem poglavju sem tudi kot
praktični primer prikazal cel cikel razvoja aplikacije, začenši z zbiranjem zahtev, analizo,
načrtovanjem, implementacijo ter testiranjem aplikacije Kino, ki sem si jo kot primer uporabe
Hibernatea izbral sam. Aplikacija je primer uporabe Hibernatea ter skozi izseke kode in opisi
nadzorno prikazuje lasten izdelek s podprtimi osnovnimi funkcionalnostmi.
Sklep je zaključno poglavje diplomskega dela, v katerem je podan povzetek opravljenega
dela in analiza rezultatov, opisane so moţne smeri nadaljnjega dela.
16
2 OBJEKTNO-RELACIJSKO MAPIRANJE V SPLOŠNEM
Termin ORM v računalniški znanosti pomeni tehnologijo, ki omogoča pretvorbo podatkov
med podatkovnimi tipi v relacijske podatkovne baze in objektno orientirane programske jezike.
Slednje omogoča neke vrste navidezno objektno podatkovno bazo, do katere lahko dostopamo
znotraj programskega jezika. Na trţišču so na voljo tako zastonj kot tudi komercialni paketi, ki
omogočajo objektno-relacijsko mapiranje.
Opravila za opravljanje s podatkovno bazo v objektno orientiranem okolju so ponavadi
implementirana kot manipuliranje objektov, ki so skoraj vedno neskalarne vrednosti [1].
Primer:
Imamo imenik, vsak zapis v njem lahko ima osebo, ki ima 0 ali več telefonskih številk ter 0
ali več naslovov prebivališč. V objektno orientiranem programskem jeziku lahko to zmodeliramo
kot nek objekt Oseba, ki vsebuje atribute:
ime osebe,
seznam telefonskih številk,
seznam naslovov.
Prav tako seznam telefonskih številk vsebuje objekte tipa telefonska številka itd. Objekt
Imenik je tretiran kot en sam in je lahko instanciran kot enojna spremenljivka. Nad razredom so
lahko implementirane različne metode, kot npr. metoda, ki vrača preferirano telefonsko številko,
naslov itd.
Mnogi produkti sistemov za upravljanje s podatkovnimi bazami kot npr SQL DBMS lahko
shranjujejo in manipulirajo samo z nekimi skalarnimi vrednostmi, kot so Integer, String, znotraj
normaliziranih tabel. Razvijalec mora zato ob shranjevanju objektov le-te organizirati v
preprostejše vrednosti, prav tako jih mora nato pretvarjati nazaj, kadar jih ţeli dobiti iz
podatkovne baze. ORM vse to naredi namesto nas.
Jedro tega problema je pretvarjanje objektov v forme, ki jih lahko shranimo v bazo podatkov
ter kasnejše laţje povpraševanje in ohranjanje objektov, njihovih atributov ter relacij med njimi.
Pri uporabi ORM-a za takšne objekte lahko rečemo, da ti postanejo stalni (persistent) [11].
17
2.1 JDBC v primerjavi z objektno-relacijskim mapirnim mehanizmom
Obe tehnologiji sta namenjeni shranjevanju podatkov za kasnejšo rabo. JDBC je predhodnik
ORM-a. Kratica JDBC pomeni Java Database Conectivity in je del Java API-ja za dostop do
relacijske podatkovne zbirke iz Java aplikacij. JDBC omogoča razvijalcu hiter razvoj majhnih
Java aplikacij, ki komunicirajo s podatkovno zbirko. Za razvoj aplikacije mora razvijalec napisati
del kode za povezavo s podatkovno bazo ter tudi napisati kodo za izvedbo povpraševalnega
stavka. Ko je povpraševalni stavek izveden in rezultati pridobljeni iz podatkovne zbirke, lahko
razvijalec prebere podatke iz ResultSet objekta kot prikazuje Slika 1 [4].
Primer:
import java.sql.*;
public class AllTableName{
public static void main(String[] args) {
System.out.println( "Izpis vseh tabel v podatkovni bazi" );
Connection con = null ;
String url = "jdbc:mysql://localhost:3306/" ;
String db = "jdbctutorial" ;
String driver = "com.mysql.jdbc.Driver" ;
String user = "root" ;
String pass = "root" ;
try {
Class.forName(driver);
con = DriverManager.getConnection(url+db, user, pass);
try {
DatabaseMetaData dbm = con.getMetaData();
String[] types = { "TABLE" };
ResultSet rs = dbm.getTables(null,null, "%" ,types);
18
System.out.println( "Ime tabele:" );
while (rs.next()){
String table = rs.getString( "TABLE_NAME" );
System.out.println(table);
con.close();
}
}
catch (SQLException s){
System.out.println( "V podatkovni bazi ne obstaja nobena tabela!" );
}
}
catch (Exception e){
e.printStackTrace();
}
}
Slika 1: Prikaz uporabe JDBC gonilnika za pridobivanje podatkov iz podatkovne baze
19
2.1.1 Primerjava JDBC in ORM
Tabela 1 prikazuje primerjavo, kdaj je kateri izmed omenjenih pristopov boljši in kdaj bi za
nek pristop bilo bolje izbrati alternativo.
Tabela 1: JDBC v primerjavi z ORM
JDBC ORM
Prednosti:
Čist in preprost za majhne
programčke.
JDBC ponuja zelo učinkovite
zmogljivosti za veliko količino
podatkov.
Majhne JDBC programe lahko
razvijemo zelo hitro.
Slabosti:
JDBC ni preprost, če ga uporabimo
pri velikih projektih. Prinaša veliko
programskega overheada.
Razvijalec mora napisati kodo za
transkacijski del v samo aplikacijo.
Zapiranje povezav je nujno [8].
Prednosti:
ORM pogosto zmanjšuje število kode,
ki bi jo brez njega morali napisati.
Ni potrebe po pisanju povpraševalnih
stavkov pri pridobivanju ter
shranjevanju podatkov.
Preprosta konfiguracija.
Standardiziran API za obstojnost
business objektov.
Hiter razvoj aplikacij.
Odlična “caching” podpora za boljše
performance aplikacije.
Preprost za učenje.
Slabosti:
Nekateri ORM mehanizmi ne delujejo
dobro pri brisanju velike količine
podatkov.
Slabša učinkovitost kot pri baznih
procedurah.
Pri slabo načrtovanih podatkovnih
bazah, mora biti uporabnik ORM-a
bolj pazljiv [8].
20
3 JPA – JAVA PERSISTENCE API
Java persistence API ali na kratko JPA je pravzaprav javanska specifikacija, ki omogoča
trajnost javanskih objektov v relacijskih podatkovnih bazah ob uporabi popularne ORM
tehnologije. JPA API ponuja dovolj orodij, ki omogočajo razvijalcem, da hitro kreirajo javanske
aplikacije v souporabi s podatkovnimi bazami. JPA API lahko uporabimo, da zagotovimo
trajnost POJO objektom v relacijskih podatkovnih bazah. Prav tako je ob uporabi JPA tudi
pretvarjanje nazaj ter povpraševanje v relacijskih bazah po takih podatkih sila preprosto [5].
JPA kot taka je samo specifikacija, napisana iz strani Suna, in je izdana pod okriljem JEE5
specifikacij. JPA standardizira objektno-relacijsko mapirno tehnologijo za Java razvijalce. JPA
ni produkt in ga ne moremo uporabiti kot mehanizem za obstojnost javanskih objektov. Zato
potrebujemo ORM implementacijo. ORM ogrodja, v katerih je dejansko implementirana JPA
specifikacija, so npr. Hibernate, TopLink, OpenJPA itd. [5].
Dandanes veliko prodajalcev ponuja svoje implementacije za trajnost podatkov. V skladu s
tem lahko razvijalci izberejo najboljšo ORM implementacijo, ki sovpada z zahtevami za svoje
aplikacije. Na primer; produkcija se lahko prične pri zastonj verzijah ORM implementacije in ko
projekt zelo naraste, ga lahko preklopimo na komercialno izvedbo ORM-jev. Tako lahko
preklopimo med ponudniki persistence, brez da bi pri tem spremenili kodo [5].
Prednosti JPA:
Poenostavlja tehnologijo za zagotavljanje trajnosti podatkov.
ORM neodvisnost od ogrodja – lahko uporabimo katero koli ORM ogrodje.
Podatki so lahko shranjeni na način v skladu z ORM-om.
Podpora od vodilnih proizvajalcev [5].
21
Seznam nekaterih ORM mehanizmov, ki so lahko uporabljeni v skladu s JPA specifikacijo:
Hibernate,
TopLink,
iBatis,
Open JPA.
JPA oz. Java persitance API je lahko, temelječe na POJO objektih, javansko ogrodje, ki
zagotavlja obstojnost Java objektov v relacijskih podatkovnih bazah. JPA uporablja podatke o
podatkih (metadata) za mapiranje stalnih objektov v podatkovno tabelo. Podpira SQL
povpraševalni jezik, da si olajša proces povpraševanja v podatkovno bazo. JPA povpraševalni
jezik lahko tako izvaja statične kot tudi dinamične povpraševalne stavke [1].
22
JPA koncept vključuje 3 komponente: entiteto, upravljalca entitet ter tovarno upravljalca
entitet. Slika 2 prikazuje primarne komponente JPA arhitekture [9].
Slika 2: JPA arhitektura s primarnimi komponentami
23
3.1 Značilnosti JPA
Dandanes Java razvijalci uporabljajo Java Persistence API za razvoj kompleksnih aplikacij.
Predstavitev JPA znotraj JEE specifikacij je še en pomemben korak pri poenostavljanju
razvojnega procesa. JPA poenostavi entiteno obstojnost in dodaja nove moţnosti. Te nove
moţnosti še niso bile predstavljene v zgodnejših verzijah EJB-jev (EJB 2.1). Danes lahko
razvijalci direktno mapirajo stalen objekt (POJO) z relacijsko podatkovno bazo. Java Persistance
API pa je zaradi te tehnike postal tudi standard objektno-relacijskega mapiranja. JPA je lahko
uporabljen izven EJB vsebnika, kar v prejšnjih verzijah, kot so EJB 2.1 ni bilo mogoče. Prav
tako ga lahko uporabimo tudi v Swing aplikacijah [10].
Značilnosti JPA:
JPA podpira ponudnike »persistence« (obstojnosti) objektov, kot so TopLink in
Hibernate.
Popolno podprte JDK 5 anotacije.
Samo nekaj razredov je potrebnih, da lahko razvijamo aplikacijo, temelječo na JPA.
JPA lahko prav tako teče izven EJB vsebnika, tako jo lahko razvijalci uporabijo tudi
pri namiznih aplikacijah.
Podpora za anotacije ter privzete vrednosti anotacij prihranijo zelo veliko časa pri
samem razvoju.
Čistejši, laţji, standardiziran pristop objektno-relacijskega mapiranja.
JPA podpira dedovanje, polimorfizem in polimorfna povpraševanja.
JPA podpira tako imenovana statična in dinamična povpraševanja.
EJB QL, močan povpraševalen jezik, ponujen iz strani JPA.
JPA pomaga zgraditi JPA sloj, ki je nevtralen od proizvajalcev, v skladu s tem lahko
uporabimo produkt katerega koli ponudnika persistence (obstojnosti).
Na razpolago je mnogo IDE ogrodij za razvoj JPA aplikacij.
Nekatera IDE ogrodja lahko celo generirajo kodo (POJO objekte) iz relacijske
podatkovne baze.
24
JPA aplikacijo lahko skonfiguriramo tudi tako, da nam zgenerira shemo relacijske
podatkovne baze, bazirano na nekem modelu persistentnih objektov.
Prav tako je preprost preklop med enim in drugim komercialnim ponudnikom
persistence (obstojnosti) objektov [10].
3.2 JAVA ter zagotovitev trajnosti
Aplikacije danes so sestavljene iz poslovne logike, interakcij z ostalimi sistemi, grafičnih
vmesnikov ter podatkov. Večina teh podatkov, ki jih upravljajo današnje aplikacije, je shranjena
v podatkovnih bazah, da jih lahko kasneje pridobimo nazaj, jih analiziramo ter obdelujemo. S
tega stališča so podatkovne baze zelo pomemben segment. Tako lahko s pomočjo podatkovnih
baz neke ţe shranjene podatke ponovno obdelujemo tako, da le-te pridobivamo s pomočjo
proţilcev ali baznih procedur. Obstojnost podatkov v aplikaciji potrebujemo skoraj povsod in
večina teh uporablja relacijsko-podatkovne baze kot spodnji sloj aplikacije. Relacijsko-
podatkovna baza shranjuje podatke v tabele, sestavljene iz vrstic in stolpcev. Podatki v tej tabeli
so identificirani s pomočjo primarnih ključev, ki so posebni stolpci z unikatnimi zapisi, ter včasih
z indeksi. Relacije med tabelami uporabljajo tuje ključe in pridruţene tabele z integriranimi
omejitvami [1].
Ves ta zgoraj omenjen slovar je popolnoma neznan v objektno usmerjenem jeziku, kot je to
npr. Java. V objektnih jezikih manipuliramo z objekti, ki so instance razredov. Objekti lahko
dedujejo od drugih, imajo neke reference na ostale objekte ter včasih v načinu rekurzije kaţejo
tudi nase. Tukaj imamo konkretne razrede, abstraktne razrede, vmesnike, anotacije, metode,
atribute itd. Objekti ovijajo neko stanje objekta na zelo prijazen način, vendar je to stanje objekta
vidno le v času izvajanja javanskega navidnega stroja (JVM – Java virtual machine). Kadar se
javanski navidezni stroj ustavi ali smetar počisti njegov spomin, instancirani objekti izginejo,
skupaj z njihovim stanjem. Tukaj pridemo do faze, ko ugotovimo, da bi nekateri objekti morali
biti trajni. Ob tem imam v mislih, da bi bili zapisani trajno v nekem magnetnem mediju, flash
pomnilniku itd. Objektu, ki lahko ohrani svoje stanje za kasnejšo uporabo, pravimo, da je postal
trajen (persistant) [1].
Znotraj Jave obstaja več načinov, kako zagotoviti trajnost objektom. En način je skozi
serializacijski mehanizem, ki je proces pretvarjanja objekta v sekvenco bitov. Objekte lahko
25
serializiramo na magnetni disk ali preko nekega omreţja (vključojoč internet) v neodvisen
format, ki je lahko ponovno uporaben v različnih operacijskih sistemih. Java ponuja preprost,
trnasparenten, serializacijski, standardiziran mehanizem za serializacijo objektov, ki
implementira java.io.Serializable vmesnik. Vendar pa kljub temu, da je mehanizem zelo
preprost, je po drugi strani zelo skromen. Mehanizem ne podpira nobenih povpraševanj in pa tudi
nobene infrastrukture za podpiranje teţkega dostopa ali grozdnosti [1].
Še ena moţnost zagotavljanja trajnega stanja objekta je uporaba JDBC (Java database
connectivity), ki je standardna knjiţnica za dostop do relacijskih podatkovnih baz. Ima zmoţnost
povezati se na podatkovno bazo, izvesti SQL (Structured Query Language) povpraševalni stavek
ter pridobiti rezultate. Ta knjiţnica je bila del Java platforme ţe od verzije 1.1. Čeprav je
dandanes še zelo široko uporabljena, jo je malo zasenčila uporaba ORM (object relational-
mapping) orodij [1].
Princip ORM mehanizma vključuje delegiranje dostopa do relacijske podatkovne baze, do
zunanjih orodij ali ogrodij, katerih namen je vračanje objektno-orientiranih relacijskih podatkov
in obratno. Mapirni mehanizmi imajo dvosmerno skladnost med podatkovno bazo ter objekti. Na
trţišču je kar nekaj mehanizmov, ki podpirajo to filozofijo, vendar je izmed vseh JPA (Java
persistance API) tehnologija, ki ima s strani Jave prednost ter je vključena v specifikacijo Java
EE [1].
3.2.1 JPA kratek pregled specifikacij
JPA 1.0 je bil ustvrajen z verzijo Java EE 5, da bi rešil problematiko ohranitev trajnega stanja
podatkov. Dejansko je povezal objektno orientirane ter relacijske modele v en sklop. V Java EE6
JPA 2.0 nadaljuje začrtano pot predhodnika, prinaša pa še večjo robustnost, preprostost ter
dodaja nekatere nove funkcionalnosti. To knjiţnico lahko uporabimo za dostop ter manipulacijo
relacijskih podatkov znotraj EJB vsebnika (Enterprise Java Bean container) ter prav tako
spletnih komponent in Java SE (standard edition) aplikacij [1].
JPA je abstrakcija nad JDBC-jem, kar omogoča, da je neodvisen od SQL-a. Vsi razredi ter
anotacije te knjiţnice se nehajajo v javax.persistence paketu. Glavne komponente JPA-ja so:
26
ORM – je mehanizem za mapiranje objektov v podatke, shranjene v podatkovni bazi.
Knjiţnica EntityManager (EntityManager API), ki sluţi za izvajanje pripadajočih
operacij nad podatkovno bazo, kot so ustvarjanje (Create), branje (Read),
posodobljanje (Update), brisanje (Delete) operacij (ang. CRUD). Ta knjiţnica
omogoča, da se izognemo direktnemu stiku z JDBC knjiţnico.
JPQL (Java persistance query language) povpraševalni jezik, ki nam omogoča
povpraševanje podatkov v skladu z objektno-orientiranimi programskimi jeziki.
Transakcije in mehanizmi zaklepanja, kadar dostopamo do podatkov v podatkovni
bazi, hkrati ponujeno s strani JTA (Java Transaction API). Transakcije nad lokalnimi
viri (tiste, ki ne vključujejo JTA) so prav tako podprte od JPA.
Odzivne funkcije ter poslušalci za lovljenje poslovne logike v ţivljenjski krog trajnega
objekta [1].
3.2.2 Zgodovina
ORM rešitve so se pojavile ţe dolgo časa nazaj, nekatere celo pred Javo. Produkti, kot je npr.
TopLink, so se prvotno začeli uporabljati z jezikom SmallTalk leta 1994, preden so dejansko
prešli na Javo. Tako so komercialne izvedbe ORM produktov, kot je TopLink, bili na voljo ţe od
prvih dni razvoja s programskim jezikom Java. Bili so uspešni, ampak nikdar standardizirani za
javansko platformo. Podoben ORM pristop so gojili pri razvoju JDO (Java data object), ki pa je
ţal podlegel pomebnemu prodoru na trg. Leta 1998 je bil ustvarjen EJB 1.0 ter kasneje dobavljen
skupaj z J2EE 1.2. Bil je t. i. teţka komponenta, namenjena transakcijam ter poslovni logiki.
CMP (Container Managed persistance) je bil predstavljen v EJB 1.0. ter tudi okrepljena verzija
vse do EJB 2.1 (s prihodom J2EE 1.4). Trajnost objektov je lahko bila zagotovljena le znotraj
vsebnika, skozi kompleksen mehanizem ter instanc Home, Local ter Remote vmesnika.
Zmoţnost ORM mehanizma je bila tudi zelo omejena, prav tako je bilo zelo teţko podpreti
dedovanje [12].
Paralelno z J2EE svetom je na trţišču obstajala popularna izvedba, ki pa je vodila do
korenitnih sprememb v pojmovanju trajnosti podatkov. Imenuje se Hibernate, ki pa je pripeljala
nazaj lahke objektno-orientirane modele za zagotavljanje trajnosti podatkov.
27
Po letih pritoţb okrog CMP 2.x entitetnih zrn in znanja o uspešnosti ter preprostosti
odprtokodnih ogrodij, kot je npr. Hibernate, je bila izboljšana izvedba modela za obstojnost
objektov popolnoma reorganizirana v verziji Java EE 5. JPA 1.0 je bil rojen z zelo lahkim
pristopom ter je posvojil precej oblikovnih principov Hibernatea. JPA 1.0 specifikacija je postala
del paketa EJB 3.0. (JSR 220 – Java specification request). Dandanes z verzijo Jave EE 6, JPA
2.0 (JSR 317) sledi začrtani poti enostavnega razvoja in prinaša nove značilnosti [12].
3.3 Razumevanje entitet
Kadar govorimo o mapiranju objektov v relacijsko podatkovno bazo, raje uporabimo termin
entiteta »entity« kot objekt »object«. Objekti so instance, ki ţivijo samo v notranjem spominu
računalnika. Entitete so objekti, ki ţivijo kratek čas v internem spominu ter trajno v podatkovni
bazi. Imajo to zmoţnost, da so mapirane v podatkovno bazo ter so lahko konkretne ali
abstraktne, podpirajo dedovanje, relacije itd. Ko enkrat mapiramo te entiete, so slednje lahko
upravljane s strani JPA-ja. Entiteto lahko v podatkovno bazo shranimo, odstranimo iz nje, po njej
povprašujemo s pomočjo JPQL. ORM nam dovoljuje, da manipuliramo z entitetami, medtem ko
v ozadju dostopa do podatkovne baze [1].
3.3.1 Objektno-relacijsko mapiranje znotraj JPA
Princip objektno-relacijskega mapiranja je delegiranje nalog zunanjim orodjem (v našem
primeru JPA-ju), da ustvari korespondenco med objekti ter tabelami. Objekti ter njihovi atributi
so nato lahko mapirani v relacijsko podatkovno bazo, narejeno iz tabel, ki vsebujejo vrstice in
stolpce. Mapiranje poskrbi za objektno-orientiran pogled za razvijalce, ki lahko pri razvoju
transparentno uporabljajo entitete namesto tabel. JPA mapira podatke v relacijsko podatkovno
bazo skozi metapodatke. Vsaka entiteta ima pripadajoče metapodatke, ki opisujejo mapiranje. Ti
metapodatki so lahko zapisani v dveh različnih formatih:
XML deskriptorji: Mapiranje, definirano v zunanji XML datoteki, ki bo naloţena na
aplikacijski streţnik skupaj z neko entiteto. To je lahko zelo uporabno, kadar se nastavitve
podatkovne baze spremenijo npr. od odvisnosti od okolja. Značilno za XML deskriptorje je tudi,
da so bili samostojno uporabljeni v zgodnjih verzijah JPA [1].
28
Anotacije: Koda, ki definira neko entiteto je direktno označena z različnimi anotacijami, ki so
opisane v javax.persistance paketu. Njihovo uporabo lahko danes kombiniramo z XML
deskriptorji.
Da naredimo mapiranje laţje, JPA (kot tudi veliko drugih Java EE 6 specifikacij) uporablja
koncept nastavitve izjem (configuration by exception). Ideja je, da JPA podpira nekatera
določena privzeta mapirna pravila (npr. ime tabele je enako kot ime entitete). Če smo s tem
imenom zadovoljni, potem ne potrebujemo dodatnih metapodatkov (nobenih XML deskriptorjev
ali anotacij), če pa ne ţelimo, da naš ponudnik trajnsoti podatkov nastavi privzete vrednosti,
lahko ob uporabi metapodatkov preprosto uporabimo eno izmed zgoraj navedenih tehnik. Z
drugimi besedami, kadar podpremo konfiguracijo s pomočjo anotacij ali XML deskriptorjev,
rečemu temu nastavitve izjem [1].
Izsek kode spodaj prikazuje entiteto Knjiga z nekaterimi atributi. Kot lahko vidimo, so
nekatere izmed njih (id, title, ter description) označene, nekatere pa niso.
@Entity
public class Knjiga {
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String naslov;
private Float cena;
@Column(length = 2000)
private String opis;
private String isbn;
private Integer stStrani;
private Boolean ilustracije;
}
Slika 3 prikazuje, kako razred Knjiga postane prepoznan kot entiteta. Da bi se to zgodilo,
mora biti označen z anotacijo @javax.persistence.Entity (Ali ekvivalentno definiran v XML
deskriptorju). @javax.persistence.id anotacija je uporabljena, da označi primarni ključ ter v
kombinaciji z anotacijo @GeneratedValue pove, da bo primarni ključ avtomatsko generiran od
ponudnika trajnosti podatkov. @Coloumn anotacija je uporabljena nad nekaterimi atributi, da
ponastavi oz. prepiše privzeto mapiranje z neko svojo vrednostjo (naslov postane not nullable ter
opis bo imel dolţino 2000 znakov). Ponudnik trajnosti bo nato zmoţen mapirati entiteto Knjiga v
29
tabelo KNJIGA (ki je seveda privzeto mapirno pravilo), generirati primarni ključ ter
sinhronizirati atribute s stolpci v tabeli [1].
Slika 3: Mapiranje atributev iz entitete v stolpce iz tabele
3.3.2 Povpraševanje po entitetah
JPA omogoča, da mapiramo entitete v podatkovno bazo ter prav tako kasneje povprašujemo
po njih ob uporabi različnih kriterijev. Moč JPA-ja je, da ponuja zmoţnost povpraševanja po
entitetah in njihovih relacijah v objektno-orientiranem načinu, brez potrebe uporabe tujih ključev
ter stolpcev pripadajoče podatkovne baze. Centralni del JPA-ja, zadolţen za orkestracijo
entitetam, je upravljalec entitet (entity manager). Njegova vloga je, da upravlja z entitetami, jih
bere ter zapisuje v podatkovno bazo, dovoljuje izvajanje preprostih CRUD operacij nad
entitetami kot tudi izvajanje zahtevnih povpraševanj s pomočjo uporabe JPQL. Gledano s
tehničnega stališča je upravljalec entitet samo vmesnik, katerega implementacija je narejena iz
strani ponudnika trajnosti podatkov, kot je npr. EclipseLink. Naslednji izsek kode prikazuje,
kako ustvariti upravljalca entitet ter shraniti entiteto Knjiga v trajno stanje [1].
EntityManagerFactory emf = Persistence.createEntityManagerFactory("chapter02PU");
EntityManager em = emf.createEntityManager();
em.persist(knjiga);
Iz navedenega primera je razvidno, kako je lahko vmesnik upravljalca entitet uporabljen v
razredu main za manipulacijo entitet, v tem primeru instance Knjiga. Z metodami, kot so
30
persist() ter find(), upravljalec entitet prikrije JDBC klice ter INSERT ter SELECT stavka na
podatkovno bazo [1].
Slika 4: Interakcija upravljalca entitet med entiteto ter podatkovno bazo
Upravljalec entitet, Slika 4, nam omogoča, da proţimo povpraševanja. Povpraševanje v tem
primeru je podobno samemu povpraševanju na podatakovni bazi, le da za razliko od SQL
povpraševalnega jezika uporabljamo JPQL nad entitetami. Uporablja podobno sintakso kot
objektni pristop z (. pika) označevanjem [1]. Npr. za povpraševanje po vseh knjigah, ki imajo
naslov H2G2, lahko napišemo naslednje:
SELECT b FROM Knjiga WHERE b.naslov = 'H2G2'
JPQL stavki so lahko izvajani z dinamičnimi povraševalnimi stavki (kreirani dinamično v
času izvajanja), statičnimi povpraševalnimi stavki (kreiranimi dinamično v času prevajanja) in pa
tudi čisto navadni SQL stavki. Statični povpraševalni stavki, znani tudi kot poimenovani stavki
(named queries), so definirani ob uporabi anotacij ali XML metapodatkov. Select stavek, napisan
zgoraj, lahko vzamemo kot primer in bi lahko bil definiran tudi kot poimenovan povpraševalen
stavek [1]. Primer spodaj prikazuje definicijo poimenovanega povpraševalnega stavka:
@Entity
@NamedQuery(name = "poisciKnjigePoNaslovu",
query = "SELECT b FROM Knjiga b WHERE b.naslov='H2G2'")
public class Knjiga {
@Id @GeneratedValue
private Long id;
31
@Column(nullable = false)
private String naslov;
private Float cena;
@Column(length = 2000)
private String opis;
private String isbn;
private Integer stStrani;
private Boolean ilustracije;
}
3.3.3 Ţivljenjski cikel entitet
Entitete so samo Plain Old Java Objects (POJOs), ki so upravljane ali ne iz strani upravljalca
entitet. Kadar so opravljane, imajo trajno identiteto in pravimo, da je njihovo stanje
sinhronizirano s podatkovno bazo. Kadar niso upravljane (rečemo, da so odklopljene – detached
od upravljalca entitet), jih lahko uporabimo kot kakršen koli drug Java razred. To pomeni, da
imajo entitete ţivljenjski cikel kot prikazuje Slika 5. Ko ustvarimo instanco entitete Knjiga z
operatorjem new, objekt ţivi v internem spominu ter JPA ne ve popolnoma nič o njem (lahko
celo konča, pobrisan s strani smetarja). Ko pa postane upravljan s strani upravljalca entitet, je
njegovo stanje mapirano in sinhronizirano s tabelo KNJIGA. Ob klicu EntityManager.remove()
metoda izbriše podatke iz podatkovne baze, vendar javanski objekt še vedno ţivi v internem
spominu, dokler ga ne pobriše smetar (garbage collector) [1].
32
Slika 5: Ţivljenjski cikel entitete
3.4 Objektno-relacijsko mapiranje
3.4.1 Mapiranje entitet
Kot sem ţe omenil, se v JPA pogosto pojavlja termin POJO. To pomeni, da je entiteta
deklarirana, instancirana ter uporabljena kot vsak navaden javanski razred. Entiteta ima atribute
(njihova stanja), ki so lahko manipulirana preko get ter set metod. Vsak atribut pa je shranjen v
stolpec v tabeli. Najprej pa mora seveda biti razred označen z @Entity anotacijo, ki omogoča
ponudniku trajnosti, da prepozna razred kot trajen in ne samo kot preprost POJO. Tudi @Id
anotacija je zelo pomembna, saj definira unikatni identifikator tega objekta. Zato ker JPA mapira
objekte v relacijske podatkovne baze, le-ta potrebuje id, da ga ustrezno zmapira v primarni ključ
v podatkovni tabeli [1].
Kadar ţelimo nek razred postaviti za entiteto, mora le-te izpolnjevati naslednje pogoje:
Označen mora biti z @Entity ali ustrezno definiran v XML deskriptorju.
@Id anotacija mora biti uporabljena nad atributom, ki se bo mapiral v primarni ključ v
podatkovni tabeli.
33
Vsebovati mora public ali protected konstruktor, ki je brez kakršnih kolih argumentov.
Seveda lahko ima poleg tega tudi še kakšnega drugega.
Enitetni razred mora biti razred na vrhu. Enum ali vmesnik ne moreta biti označena
kot entiteta.
Entitetni razred ne sme biti final. Prav tako ne smejo biti final atributi ali metode, ki
jih razred vsebuje.
Če mora instanca entitete biti posredovana po vrednosti kot argument (seveda le, kadar
je stanju odkljopljen – (detached), potem mora le-ta implementirati vmesnik
Serialiazable.
34
3.4.2 Koncept nastavitve izjem (configuration by exception)
Java EE 5 je predstavila idejo nastavitve izjem (configuration by exception), včasih znano
tudi kot programiranje izjem (programming by exception). To pomeni, če ne specificiramo
drugače, vsebnik ali ponudnik trajnosti podatkov ponastavi privzeta pravila. Z drugimi
besedami, kadar ročno podpremo konfiguracijo, je to neka izjema obstoječim pravilom. Ta
značilnost nam omogoča, da pišemo minimalno število kode, kadar razvijamo aplikacijo.
Poglejmo na primeru Slika 3. Brez kakršne koli anotacije je entiteta Knjiga tretirana kot
navaden POJO in ne more biti zapisana v trajno stanje. Torej je pravilo: če ni podanih nobenih
posebnih konfiguracij, ponastavimo privzete vrednosti in privzeto za JVM v tem primeru je, da
je razred Knjiga čisto navaden javanski razred. Ampak, ker bi radi spremenili to obnašanje,
moramo spremeniti to privzeto pravilo tako, da nad razredom napišemo anotacijo @Entity.
Enako velja za identifikator. Potrebujemo način, kako povemo ponudniku trajnosti, da mora biti
id atribut mapiran v mapirni ključ, torej le-tega označimo z @Id. Privzeta mapiranja pravil se
vidijo tudi na:
Ime entitete je mapirano v ime relacijske podatkovne tabele (Knjiga entiteta v
KNJIGA tabelo). Če bi hoteli entiteto mapirati v drugo tabelo, bi morali uporabiti
@Table anotacijo, kot bomo to videli v naslednjem pogavju – elementarno mapiranje
[1].
Imena atributov so mapirana v imena stolpcev v tabeli (id atribut oz getId(), metoda je
mapirana v stolpec ID v podatkovni tabeli. Če bi hoteli mapiranje atributov spremeniti
v neke druge stolpce v tabeli, bi morali uporabiti @Column anotacijo.
JDBC pravila nastavijo javanske primitivne tipe v relacijske podatkovne tipe. String
bo mapiran v VARCHAR, long v BIGINT, Boolean v SMALLINT itd. Privzeta dolţina
stolpca, mapiranega iz Stringa, je 255 (String je mapiran v VARCHAR(255)). Vendar
moramo imeti v mislih, da se privzeta mapirna pravila razlikujejo od podatkovne baze
do podatkovne baze. Npr. String je mapiran v VARCHAR v Derbyju, ampak v
VARCHAR2 na Oracleovi podatkovni bazi. Integer je mapiran v INTEGER na Derbyju
ter v NUMBER na Oracleovi podatkovni bazi. Vsi ti podatki od pripadajoče baze so
zbrani v persistence.xml datoteki, ki jo bom opisal v kasnejših poglavjih [1].
35
3.4.3 Elementarno mapiranje
Obstajajo velike razlike, kako so podatki obravnavani v Javi ter kako v relacijski podatkovni
bazi. V Javi uporabljamo razrede, da opišemo atribute, ki hranijo podake, ter metode za dostop in
manipuliranje le-teh. Ko enkrat definiramo razred, lahko ustvarimo toliko instanc, kolikor
hočemo s pomočjo operatorja new. V relacijske podatkovne baze shranjujemo samo podatke, ne
pa tudi obnašanje (izjema so proţilci ter bazne procedure), njihova struktura je popolnoma
drugačna, saj v primerjavi z razredi uporabljajo vrstice ter stolpce. Včasih je mapiranje javanskih
objektov v pripadajočo vrstico v relacijski tabeli zelo enostavno ter so lahko sprejete privzete
vrednosti. V nekaterih drugih primerih te vrednosti ne izpolnjujejo zahtev, zato moramo
popolnoma po svoje nastaviti mapiranje. Elementarno mapiranje se osredotoča na nastavljanje
pravil tabelam, primarnim ključem, stolpcem [1].
3.5 Tabele
3.5.1 @Table
@javax.persistance.Table anotacija omogoča, da spremenimo privzete vrednosti k tej
pripadajoči tabeli. Npr. lahko specificiramo ime tabele, v katero se bodo shranili podatki, katalog
kot tudi shema podatkovne baze. Če to anotacijo izpustimo, bo JVM to razumel kot privzeto
vrednost ter bo mapiral ime podatkovne tabele enako, kot je ime entitete. Če ţelimo npr.
spremeniti ime tabele T_KNJIGA namesto KNJIGA, naredimo to kot prikazuje primer spodaj:
@Entity
@Table(name = "t_knjiga")
public class Knjiga {
@Id
private Long id;
private String naslov;
private Float cena;
private String opis;
private String isbn;
private Integer stStrani;
private Boolean ilustracije;
public Knjiga() {
}
}
36
Naj še omenim, da @Table anotacija vsebuje ime tabele t_knjiga z malimi črkami. Privzeto
bo večino podatkovnih baz mapirala entiteto v tabelo z velikimi črkami (T_KNJIGA), zatorej se
prikazano ne smatra kot napaka [1].
3.5.2 @SecondaryTable
Vse do zdaj sem povzemal, da je entiteta mapirana v eno samo tabelo, znano tudi kot
primarna tabela. Ampak včasih, kadar imamo ţe obstoječ model, moramo razporediti podatke po
večih tabelah, imenovanih tudi sekundarne tabele. Če ţelimo doseči to funkcionalnost, moramo
uporabiti anotacijo @SecondaryTables ter znotraj te definirati sekundarne tabele z anotacijo
@SecondaryTable. S tem doseţemo, da lahko razpršimo podatke neke entitete po različnih
stolpcih obeh tabel – primarne ter sekundarne tabele. Nato lahko nad vsakim atributom
definiramo @Column anotacijo, v kateri navedemo, na katero tabelo se navezuje posamezen
atribut, kot je prikazano na primeru spodaj, ko je eniteta Naslov mapirana v tri različne tabele:
@Entity
@SecondaryTables({
@SecondaryTable(name = "mesto"),
@SecondaryTable(name = "država")
})
public class Address {
@Id
private Long id;
private String ulica1;
private String ulica2;
@Column(table = "city")
private String mesto;
@Column(table = "mesto")
private String postnaStevilka;
@Column(table = "država")
private String država;
// Constructors, getters, setters
}
37
Privzeto so atributi entitete Naslov, mapirani na primarno tabelo (katera ima privzeto ime
entitete, njeno ime je NASLOV). Anotacija @SecondTables nas informira, da bosta tukaj
uporabljeni dve sekundarni tabeli MESTO ter DRŽAVA. Seveda moramo nato določiti, kateri
atribut bo shranjen v katero sekundarno tabelo (@Column(table= »mesto«) ali
@Column(table= »država«)). Rezultat, prikazan na sliki 6 – Entiteta Naslov je mapirana v tri
tabele, je primer treh tabel, ki vsebujejo različne atribute, ampak z enakim primarnim ključem
(za zdruţevanje tabel) [1].
Slika 6: Entiteta Naslov je mapirana v tri tabele
Kot je razvidno iz primera na Slika 6, lahko imamo različne anotacije za enako entiteto. Če
hočemo preimenovati primarno tabelo, lahko uporabimo anotacijo @Table. Primer spodaj
prikazuje spremembo imena primarne tabele, in sicer iz prejšnje NASLOV v T_NASLOV [1].
@Entity
@Table(name = "t_naslov ")
@SecondaryTables({
@SecondaryTable(name = "t_mesto"),
@SecondaryTable(name = "t_država")
})
public class Naslov {
// Attributes, constructor, getters, setters
}
38
3.6 Primarni ključi
V relacijskih podatkovnih bazah primarni ključ unikatno identificira vsako vrstico v tabeli.
Primarni ključ obsega enojni stolpec ali set večih stolpcev skupaj. Mora biti unikaten, saj
identificira vsak posamezen zapis (null vrednosti niso dovoljene) [1].
3.6.1 @Id ter @GeneratedValue
Preprost nesestavljen ključ mora biti skladen z enojnim atributom v entitetnem razredu.
Anotacijo @Id, ki smo jo ţe videli, uporabljamo, da označimo primarni ključ.
@javax.persistance.Id označi atribut kot unikatni identifikator, ki je lahko eden izmed sledečih
tipov:
- primitivni javanski tip: byte, short, long, char, int;
- oviti razred primitivnih javanskih tipov: Byte, Short, Long, Char, Int;
- polje primitivnih tipov ali ovitih razredov: int[], Integer[] ...;
- niz, število ter datum: java.lang.String, java.math.BigInteger, java.util.Date, java.sql.Date.
Kadar ustvarimo entiteto, je lahko njena vrednost prirejena ročno od aplikacije ali avtomatsko
od ponudnika trajnosti z anotacijo @GeneratedValue. Ta anotacija ima štiri moţne vrednosti:
SEQUENCE in IDENTITY.
TABLE naroči ponudniku trajnosti podatkov, da shrani ime sekvence in njeno trenutno
vrendnost v tabelo tako, da povečuje vrednost vsakič, ko je shranjena nova instanca
entitete. Kot primer navajam, da podatkovna baza Derby ustvari SEQUENCE tabelo z
dvema stolpcema: ime sekvence (ki je samovoljno ime) ter vrednost sekvence
(Integer, avtomatično povečan s strani Derby podatkovne baze) [1].
AUTO generiranje ključa je avtomatično narejeno od pripadajoče podatkovne baze. Ta
izbere strategijo generiranja, ki pa se spet razlikuje med posameznimi podatkovnimi
bazami. AUTO je tudi privzeta vrednost anotacije @GeneratedValue.
39
Primer:
@Entity
public class Knjiga {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
...
}
3.7 Sestavljeni primarni ključi
Včasih se v praksi pojavijo potrebe po sestavljenih ključih. Da bi podprli to, mora biti najprej
definiran primarni ključ. Nato imamo na voljo dve anotaciji za definicijo razreda, odvisno, kako
ţelimo strukturirati entiteto. Ti moţnosti sta: @Embeeded ter @IdClass. Kot bomo videli, je
končni rezultat enak, le način, kako bomo povpraševali po entitetah, je malo drugačen [1].
3.7.1 @EmbeededId
JPA uporablja različne načine vključevanja objektov. Če povzamemo, vključen objekt nima
nobene identitete (nobenega samostojnega primarnega ključa), v stolpce bodo mapirani le
njegovi atributi, ki jih ta vsebuje. Primer spodaj prikazuje razred NovicaId, ki je vključen razred.
Je preprost razred, ki je označen z anotacijo @Embeeddable in vsebuje dva atributa naslov ter
jezik. Ta razred mora vsebovati tudi get ter set metode, konstruktor brez argumentov ter metodi
equals() ter hashCode(). Razred kot sam nima nobene oznake identitete (brez anotacije @Id).
Primer vidimo na izseku spodaj:
@Embeddable
public class NovicaId {
private String naslov;
private String jezik;
// Constructors, getters, setters, equals, and hashcode
}
40
V entiteto Novica moramo nato vključiti primarni ključ, ki je tipa NovicaId, z anotacijo
@EmbeededId. Vsak @EmbeededId, se mora navezovati na razred, ki je označen z
@Embeeddable anotacijo. Navedeno prikazuje spodnji izsek:
@Entity
public class Novica {
@EmbeddedId
private NovicaId id;
private String vsebina;
// Constructors, getters, setters
}
Primer iskanje objekta ob uporabi sestavljenega ključa @EmbeededId:
NewsId pk = new NewsId("Richard Wright has died", "EN")
News news = em.find(News.class, pk);
3.7.2 @IdClass
Drug način, kako deklarirati sestavljen ključ, je s pomočjo @IdClass anotacije. Je popolnoma
drugačen pristop, saj mora vsak atribut v razredu primarnih ključev biti tudi deklariran v
entitetnem razredu in označen z @Id. Iz primera spodaj je razvidno, da je razred NovicaId
preprost POJO in ne zahteva nobene anotacije [1].
public class NovicaId {
private String naslov;
private String jezik;
// Constructors, getters, setters, equals, and hashcode
}
Entiteta Novica mora nato definirati primarna ključa ob uporabi @IdClass anotacije nad
entiteto ter prav tako označiti vsak ključ z @Id anotacijo. Slednje prikazuje primer spodaj:
@Entity
@IdClass(NovicaId.class)
public class Novica {
@Id private String naslov;
@Id private String jezik;
private String vsebina;
// Constructors, getters, setters, equals, and hashcode
}
Oba pristopa @EmbeededId ter @IdClass pa mapirata v isto tabelo. Atributi entitete in primarni ključi
bodo v obeh primerih končali zapisani v tej tabeli. Struktura tabele NOVICA:
create table NOVICA (
VSEBINA VARCHAR(255),
NASLOV VARCHAR(255) not null,
41
JEZIK VARCHAR(255) not null,
primary key (NASLOV, JEZIK)
);
@IdClass pristop je bolj dovzeten za napake, saj moramo definirati vsak primarni ključ v
obeh entitetah ter @IdClass razredu. Pri tem moramo biti pozorni na uporabo enakih imen ter
javanskih podatkovnih tipov. Prednost pa je, da pri tem ne potrebujemo pisati posebnega razreda
za primarne ključe. Vidna razlika je, kako ob uporabi JPQL povprašujemo po podatkih.
Z uporabo @IdClass povpraševanje izgleda:
select n.naslov from Novica n
Z uporabo @EmbeededId pa:
select n.novicaid.naslov from Novica n
3.8 Atributi
Kot sem ţe prikazal, so z nastavitvijo izjem atributi mapirani s privzetimi pravili v nek stolpec
v podatkovni bazi. Seveda pa včasih ţelimo sami ponastaviti kak del tega mapiranja. Pri tem
nam pomagajo JPA anotacije (ali ekvivalentna XML datoteka) [1].
3.8.1 @Basic
@javax.perisistance.Basic anotacija je najpreprostejši način mapiranja atributov v nek
stolpec, ki prepiše osnovne nastavitve. Osnovni elementi anotacije:
@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface Basic {
FetchType fetch() default EAGER;
boolean optional() default true;
}
Anotacija ima dva parametra: optional ter fetch. Parameter otional nam da nasvet, kdaj lahko
ima atribut vrednosti null. Seveda to ne velja za primitivne tipe. Fetch element sprejme dve
vrednosti: LAZY ali EAGER, ki povesta ponudniku trajnosti, da nalaga podatke »lazy« – počasi,
kar pomeni, da so naloţeni šele, kadar uporabnik dejansko povpraša po objektu, ali pa EAGER,
ki pove, da so vsi objekti napolnjeni naenkrat, torej ob naloţitvi entitete v pomnilnik. Primer za
LAZY prikazuje izsek spodaj:
42
@Entity
public class Pesem {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String naslov;
private Float dolžina;
@Basic(fetch = FetchType.LAZY)
@Lob
private byte[] wav;
@Basic(optional = true)
private String opis;
// Constructors, getters, setters
}
Kot vidmo, ima atribut wav tipa byte[] tudi anotacijo @Lob, kar pove ponudniku trajnosti, da
shranjuje podatke kot »Large object« velike objekte. Podatkovne baze, ki so sposobne hraniti
takšne podatke, zahtevajo posebne JDBC klice. Da obvestimo ponudnika o tem, se na tem mestu
zahteva anotacija @Lob [1].
3.8.2 @Column
Ta anotacija definira atribute nekega stolpca. Lahko mu spremenimo ime, velikost ter
autoriziramo stolpec, da sprejme vrednosti null ali pa tudi ne. Lahko ga naredimo unikatnega in
dovolimo njegovi vrednosti vstavljanje ali posodabljanje. Odsek kode spodaj prikazuje vmesnik
@Column ter njegove privzete vrednosti [1].
@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface Column {
String name() default "";
boolean unique() default false;
boolean nullable() default true;
boolean insertable() default true;
boolean updatable() default true;
String columnDefinition() default "";
String table() default "";
int length() default 255;
int precision() default 0; // decimal precision
int scale() default 0; // decimal scale
}
Primer razreda iz odseka kode, naveden spodaj, pa prikazuje, kako po svoje ponastavimo
atribute v entiteti Knjiga. Ponastavili smo ime za naslov in stStrani ter dodali novo dolţino
atributu opis. Prav tako smo atributoma naslov ter stStrani nastavili pravilo, ki ne dovoljuje null
43
vrednosti tega stolpca, naslovu pa tudi, da kadar je vrednost enkrat zapisana v podatkovni bazi,
se le-ta več ne more prepisati s kakšno drugo vrednostjo:
public class Knjiga {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "knjiga_naslov", nullable = false, updatable = false)
private String naslov;
private Float cena;
@Column(length = 2000)
private String opis;
private String isbn;
@Column(name = "st_strani", nullable = false)
private Integer stStrani;
private Boolean ilustracije;
// Constructors, getters, setters
}
Večina elementov z @Column anotacijo ima vpliv na mapiranje. Če npr. spremenimo število
znakov v atributu opis na 2000, se spremeni prav tako stolpec v bazi. Kar pomeni, da naša tabela
po spremembi entitete sedaj izgleda tako:
create table KNJIGA (
ID BIGINT not null,
KNJIGA_NASLOV VARCHAR(255) not null,
CENA DOUBLE(52, 0),
OPIS VARCHAR(2000),
ISBN VARCHAR(255),
ST_STRANI INTEGER not null,
ILUSTRACIJE SMALLINT,
primary key (ID)
);
3.8.3 @Temporal
V Javi lahko uporabljamo java.util.Date ter Java.util.Calendar za shranjevanje podatkov kot
so datum, ura, milisekunde itd. Da specificiramo to v objektno-relacijskem mapirnem
mehanizmu, lahko uporabimo @javax.persistance.Temporal anotacijo. Ta podpira tri moţne
vrednosti: DATE, TIME, TIMESTAMP. Primer spodaj prikazuje entiteto Stranka, ki vsebuje
datum rojstva ter točen čas dejanskega ustvarjanja entitete [1].
@Entity
public class Stranka {
@Id
@GeneratedValue
private Long id;
private String ime;
private String priimek;
private String email;
44
private String telefonsksSt;
@Temporal(TemporalType.DATE)
private Date datumRojstva;
@Temporal(TemporalType.TIMESTAMP)
private Date datumVnosa;
// Constructors, getters, setters
}
Ta entiteta bo mapirana v tabelo, prikazano spodaj, atribut datumRojstva v stolpec tipa DATE
ter atribut datumVnosa v stolpec tipa TIMESTAMP:
create table STRANKA (
ID BIGINT not null,
IME VARCHAR(255),
PRIIMEK VARCHAR(255),
EMAIL VARCHAR(255),
TELEFONSKAST VARCHAR(255),
DATUMROJSTVA DATE,
DATUMVNOSA TIMESTAMP,
primary key (ID)
);
3.8.4 @Transient
JPA omogoča, da, kakor hitro označimo razred kot entiteto (@Entity), vsi njegovi atributi
privzeto postanejo mapirni s pripadajočo tabelo. Včasih pa te funkcionalnosti ne ţelimo. To
eliminiramo tako, da nad atributom, nad katerim nočemo, da se zapisuje v podatkovno tabelo,
napišemo anotacijo @Transient [1]. Primer spodaj nakazuje uporabo omenjenega nad atributom
age:
@Entity
public class Stranka {
@Id
@GeneratedValue
private Long id;
private String ime;
private String priimek;
private String email;
private String telefonskaSt;
@Temporal(TemporalType.DATE)
private Date datumRojstva;
@Transient
private Integer starost;
@Temporal(TemporalType.TIMESTAMP)
private Date datumVnosa;
// Constructors, getters, setters
}
45
3.8.5 @Enumerated
Java SE 5 je predstavila naštevne tipe, kateri so danes tako široko uporabljeni, da jih razvijalci
vsakodnevno uporabljajo pri svojih opravilih. Vrednosti naštevnega tipa so konstante in imajo
implicitno zaporedno prireditev, ki je določena v vrstnem redu, kot je deklarirana. To zaporedje
ne more biti spremenjeno med časom izvajanja, vendar pa je lahko uporabljeno, da shrani
podatke v podatkovno bazo [1]. Primer spodaj prikazuje naštevni tip za kreditne kartice:
public enum TipKreditneKartice {
VISA,
MASTER_CARD,
AMERICAN_EXPRESS
}
Vsaki deklarirani konstanti se tako ob času povezovanja priredi neka vrednost. Torej: 0 za
VISA, 1 za MASTER_CARD, 2 za AMERICAN_EXPRESS. Tako bo naš ponudnik trajnosti
privzeto mapiral ta naštevni tip v podatkovno bazo ob predpostavki, da je mapiran stolpec tipa
Integer [1]. Primer se nahaja na izseku spodaj:
@Entity
@Table(name = "kreditna_kartica")
public class KreditnaKartica{
@Id
private String stevilka;
private String dastumVeljavnosti;
private Integer kontrolnaStevilka;
private TipKreditneKartice tipKreditneKartice;
}
Ker tudi tukaj privzete vrednosti niso izjema, bo naštevni tip mapiran na stolpec tipa Integer.
Ampak pojavi se teţava, kadar ţelimo dodati naštevni tip in tega dodamo na vrh deklaracije
naštevnega tipa. Takrat se nam skladnost s podatki v bazi poruši. Iz tega vidika je bolje uporabiti
podatkovni tip String kot pa Integer. To storimo tako, da nad atributom, ki je naštevni tip,
zapišemo anotacijo v @Enumerated ter mu spremenimo podatkovni tip v String [1]. Primer
prikazuje izsek spodaj:
@Entity
@Table(name = "kreditna_kartica")
public class KreditnaKartica {
@Id
private String stevilka;
private String datumVeljavnosti;
private Integer kontrolnaStevilka;
@Enumerated(EnumType.STRING)
private TipKreditneKartice tipKreditneKartice;
}
46
Zdaj ima naš naštevni tip stolpec v podatkovni bazi, ki je tipa VARCHAR, ter tudi kartica Visa
bo shranjena kot »VISA«.
3.9 Tipi dostopanj
Do zdaj sem anotacije, ki se tičejo atributov oz. dostopov do polij, vedno prikazoval samo nad
atributi. Obstaja pa tudi drug način označevanja oz. uporabe anotacij za atribute z enako
funkcionalnostjo. Ta je, da lahko uporabimo anotacije tudi na get metodami nekaterih atributov.
Ker je to odvisno od razvijalčevih osebnih preferenc, na tem mestu na kratko prikazujem, kako
uporabiti ene in druge [1]. Ker anotacije nad atributi ţe poznamo, izsek spodaj prikazuje
označevanje metod:
@Entity
public class Stranka {
//attributes
@Column(name = "priimek", nullable = false, length = 50)
public String getPriimek() {
return priimek;
}
//other get and set methods
}
Seveda pa to ni edina značilnost dostopov do podatkovnih tipov. Kadar ţelimo, lahko te med
sabo tudi mešamo. To storimo z uporabo @javax.persistance.Access anotacije. Seveda obstajajo
zato določena pravila. Ta anotacija sprejme dve vrednosti ali FIELD ali PROPERTY. Če npr.
uporabimo @Access(AccessType.FIELD) določilo nad samo entiteto, to za ponudnika trajnosti
pomeni, da upošteva samo anotacije nad atributi. Obstaja pa moţnost, da kasneje ta koncept
preuredimo. To storimo z uporabo @Access(AccessType.PROPERTY), nadpisanim nad get
metode, in dobimo entiteto, v kateri uporabljamo tako atribute kot metode za tipe dostopa [1].
Omenjeni primer prikazuje izsek spodaj:
@Entity
@Access(AccessType.FIELD)
public class Stranka {
@Id @GeneratedValue
private Long id;
@Column(name = "ime", nullable = false, length = 50)
private String ime;
@Column(name = "priimek", nullable = false, length = 50)
private String priimek;
private String email;
@Column(name = "telefonska_stevilka", length = 15)
private String telefonskaStevilka;
47
// Constructors, getters, setters
@Access(AccessType.PROPERTY)
@Column(name = "telefonska_stevilka", length = 555)
public String getTelefonskaStevilka() {
return telefonskaStevilka;
}
}
3.9.1 Kolekcije osnovnih tipov
S prihodom JPA 2.0 lahko imamo v neki entiteti kolekcijo, torej nek seznam primitivnih
tipov, ki jih je prav tako preprosto mapirati brez pisanja ločenih razredov. To storimo tako, da
uporabimo anotacijo @ElementCollection ter @CollectionTable. Z anotacijo
@ElementCollection povemo ponudniku trajnosti, da imamo opravka s seznamom, ki vsebuje
preproste javanske podatkovne tipe. @CollectionTable nam poleg omenjenega omogoči, da
ponastavimo še detajle seznama, kot so npr. ime. Če le-to izpustimo, je ime konkatinirano
samodejno, in sicer ime entitete ter ime atributa, ki deklarira seznam ločeno s podčrtajem [1].
Primer:
public class Knjiga {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String naslov;
private Float cena;
private String opis;
private String isbn;
private Integer stStrani;
private Boolean ilustracije;
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(name = "Zaznamek")
@Column(name = "vrednost")
private ArrayList<String> zaznamki;
// Constructors, getters, setters
}
@ElementCollection določa strategijo pridobivanja podatkov. V mojem primeru je določen
LAZY, kar pomeni, da so pridobljeni šele ob dejanskem povpraševanju po objektu.
@CollectionTable je prav tako podan, kar pomeni, da je v veljavi ta. Če bi ga izpsutili, bi bilo
ime privzeto sestavljeno in bi izgledalo KNJIGA_ZAZNAMEK. Prav tako sem dodal
komplementarno @Column anotacijo za preimenovanje stolpca v vrednost. Slika 7 prikazuje
relacijo med tabelo KNJIGA ter ZAZNAMEK [1].
48
Slika 7: Relacija med KNJIGA in ZAZNAMEK tabelo
3.10 Relacije entitete
Večina entitet v realnem svetu je zasnovana tako, da je zmoţna imeti referenco oz. relacijo z
drugimi entitetami. JPA omogoča, da mapiramo asociacije tako, da so entitete v relacijskem
modelu lahko povezane druga na drugo. Tudi tukaj JPA uporablja nastavitve izjem pri uporabi
anotacij. Tako ima privzet način shranjevanja relacije, vendar, kadar to ne zadosti našemu
relacijskemu modelu, imamo na razpolago nekaj načinov anotacij, ki jih lahko uporabimo za
prilagoditev mapiranj. Kardinalnost med dvema entitetama je lahko ena-ena, ena-mnogo,
mnogo-ena, mnogo-mnogo. Vsaka pa ima seveda svojo anotacijo. Tabela spodaj prikazuje vse
moţne kombinacije med kardinalnostjo in usmerjenostjo [1].
49
Tabela 2: Moţne kombinacije med kardinalnostjo in usmerjenostjo
Kardinalnost Usmerjenost
Ena-ena Enosmerna
Ena-ena Dvosmerna
Ena-mnogo Enosmerna
Ena-mnogo / mnogo-ena Dvosmerna
Mnogo-ena Enosmerna
Mnogo-mnogo Enosmerna
Mnogo-mnogo Dvosmerna
Kot je vidno iz Tabela 2, obstajajo različne kardinalnosti v kombinacijami z usmerjenostjo, ki
jih JPA podpira. Vendar se bom zaradi preprostosti prikaza v nadaljevanju sam osredotočil na
dve.
3.10.1 Enosmerna in dvosmerna usmerjenost
3.10.1.1 Enosmerna ena-ena
Pri enosmerni relaciji, kot prikazuje Slika 8, vidimo, da ima entiteta Stranka atribut tipa
Naslov. Ta relacija je enosmerna, navigirana iz ene smeri proti drugi. Stranka je v tem primeru
lastnik relacije. S stališča podatkovne baze to pomeni, da vsebuje tabela STRANKA tuji ključ, ki
se nanaša na tabelo NASLOV. Prav tako je Stranka kot lastnik relacije odgovoren za mapiranje
le-te [1].
50
Slika 8: Enosmerna asociacija med entitetama Customer ter Address
3.10.1.2 Dvosmerna ena-ena
Relacija pa je lahko tudi dvosmerna, kot prikazuje Slika 9. Da bi lahko navigirali iz ene v
drugo in tudi obratno, moramo spremeniti enosmerno v dvosmerno relacijo. To naredimo tako,
da v praksi dodamo atribut tipa Stranka tudi v entiteto Naslov [1].
Slika 9: Dvosmerna asociacija med entitetama Stranka ter Naslov
3.10.2 Nalaganje relacij
Vse anotacije, ki jih JPA podpira (@OneToOne, @OneToMany, @ManyToOne, ter
@ManyTo-Many), definirajo atribut, ki definira, kako se bo objekt naloţil. Obstajata dva
pristopa, in sicer takoj (eager) ali pa je njegovo nalaganje odloţeno na kasnejši čas (lazy), kar
znatno pripomore k izboljšanju performance.
51
Kot primer, ki ga prikazuje Slika 10, vzamimo štiri entitete, povezane med sabo z različno
kardinalnostjo. Vse naj imajo definirano takojšnje (eager) nalaganje, kar pomeni, da takoj, ko
bomo povprašali po objektu 1, bomo kot rezultat dobili vse objekte zapisane v pomnilnik, kar
lahko drastično upočasni aplikacijo [1].
Slika 10: Štiri entitete s takojšnjim nalaganjem
Opozicijski scenarij prikazuje Slika 11. Vzamimo, da vse relacije uporabljajo odloţeno (lazy)
nalaganje. Kadar naloţimo objekt 1, se naloţi le-ta in nič drugega. Seveda pa ta hrani
informacijo o objektu 2, zato ob povpraševanju iz enega objekta v drugega dobimo nazaj tudi
tega. Za to poskrbi ponudnik trajnosti, ki ob vsakem pomiku naloţi podatke iz podatkovne baze
(class1.getClass2().getClass3().getClass4()) [1].
Slika 11: Štiri entitete z odloţenim nalaganjem
Vendar je zmotno misliti, da je eager nekaj slabega ter lazy nekaj dobrega. Vsak prinaša
prednosti in slabosti. Slabost takojšnjega nalaganja je performance aplikacije, slabost odloţenega
pa večkratno klicanje podatkovne baze. Vsaka anotacija ima svoje privzete vrednosti nalaganja,
ki jih je dobro poznati, da se izognemo performančnim teţavam. Slednje prikazuje Slika 12 [1].
Slika 12: Privzete nalagalne strategije
52
3.10.3 Razvrščanje relacij
Z uporabo ena-mnogo ter mnogo-ena relacij entitete upravljajo s seznami (kolekcijami)
objektov. Iz strani Jave so ti seznami ponavadi nesortirani. Tudi s strani podatkovne baze na
tabelah ne najdemo atributov, ki bi skrbeli za vrstni red. Kadar bi se radi posluţevali nekega
vrstnega reda podatkov, se moramo ali posluţiti programskega sortiranja podatkov ali pri
uporabi JPQL povpraševalnega stavka dodati določilo Order By. JPA podpira za ta namen
enostavnejši mehanizem [1].
3.10.4 @OrderBy
Dinamično razvrščanje lahko uporabimo s pomočjo @OrderBy anotacije. Dinamično pomeni,
da se razvrščanje izvede pri samem povpraševanju. Spodnji primer se navezuje na komentarje
oddane na neki spletni strani [1]. Ker bi hoteli, da so le-ti razvrščeni po datumu, uporabimo
naslednje anotacije:
@Entity
@Entity
public class Novica {
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String vsebina;
@OneToMany(fetch = FetchType.EAGER)
@OrderBy("datumVnosa desc")
private List<Komentar> komentarji;
// Constructors, getters, setters
}
@Entity
public class Komentar{
@Id @GeneratedValue
private Long id;
private String vzdevek;
private String vsebina;
private Integer opomba;
@Column(name = "datum_vnosa")
@Temporal(TemporalType.TIMESTAMP)
private Date datumVnosa;
// Constructors, getters, setters
}
53
Kot vidimo, ima entiteta Novica relacijo s seznamom komentarjev tipa Komentar. Anotaciji
za relacijo @OneToMany dodamo še eno, in sicer @OrderBy, ki pove, da naj bodo objekti ob
nalaganju razvrščeni po atributu datumVnosa iz entitete Komentar. Atribut datumVnosa je tipa
TIMESTAMP in je avtomatično ustvarjena s strani sistema [1].
3.10.5 @OrderColumn
JPA 1.0 podpira @OrderBy anotacijo, vendar pa ne zna upravljati s trajnostjo razvrščanja. S
prihodom JPA 2.0 pa se za te namene doda nova anotacija @OrderColumn. Ta anotacija
informira ponudnika trajnosti, da je zahtevan vrstni red ob uporabi posebnega stolpca, kjer se
shranjuje indeks. @OrderColumn v bistvu definira ta stolpec. V kratkem to pomeni, da kadar
imamo entiteto, ki ne ponuja atributa, po katerem bi lahko sortirali, JPA sam poskrbi za to s
kreiranjem lastnega stolpca, kjer se ob vstavljanju, posodobljanju itd. vpisuje indeks, ki kasnje
sluţi za razvrščanje. Primer spodaj prikazuje navedeno. Zopet imamo entiteto Komentar, vendar
tokrat brez datumVnosa atributa [1].
@Entity
public class Komentar {
@Id @GeneratedValue
private Long id;
private String vzdevek;
private String vsebina;
private Integer opomba;
// Constructors, getters, setters
}
Kot je razvidno iz entitete Novica spodaj, se tokrat ne morem sklicevati na atribut iz tabele
Komentar. Zato si ponastavim ime, za katerega pa JPA kreira dodaten stolpec v tabeli. Če imena
ne ponastavim, razvrščanje pa kljub temu ţelim uporabiti, bo JPA v našem primeru kreiral
stolpec z imenom KOMENTAR_ORDER [1].
@Entity
public class Novica {
@Id @GeneratedValue
private Long id;
@Column(nullable = false)
private String vsebina;
@OneToMany(fetch = FetchType.EAGER)
@OrderColumn("index_vnosa")
private List<Comment> komentarji;
// Constructors, getters, setters
}
54
3.11 Mapiranje dedovanja
Dedovanje je precej neznano področje na področju relacijskih podatkovnih baz. Koncept
dedovanja postavi vprašanja, kot je kako organizirati hierarhični model v relacijskega? JPA
pozna tri različne strategije. Izbiramo lahko med:
Strategija enojna tabela – pri čemer so vsi atributi celotne entitetne hierarhije
sploščeni navzdol v enojno tabelo (privzeta strategija).
Strategija pridruţenja – pri tem pristopu je vsaka entiteta v hierarhiji, konkretna ali
abstraktna, mapirana v svojo pripadajočo tabelo.
Strategija tabela na konkreten razred – pri tem pristopu je vsaka, ampak samo
konkretna entiteta v hierarhiji, mapirana v svojo pripadajočo tabelo [1].
3.11.1 Strategije dedovanja
3.11.1.1 Enojna tabela (SINGLE TABLE Strategy)
To je tudi privzeta strategija dedovanja, v kateri vse entitete v hierarhiji zmapiramo v enotno
tabelo. Kot privzeto lahko kompletno prezremo @Inheritance anotacijo na korenski entiteti
(zahvaljujoč nastavitvam izjem) [1]. Primer prikazuje dedovanje CD-ja ter knjige iz osnovnega
razreda Predmet:
@Entity
public class Predmet {
@Id @GeneratedValue
protected Long id;
@Column(nullable = false)
protected String naslov;
@Column(nullable = false)
protected Float cena;
protected String opis;
// Constructors, getters, setters
}
@Entity
public class Knjiga extends Predmet {
private String isbn;
private String založnik;
private Integer stStrani;
private Boolean ilustracije;
// Constructors, getters, setters
}
55
@Entity
public class CD extends Predmet {
private String glasbenaZaložba;
private Integer steviloCdjev;
private Float skupnaDolžina;
private String zvrst;
// Constructors, getters, setters
}
Če bi mapirali entitete, kot smo vajeni do sedaj, bi se vse tri različne entitete preslikale v tri
različne tabele. Seveda pa je to s pomočjo dedovanja, podprtega s strani JPA-ja, popolnoma
drugače. S strategijo enojne tabele bodo vse tri končale v isti tabeli, njeno ime pa je enako
tistemu iz entitete na vrhu hierarhije (starša) [1].
Slika 13: Struktura tabele Predmet
Kot lahko vidimo, Slika 13 prikazuje tabelo Predmet, ki zdruţuje vse atribute entitete Knjiga,
Predmet ter CD. Vendar imamo tudi dodaten stolpec, ki ne pripada nobeni lastnosti teh treh
entitet. Stolpec imenujemo »discriminator« in je v našem primeru poimenovan ot DTYPE.
Tabela Predmet bo napolnjena s predmeti, knjigami ter CD-ji. Kadar povprašujemo po podatkih,
mora ponudnik trajnosti vedeti, kateri zapis pripada kateri entiteti. Tako lahko ponudnik trajnosti
zagotovi pravilno instanciranje objekta (Predmet, Knjiga ali CD). Slika 14 prikazuje delček
tabele PREDMET s pripadajočimi podatki. Kot lahko vidimo, ima strategija enojne tabele
pomankljivosti. Ena izmed teh je, da ni vsak stolpec uporaben za vsako entiteto. Prvi zapis v
tabeli je zapis, shranjen iz entitete Predmet (DTYPE pove, kateri entiteti pripada zapis). Predmeti
imajo samo naslov, ceno ter opis, nimajo pa npr. glasbenega zaloţnika, isbnja itd. Tako ti stolpci
vedno ostanejo prazni [1].
56
Slika 14 : Izsek zapisov iz podatkovne tabele PREDMET
Poglejmo še anotacijo @DiscriminatorName, s katero spremenimo ime stolpcu
»discriminator« v podatkovni bazi, ter anotacijo @DiscriminatorValue, s katero spremenimo ime
entitete v discriminator stolpcu podatkovne baze. Izsek spodaj prikazuje preimenovanje
discriminator stolpca ter imen entitet:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn (name="disc", discriminatorType = DiscriminatorType.CHAR)
@DiscriminatorValue("P")
public class Predmet {
@Id @GeneratedValue
private Long id;
private String naslov;
private Float cena;
private String opis;
// Constructors, getters, setters
}
@Entity
@DiscriminatorValue("K")
public class Knjiga extends Predmet {
private String isbn;
private String založnik;
private Integer stStrani;
private Boolean ilustracije;
// Constructors, getters, setters
}
@Entity
@DiscriminatorValue("C")
public class CD extends Predmet {
private String glasbeniZaložnik;
private Integer steviloCdjev;
private Float skupnaDolžina;
private String zvrst;
// Constructors, getters, setters
}
57
Rezultat zapisov v bazi bi sedaj izgledal kot prikazuje Slika 15:
Slika 15: Zapisi v tabeli PREDMET po spremenjenih diskriminator stolpcih ter imenih entitet
3.11.1.2 Strategija pridruženja (JOINED strategy)
Pri strategiji pridruţenja je vsaka entiteta v hierarhiji mapirana v svojo tabelo kot prikazuje
Slika 16. Korenska entiteta mapira v tabelo, ki definira primarni ključ in je uporabljen od vseh
tabel v hierarhiji kot tudi »discriminator« stolpec. Vsak podrazred je predstavljen v ločeni tabeli
ter vsebuje svoje atribute (nededovane od korenskega razreda) in pa primarni ključ, ki se nanaša
na primarni ključ korenske tabele. Nekorenske tabele ne vsebujejo »discriminator« stolpca. Če
ţelimo implementirati strategijo pridruţenja, to storimo tako, da označimo korensko entiteto z
@Inheritance anotacijo kot prikazuje izsek kode spodaj. Entiteti CD ter Knjiga ostajata
nespremenjeni, torej isti kot v prejšnjem primeru [1].
58
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Predmet {
@Id @GeneratedValue
protected Long id;
protected String naslov;
protected Float cena;
protected String opis;
// Constructors, getters, setters
}
Iz pogleda razvijalca ima strategija pridruţenja zelo naraven pristop, saj je vsaka entiteta, pa
naj si bo abstraktna ali konkretna, mapirana v svojo tabelo v podatkovni bazi.
Slika 16: Mapiranje dedovanja z uporabo strategije pridruţenja
Tudi tukaj lahko uporabimo anotacije @DiscriminatorColumn ter @DiscriminatorValue v
korenski entiteti za ponastavitev opisa ter vrednosti »discriminator« stolpca. Strategija
pridruţenja je inovativna in blizu temu, kar je razvijalcem znano kot objektno dedovanje. Vendar
ima lahko performančni vpliv, kadar je uporabljena ter povprašujemo po objektih. Ta strategija
je poimenovana pridruţena, saj mora razdruţiti entitete po različnih tabelah in jih pri
povpraševanju zopet ustrezno zdruţiti in pravilno instancirati objekte po hierarhiji. Globja je
hierarhija več pridruţevanj moramo uporabiti, da bi pridruţili neko entiteto na listu (koncu
hierarhije) [1].
3.11.1.3 Strategija tabela na konkreten razred (TABLE PER CLASS strategy)
V strategiji tabela na konkreten razred je vsaka entiteta mapirana v svojo pripadajočo tabelo
kot pri strategiji pridruţevanja. Razlika tukaj je ta, da bodo vsi atributi korenskega razreda prav
tako mapirani v stolpce tabele otroka. Z vidika podatkovne baze ta strategija denormalizira
podatkovni model, saj povzroči, da so vsi atributi korenske entitete ponovno definirani v tabelah,
ki pripadajo listnim entitetam, ki dedujejo iz korenske. S to strategijo nimamo nobenih deljenih
tabel, deljenih stolpcev ter discriminator stolpcev. Edina zahteva tukaj je, da imajo vse tabele v
skupni rabi primarni ključ, ki se ujema skozi vse tabele v hierarhiji [1].
59
Izsek iz kode:
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Predmet{
@Id @GeneratedValue
protected Long id;
protected String naslov;
protected Float cena;
protected String opis;
// Constructors, getters, setters
}
Slika 17 prikazuje tabele PREDMET, KNJIGA ter CD. Kot lahko vidimo, se v tabeli KNJIGA
ter CD podvajajo stolpci iz tabele PREDMET.
Slika 17: Tabeli KNJIGA ter CD s podvojenimi stolpci iz tabele PREDMET
Seveda so performance te strategije relativno dobre, saj je strategija podobna strategiji enojne
tabele in se povpraševanje tukaj navezuje na eno samo tabelo. Slaba stran je tudi, da je
polimorfično povpraševanje po hierarhiji razredov veliko draţje kot pri drugih strategijah. Npr.
iskanje vseh postavk, vključejoč CD-je ter knjige, zahteva UNION operacijo nad podatkovno
bazo, ta pa je lahko zelo draga, kadar imamo veliko količino podatkov [1].
3.11.2 Prepisovanje atributov
S strategijo tabela na konkreten razred se stolpci korenskega razreda v tabelah listov
podvojijo. Ohranijo pa enako ime. Ampak kaj narediti, kadar uporabljamo obstoječo podatkovno
bazo ter imajo stolpci v podatkovni bazi drugačno ime in bi ţeleli takšne tudi ohraniti. JPA v
takšnih primerih uporablja anotaciji @AttributeOverride ter @AttributeOverrides. Če ţelimo
preimenovati stolpce ID, NASLOV, OPIS v KNJIGA ter CD podatkovnih tablah, se koda entitete
60
Predmet ne rabi spremeniti. Vendar moramo poskrbeti za primerne anotacije nad entitetama CD
ter Knjiga [1].
@Entity
@AttributeOverrides({
@AttributeOverride(name = "id",
column = @Column(name = "cd_id")),
@AttributeOverride(name = "naslov",
column = @Column(name = "cd_naslov")),
@AttributeOverride(name = "opis",
column = @Column(name = "cd_opis"))
})
public class CD extends Predmet {
private String glasbeniZaložnik;
private Integer steviloCdjev;
private Float skupnaDolžina;
private String zvrst;
// Constructors, getters, setters
}
3.12 Upravljanje s trajnimi objekti
Na JPA lahko gledamo z dveh strani. Prvi aspekt je zmoţnost mapiranja objektov v relacijsko
podatkovno bazo. Nastavitve izjem omogočajo, da naredimo veliko dela brez pisanja veliko
kode, seveda pa je bogastvo JPA-ja ponastavljanje mapiranja ob uporabi anotacij ali XML
datotek. JPA ponuja širok spekter ponastavitev od preprostih, ki se nanašajo na stolpce do
zahtevnih, recimo dedovanja. Kot rezultat lahko mapiramo skoraj vsak objektni model v
relacijsko podatkovno bazo.
Drug aspekt JPA-ja je zmoţnost povpraševanja po teh trajnih podatkih, V JPA-ju je ta
osrednja storitev za manipuliranje instanc entitet v standardnem načinu upravljalec entitet (entity
manager). Ponuja standardno knjiţnico za ustvarjanje, iskanje, odstranjevanje ter sinhroniziranje
objektov nad podatkovno bazo. Prav tako omogoča izvedbo različnih vrst JPQL povpraševalnih
stavkov nad entitetami, kot so statični, dinamični ali popolnoma normalni SQL stavki ter
mehanizem zaklepanja. JPQL je jezik definiran znotraj JPA za povpraševanje nad entitetami,
shranjenimi v relacijski podatkovni bazi. JPQL sintaksa je podobna SQL-u, ampak v primerjavi s
SQL-om, ki dela nad tabelami podatkovne baze ta dela nad objekti.
61
JPQL ne vidi pripadajoče podatkovne baze ter se ne ukvarja z vrsticami in stolpci iz
podatkovnih tabel, vendar je njegovo poslanstvo delo z objekti ter njihovimi atributi. V skladu s
tem uporablja objekten pristop ter piko (.) za dostope do podatkov, kar je razvijalcem danes zelo
znan pristop [1].
3.12.1 Dostopanje do entitet
Na primeru spodaj vidimo entiteto Knjiga ter atribut Id, ki je označen kot primarni ključ ob
uporabi anotacije @Id.
@Entity
public class Knjiga {
@Id
private Long id;
private String naslov;
private Float cena;
private String opis;
private String isbn;
private Integer stStrani;
private Boolean ilustracije;
}
Entiteta Knjiga zdruţuje mapirane informacije. V tem primeru se bomo povečini posluţevali
privzetih nastavitev, zato bodo podatki shranjeni v tabelo KNJIGA ter tudi imena stolpcev se
bodo ujemala z imeni atributov. Uporabo shranjevanja bomo prikazali v ločenem main razredu,
ki uporablja javax.persistance.EntityManager vmesnik [1].
public class Main {
public static void main(String[] args) {
// 1-Kreiranje instance entitete Knjiga
Knjiga knjiga = new Knjiga();
knjiga.setId(1234L);
knjiga.setTitle("The Hitchhiker's Guide to the Galaxy");
knjiga.setPrice(12.5F);
knjiga.setDescription("Znanstvena fantastika");
knjiga.setIsbn("1-84023-742-2");
knjiga.setNbOfPage(354);
knjiga.setIllustrations(false);
// 2-Pridobivanje entitenega upravljalca ter transakcije
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("chapter04PU");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
// 3-Shranjevanje knjige v podatkovno bazo
tx.begin();
em.persist(knjiga);
tx.commit();
62
// 4-Poizvedovanje po knjigi glede na njen id
knjiga = em.find(Knjiga.class, 1234L);
System.out.println(knjiga);
em.close();
emf.close();
}
}
Razred main komunicira s pripadajočo podatkovno bazo preko upravljalca entitet, ki ponuja
cel set metod, ki nam omogočajo izvajanje operacij nad Knjiga enititeto. V ozadju pa se ta
sklicuje na JDBC gonilnik za komunikacijo s podatkovno bazo. Kadar pokličemo upravljalca
entitet, ponudnik trajnosti ustvari ter izvaja SQL stavke skozi pripadajoč JDBC gonilnik.
3.12.2 Upravljalec entitet
Upravljalec entitet je centralni vmesnik za interakcijo z entitetami. Upravlja s stanjem in
ţivljenjskim ciklom entitet ter se ukvarja s povpraševanjem po entitetah znotraj trajne vsebine
(persistance context). Odgovoren je za operacije CRUD (create, read, update, delete), ki se
izvajajo nad entitetami. Ima moţnost zaklepanja entitet, da bi le-te zaščitil pred konkurenčnim
dostopom. Za to uporablja strategije optimističnega in pesimističnega zaklepanja. Prav tako zna
povpraševati po objektih s pomočjo JPQL-a in pripadajočih kriterijev [1].
Kadar upravljalec entitet pridobi referenco na neko entiteto, rečemo, da je ta v stanju
upravljanja (managed). Do tega mesta smo entiteto kot POJO objekt obravnavali kot odklopljeno
(detached). Prednost JPA-ja je, da so lahko entitete uporabljene kot navadni objekti v različnih
slojih aplikacije, lahko pa postanejo upravljanje s strani upravljalca entitet, kadar potrebujemo
naloţiti ali vstaviti neke podatke v ali iz podatkovne baze. Ko je entiteta v stanju upravljanja,
lahko proţimo nad njo CRUD operacije in upravljalec entitet bo avtomatsko sinhroniziral stanje
entitete s podatkovno bazo. Kadar pa je entiteta odklopljena, postane navaden POJO in je lahko
uporabljena v kakšnem drugem sloju (npr. JSF prezentacijskem sloju) brez sinhronizacije
njenega stanja s podatkovno bazo [1].
API upravljalca entitet:
public interface EntityManager {
public EntityTransaction getTransaction();
public EntityManagerFactory getEntityManagerFactory();
public void close();
public boolean isOpen();
public void persist(Object entity);
public <T> T merge(T entity);
public void remove(Object entity);
63
public <T> T find(Class<T> entityClass, Object primaryKey);
public <T> T find(Class<T> entityClass, Object primaryKey,
LockModeType lockMode);
public <T> T find(Class<T> entityClass, Object primaryKey,
LockModeType lockMode, Map<String, Object> properties);
public <T> T getReference(Class<T> entityClass, Object primaryKey);
public void flush();
public void setFlushMode(FlushModeType flushMode);
public FlushModeType getFlushMode();
public void lock(Object entity, LockModeType lockMode);
public void lock(Object entity, LockModeType lockMode,
Map<String, Object> properties);
public void refresh(Object entity);
public void refresh(Object entity, LockModeType lockMode);
public void refresh(Object entity, LockModeType lockMode,
Map<String, Object> properties);
public void clear();
public void detach(Object entity);
public boolean contains(Object entity);
public Map<String, Object> getProperties();
public Set<String> getSupportedProperties();
public Query createQuery(String qlString);
public Query createQuery(QueryDefinition qdef);
public Query createNamedQuery(String name);
public Query createNativeQuery(String sqlString);
public Query createNativeQuery(String sqlString, Class resultClass);
public Query createNativeQuery(String sqlString,
String resultSetMapping);
public void joinTransaction();
public <T> T unwrap(Class<T> cls);
public Object getDelegate();
public QueryBuilder getQueryBuilder();
}
3.12.3 Trajna vsebina
Trajna vsebina (persistance context) je set upravljanih entitet v nekem danem času. To
pomeni, da lahko v trajni vsebini obstaja samo ena instanca z enako trajno identiteto naenkrat.
Npr. če obstaja v trajni vsebini entiteta Knjiga z id-jem 1234, se ob tem trenutku nobena druga
instanca knjige z istim id-jem ne more nahajati v tej trajni vsebini. Spremembe v podatkovni bazi
bodo vidne samo od entitet, ki so vključene v trajno vsebino in upravljane s strani upravljalca
vsebine. Upravljalec vsebine posodobi trajno vsebino, kadar uporabimo kakršno koli metodo iz
javax.persistance.EntityManager vmesnika. Na primer, kadar kličemo persist(), je entiteta
posredovana kot argument in bo dodana trajni vsebini, če ţe tam slučajno ne obstaja.
64
Podobno, kadar iščemo entiteto po nekem primarnem ključu, upravljalec entitet najprej
preveri, ali je entiteta ţe morda v trajni vsebini. Na trajno vsebino lahko gledamo tudi kot na
predpomnilnik na prvem nivoju (first-level cache). Je prostor, v katerem upravljalec entitet
kratkoročno hrani entitete, preden jih posodobi, zapiše ali izbriše iz podatkovne baze. Objekti
ţivijo v trajni vsebini tako dolgo, kolikor je dolga transakcija [1].
Nastavitve upravljalca entitet se veţejo na tovarno upravljalca entitet
(EntityManagerFactory), ki ga je ustvarila. Tovarna potrebuje trajno enoto (persistance unit), iz
katere lahko ustvari upravljalca entitet. Trajna enota diktira nastavitve za povezavo na
podatkovno bazo in seznam entitet, ki so lahko upravljane znotraj trajne vsebine. Trajna enota je
definirana znotraj persistence.xml datoteke. V našem primeru je njeno ime chapter04PU ter
definira set atributov [1]. Primer datoteke persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="chapter04PU" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>com.apress.javaee6.chapter04.Book</class>
<class>com.apress.javaee6.chapter04.Customer</class>
<class>com.apress.javaee6.chapter04.Address</class>
<properties>
<property name="eclipselink.target-database" value="DERBY"/>
<property name="eclipselink.jdbc.driver"
value="org.apache.derby.jdbc.ClientDriver"/>
<property name="eclipselink.jdbc.url"
value="jdbc:derby://localhost:1527/chapter04DB"/>
<property name="eclipselink.jdbc.user" value="APP"/>
<property name="eclipselink.jdbc.password" value="APP"/>
</properties>
</persistence-unit>
</persistence>
Trajna enota je most med trajno vsebino ter podatkovno bazo. Z ene strani vodi cel seznam
razredov, ki so lahko upravljane znotraj trajne vsebine in z druge strani skrbi za vse informacije
ter fizično povezavo na podatkovno bazo. V našem primeru se nahajamo v aplikacijsko
upravljanem okolju (application-managed enviroment), zato vidimo, da morajo transakcije biti
upravljane ročno (transaction-type="RESOURCE_LOCAL"). V okolju, upravljanem od vsebnika
(container-managed enviroment), bi ta xml datoteka definirala podatkovni vir (datasource)
namesto povezave na podatkovno bazo, kot vidimo tukaj, in tudi tip transkacij bi bil upravljan od
vsebnika ter bi zato morali uporabiti določilo JTA (transaction-type="JTA") [1].
65
3.12.4 API predpomnilnik
Veliko specifikacij se zelo osredotoča na funkcijske zahteve, ob tem pa puščajo nefunkcijske
zadeve, kot so odzivnost, razširljivost ali povezovanje v grozde kot implementacijske detalje.
Implementacija mora dosledno slediti specifikacijam, ampak mnoge od njih dodajo zraven tudi
specifične značilnosti. Lep primer tega je JPA predpomnilnik. Vse do JPA 2.0 predpomnjenja ni
bilo omenjeno v specifikacijah. Upravljalec entitet je predpomnilnik na prvem nivoju, ki
obravnava podatke na celovit način za podatkovno bazo ter skrbi za kratko predpomnjenje ţivih
entitet. Predpomnjenje na prvem nivoju je bazirano na transakcijo, da zreducira število SQL
povpraševanj znotraj dane transakcije. Na primer, če je objekt spremenjen večkrat znotraj ene
transakcije, bo upravljalec entitet generiral samo en UPDATE stavek na koncu transakcije.
Pomnjenje na prvem nivoju ni performančni predpomnilnik. Vse implementacije JPA-ja pa
uporabljajo še performančen pomnilnik (pomnilnik na drugem nivoju a.k.a. second-level cache),
da optimizirajo dostop do podatkovne baze, povpraševanja, pridruţevanja itd. Pomnilnik na
drugem nivoju zmanjša promet na podatkovno bazo, saj zadrţuje v pomnilniku objekte na voljo
celi aplikaciji. Vsaka implementacija ima svoj način predpomnjenja podatkov, pa naj si bo to
svoje razvit mehanizem ali uporaba ţe obstoječe odprtokodne izvedbe. Z JPA 2.0 so pri Sunu
oznanili, da je predpomnjenje na drugem nivoju pomembno in dodali tudi to kot del standardne
API knjiţnice. Knjiţnica, prikazana spodaj, je minimalistična, saj namen uporabe
predpomnilnika ni standardiziranje polne funkcionalnosti le-tega, ampak zagotoviti
povpraševanje in odstranjevanje entitet iz predpomnilnika na drugem nivoju. Kot upravljalec
entitet je tudi javax.persistance.Cache vmesnik implementiran s strani ponudnika mehanizma za
zagotavljanje trajnosti [1].
public interface Cache {
// Vrne true kadar cache vsebuje podatke dane Entitete.
public boolean contains(Class cls, Object primaryKey);
// Odstrani podatke dane entitete iz cache-a.
public void evict(Class cls, Object primaryKey);
// Odstrani podatke specifičnega razreda iz cache-a.
public void evict(Class cls);
// Izbriše cache.
public void evictAll();
}
66
3.13 JPQL
Iz predhodnih primerov je ţe bilo razvidno, kako povpraševati po nekih objektih ob uporabi
upravljalca entitet. Vendar pa je ob uporabi metode find() ter posredovanega primarnega ključa
kot argument metode to precej omejujoča stvar. V praksi se nam pogosto zgodi, da potrebujemo
povpraševanje po imenih, naslovih, številkah itd. Ţal bi vse to s samim upravljalcem entitet
teţko dosegli. Zato JPA podpira povpraševalni jezik JPQL, ki obogati enostavna povpraševanja
[1].
JPQL je povpraševalni jezik, ki omogoča povpraševanja neodvisno od podatkovne baze, ki
leţi pod neko aplikacijo. Njegove korenine segajo v sintakso SQL-a (Standard query language),
ki je standardni jezik za povpraševanja iz podatkovne baze. Vendar je glavna razlika, da SQL
vrne rezulate v obliki vrstic ter stolpcev, medtem ko JPQL uporablja entitete oz. kolekcije entitet.
JPQL sintaksa je objektno orientirana in tako laţja za razumevanje razvijalcem, katerih izkušnje
so omejene na objektne pristope. Pod krinko JPQL uporablja mehanizem za mapiranje za
transformacijo JPQL povpraševanja v jezik, skladen s SQL podatkovno bazo. Povpraševanje je
izvedeno nad podatkovno bazo s SQL ter JDBC klici. Po vrnjenih rezultatih mehanizem
ponudnika trajnosti zna zagotoviti ustrezno instanciranje ter polnjenje objektov preko set metod.
Tako dobimo objekt [1].
Primer preprostega povpraševanja, kjer se razvijalec sklicuje na atribut naslov entitete Knjiga:
SELECT k FROM Knjiga k WHERE k.naslov = "H2G2"
3.13.1 SELECT
Select stavek sledi sintaksi potiizrazov, rezultat tega pa je eden izmed naslednjih form:
entiteta, atribut entitete, konstruktor izraz, agregatna funkcija ali sekvenca le-teh [1]. Pot izrazov
so povezani bloki povpraševanj in so namenjeni za navigiranje skozi entitetne atribute ali
entitetne relacije s pomočjo pike (.). Odsek spodaj prikazuje sintakso za SELECT stavek:
SELECT <select izraz>
FROM <from stavek>
[WHERE <pogojni izraz>]
[ORDER BY <order by stavek>]
[GROUP BY <group by stavek>]
[HAVING <having stavek>
67
Primeri razičnih select stavkov ter vrnjenih vrednosti. Primer vračanje entitete:
SELECT c FROM Customer c
Primer vračanja atributa:
SELECT c.firstName FROM Customer c
Če ima neka stranka (Customer) ena-ena relacijo z entiteto naslov (Address), lahko vrnemo
cel seznam naslovov z naslednjim select stavkom:
SELECT s.naslov FROM Stranka s
Tudi konstruktor je lahko uporaben v select stavku. Spodnji primer prikazuje instanciranje
javanskega razreda z rezultati, ki jih vrne neko povpraševanje. Ta instanciran razred ne rabi biti
entiteta, vendar mora konstruktor primerno poskrbeti za popolno ujemanje atributov.
SELECT NEW com.apress.javaee6.StrankaDTO(s.firstName, s.lastName,
s.naslov.ulica1)
Moţno je uporabiti tudi funkcije v samem povpraševanju. Primer spodaj prikazuje funkcijo
DISTINCT, ki vrne sama različna imena iz tabele STRANKA:
SELECT DISTINCT s.ime FROM Stranka s
3.13.2 FROM
From stavek definira entiteto z deklaracijo identifikacijskih spremenljivk. Identifikacijska
spremenljivka ali alias je identifikator, ki je lahko v uporabi tudi z drugimi stavki (select,where
... ) Sintaksa from stavka vsebuje entiteto ter alias [1].
SELECT s FROM Stranka s
68
3.13.3 WHERE
Where stavek nekega povpraševanja vsebuje pogojni izraz za omejitev rezultatev. Where
stavek je lahko preprost izraz ali set pogojnih izrazev za izločanje rezultatev na prefinjen način.
Najpreprostejši način uporabe je z uporabo atributa neke entitete. Primer spodaj prikazuje, kako
kot rezultat dobiti vse uporabnike z imenom Vincent [1].
SELECT s FROM Stranka s WHERE s.ime = 'Vincent'
Seveda pa lahko omejitve tudi kombiniramo med seboj z logičnimi operatorji AND ter OR.
Spodnji primer prikazuje, kako dobiti vse stranke z imenom Vincent, ki ţivijo v Franciji:
SELECT s FROM Stranka s
WHERE s.ime = 'Vincent' AND s.naslov.država = 'France'
Where stavek pa nam ponuja še cel nabor primerjalnih operatorjev, ki so lahko z njim
uporabljeni. Ti operatorji so:
=, >, >=, <, <=, <>, [NOT] BETWEEN, [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT]
EMPTY, [NOT] MEMBER [OF]
3.13.4 Vgnezdeni stavki
Vgnezden stavek je stavek, ki je vgrajen znotraj pogojnih izrazov WHERE ali HAVING.
Rezultati vgnezdenega stavka se prenesejo v pogoj glavnega stavka. Primer pridobivanje
najmlajše stranke iz podatkovne baze prikazuje naslednji izsek iz kode:
SELECT s
FROM Stranka s
WHERE s.starost = (SELECT MIN(s.starost) FROM Stranka s)
69
3.13.5 ORDER BY
Ta stavek omogoča entitetam oz. vrednostim, ki so vrnjene, da so razporejene po nekem
vrstnem redu. Razvrščanje lahko poteka po naraščojočem ali padajočem vrstnem redu ob
souporabi ključnih besed DESC ter ASC. DESC nam razvrsti po padajočem vrstnem redu ter ASC
po padajočem vrstnem redu. ACS je privzeta vrednost in jo lahko pri pisanju stavka, kadar
ţelimo naraščojoči vrstni red, izpustimo [1]. Primer spodaj prikazuje, kako dobiti rezultate vseh
strank, katerih starost je večja od 18 let, vrstni red rezultatov pa je razvrščen po padajočem
pravilu:
SELECT s FROM Stranka s WHERE s.starost > 18 ORDER BY s.starost DESC
3.13.6 GROUP BY ter HAVING
Group by konstrukt omogoča agregacijo rezulatatov v skladu s podanimi zahtevami. Entitete
so razporejene v skupine, temelječ na vrednostih entitetnih atributov, podanih v group by stavku
[1]. Primer prikazuje, kako dobiti število strank, zdruţenih v neko skupino po kriteriju, da so le-
te iz iste drţave:
SELECT s.naslov.država, count(s) FROM Stranka s GROUP BY s.naslov.država
Having stavek definira pripadajoč filter za rezulatate ţe po narejenem zdruţevanju. Če
uporabimo prejšnji primer ter dodamo having stavek, bomo dobili samo tista imena drţav ter
števila strank, kjer je število strank večjih od 100:
SELECT s.naslov.država, count(s) FROM Stranka s
GROUP BY s.naslov.država HAVING count(s) > 100
70
3.13.7 Bulk DELETE
Kadar ţelimo izbrisati neko entiteto, je to mogoče na dva načina. Prvi je, da uporabimo
upravljalca entitet ter njegovo metodo remove(). Slabost tega pristopa je, da se moramo na
entitete sklicovati po id-ju. Torej kadar ţelimo izbrisati neke entitete, ki imajo ime Vincent,
moramo najprej vse pridobiti v pomnilnik, nato preverjati z logiko, ali ustrezajo kriteriju
brisanja, in jih individualno brisati s pomočjo metode remove() ter podanega id-ja. Seveda pa to
ni najboljši pristop, saj zahteva veliko klicev na podatkovno bazo. Obstaja boljši pristop tega,
imenujemo ga »Bulk DELETE«. JPQL omogoča tej operaciji, da izbriše nek skupek entitet z eno
samo operacijo. Delete stavek izgleda podobno kot select stavek, saj lahko prav tako vsebuje
where pogoje ter parametre [1]. Sintaksa delete stavka je sledeča:
DELETE FROM <ime entitete> [[AS] <identifikacijska spremenljivka>]
[WHERE <pogojni izraz>]
Primer spodaj prikazuje, kako izbrisati vse stranke, mlajše od 18 let, v eni sami operacji s
pomočjo DELETE stavka:
DELETE FROM Stranka s WHERE s.starost < 18
3.13.8 Bulk UPDATE
Bulk UPDATE nam omogoči posodobitev enega ali več atributov v podatkovni bazi nad eno
ali več entitetami. Tudi tukaj bi za posodobljanje podatkov lahko uporabili upravljalca entitet,
vendar se zaradi istega razloga kot pri DELETE stavku raje posluţujemo posodobljanja s
pomočjo JPQL-a [1]. Sintaksa update stavka je sledeča:
UPDATE <entity name> [[AS] <identifikacijska spremenljivka>]
SET <update stavek> {, <update stavek>}*
[WHERE <pogojni izraz>]
Poglejmo še primer posodobljanja imena strank. Imena vseh strank, katerih starost je manjša
od 18 let, bodo posodobljena v 'Premlad'.
UPDATE Starost s
SET s.ime = 'Premlad'
WHERE c.starost < 18
71
3.13.9 Povpraševanja
JPA 2.0 podpira štiri različne tipe povpraševanj, ki so lahko uporabljeni v kodi, vsak seveda s
svojim namenom:
Dinamična povpraševanja – so preprosta povpraševanja, sestavljena iz JPQL
povpraševanj, definirana dinamično v času izvajanja.
Poimenovani povpraševalni stavki – so statični in v času izvajanja ne morejo biti
spremenjeni.
Navadna povpraševanja – omogočajo uporabo navadnega SQL povpraševalnega
jezika namesto JPQL povpraševanj.
Criteria API – to je popolnoma nov koncept znotraj JPA 2.0 in ga bom podrobneje
predstavil ob uporabi Hibernate v naslednjem večjem poglavju [1].
3.13.9.1 Dinamična povpraševanja
Dinamična povpraševanja so dokončno definirana v času izvajanja aplikacije. Kadar ţelimo
uporabiti dinamična povpraševanja, uporabimo metodo EntityManager.createQuery(), ki
sprejme kot argument String, ki predstavlja JPQL povpraševanje [1]. Primer spodaj prikazuje
dinamično povpraševanje ter kako v času izvajanja glede na nek kriterij prilepimo fiksnemu delu
niza, ki predstavlja JPQL stavek, še določen pogoj:
String jpqlQuery = "SELECT s FROM Stranka s";
if (nekPogoj)
jpqlQuery += " WHERE s.ime = 'Vincent'";
query = em.createQuery(jpqlQuery);
List<Customer> customers = query.getResultList();
Dinamična povpraševanja pa omogočajo tudi povpraševanja ob uporabi posredovanja
parametrov. To lahko storimo na dva načina, in sicer:
parameter posredujemo po imenu in
parameter posredujemo po poziciji.
72
Primera prikazujeta obe moţni izvedbi:
Po imenu:
jpqlQuery = "SELECT s FROM Stranka s";
if (someCriteria)
jpqlQuery += " where s.ime = :ime";
query = em.createQuery(jpqlQuery);
query.setParameter("ime", "Vincent");
List<Stranka> stranke = query.getResultList();
Glede na pozicijo:
jpqlQuery = "SELECT s FROM Stranka s";
if (someCriteria)
jpqlQuery += " where s.ime = ?1";
query = em.createQuery(jpqlQuery);
query.setParameter(1, "Vincent");
List<Stranka> stranke = query.getResultList();
3.13.9.2 Poimenovana povpraševanja
Se od dinamičnih razlikujejo v tem, da so statična in v času izvajanja ne morejo biti
spremenjena. Dodana vrednost k poimenovanim povpraševanjem pa je zagotovo njihova statična
usmerjenost, saj to pomeni, da jih za razliko od dinamičnih povpraševanj ponudnik trajnosti
prevede le enkrat in to je ob zagonu aplikacije, kar je vsekakor bolj učinkovito kot vsakič, ko se
povpraševalni stavek izvede. Poimenovana povpraševanja doseţemo tako, da povpraševalni
stavek označimo z @NamedQuery anotacijo ali @NamedQuerys, kadar gre za cel set
povpraševalnih stavkov [1]. Definicija slednjih prikazuje spodnji izsek:
@Entity
@NamedQueries({
@NamedQuery(name = "poisciVse", query="select s from Stranka s"),
@NamedQuery(name = "poisciVincenta",
query="select s from Stranka s where s.ime = 'Vincent'")
@NamedQuery(name = "poisciZParametri",
query="select s from Stranka s where s.ime = :ime")
})
public class Stranka {
@Id @GeneratedValue
private Long id;
private String ime;
private String priimek;
private Integer starost;
private String email;
@OneToOne
@JoinColumn(name = "naslov_fk")
private Naslov naslov;
// Constructors, getters, setters
}
73
In še primeri uporabe poimenovanih povpraševalnih stavkov:
Query query = em.createNamedQuery("posiciVse");
List<Stranka> stranke = query.getResultList();
Query query = em.createNamedQuery("poisciZParametri");
query.setParameter("ime", "Vincent");
query.setMaxResults(3);
List<Stranka> stranke = query.getResultList();
3.13.9.3 Navadna povpraševanja
JPQL ima zelo bogato sintakso, ki nam omogoča upravljanje entitet v zelo prenosnem načinu.
JPA pa omogoča tudi uporabo navadnih SQL povpraševanj z uporabo SQL stavkov. Seveda pa
ne moremo pričakovati, da bodo navadna SQL povpraševanja prenosna – torej delovala povsod
skozi vse vrste podatkovnih baz. Če ţelimo uporabiti navadna povpraševanja, uporabimo metodo
EntityManager.createNativeQuery(), ki kot argument sprejme navaden SQL stavek [1]. Slednje
prikazuje spodnji izsek:
Query query = em.createNativeQuery("SELECT * FROM t_stranka",
Stranka.class);
List<Stranka> stranke = query.getResultList();
Kot lahko vidimo, izsek iz kode prikazuje, da je SQL niz navaden String ter je lahko
dinamično ustvarjen v času izvajanja kot pri JPQL povpraševalnih stavkih. Seveda je lahko zato
koda povpraševalnega stavka zelo kompleksna. Iz tega namena ponudnik trajnosti ne more
predvideti vnaprej kakšna bo, zatorej se posluţuje tega, da jo vsakič znova prevaja.
3.14 Vzporednost
Pri JPQL povpraševanjih povprašujemo po podatkih znotraj nekih kriterijev, medtem ko
aplikacija teče v neki grozdni razporeditvi, z večimi vozlišči, večimi nitmi ter eno samo
podatkovno bazo. Iz omenjenega je razvido, da je zelo velika verjetnost, da bomo do entitet
dostopali vzporedno. Ko omenjamo vzporednost, moramo poskrbeti za sinhronizacijo. To lahko
priskrbimo z uporabo mehanizmov zaklepanja [1]. Slika 18 prikazuje moţnost vzporednega
dostopa do iste entitete.
74
Slika 18: Transakciji tx1 ter tx2 vzporedno posodabljata ceno knjige.
Viden problem vzporednosti, kjer je zmagovalec tisti, ki je zadnji pognal »commit«, ni
specifičen za uporabo v JPA. Podatkovne baze se morajo soočiti z danimi teţavami ter so do
danes našle različne rešitve izoliranja transakcij ene od druge. En skupen mehanizem za
reševanje tega je zaklepanje vrstice, nad katero se izvaja SQL stavek. Obstajata dve vrsti, ki jih
uporablja JPA 2.0. (Verzija 1.0 je podpirala samo optimistično zaklepanje.) [1]:
optimistično zaklepanje,
pesimistično zaklepanje.
75
3.14.1 Verzioniranje
JPA uporablja mehanizem za povečevanje verzije entitete, kadar je to potrebno. Torej ko
shranimo neko entiteto v podatkovno bazo, se v atribut version, ki ga prej definiramo v entiteti
ter označimo z anoatcijo @Version, nastavi vrednost 1. Kadar to entiteto posodabljamo in
transakcija v njo zapiše nove vrednosti, se verzija poveča za 1. Tip stolpca v tabeli za podporo
verzijam je lahko int, Integer, short, Short, long, Long, ali Timestamp [1]. Izsek iz kode prikazuje
verzioniranje:
@Entity
public class Knjiga {
@Id @GeneratedValue
private Long id;
@Version
private Integer verzija;
private String naslov;
private Float cena;
private String opis;
private String isbn;
private Integer stStrani;
private Boolean ilustracije;
// Constructors, getters, setters
}
Do atributa verzija v entiteti sicer lahko dostopamo, vendar pa mu ne moremo spremeniti
vrednosti. To lahko stori le ponudnik trajnosti ob zapisu ali posodobitvi entitete v podatkovno
bazo.
3.14.2 Optimistično zaklepanje
Kot ţe ime pove, optimistično zaklepanje temelji na dejstvu, da transakcije na podatkovni
bazi niso med sabo konfliktne. Z drugimi besedami obstaja velika moţnost, da bo transakcija, ki
posodoblja entiteto edina, ki dejansko posodablja to vrednost v nekem časovnem intervalu. Tako
je odločitev za pridobitev zaklepanja nad entiteto v skladu s trenutnim stanjem podatkovne baze.
Uporaba anotacije @Version v entiteti omogoča upravljalcu entitet, da izvede optimistično
zaklepanje preprosto s primerjavo vrednosti atributa verzija s stolpcem v podatkovni bazi. Brez
te anotacije upravljalec entite ne more podpreti optimističnega zaklepanja. Slika 19 prikazuje
konflikt brez uporabe optimističnega zaklepanja [1].
76
Slika 19: OptimisticLockException izjema nad transakcijo tx2
Primer izseka iz kode pa nakazuje rešitev iz primera Slika 19 z uporabo naslednje kode:
Knjiga knjiga = em.find(Knjiga.class, 12);
// Lock to raise the price
em.lock(knjiga, LockModeType.OPTIMISTIC);
knjiga.zvišajCenoZaDvaEura();
3.14.3 Pesimistično zaklepanje
Temelji na predpostavki, da je zaklepanje nad entiteto narejeno preden se sploh lotimo
obdelave in posodobljanja atributov nad njo. Ta pristop je zelo nepriporočjiv, saj so rezultati ter
operacije v tem času, kot npr. preprosto branje entitete v tem času, degradirani. Vzemimo primer
ekonomske krize. Optimistično zaklepanje tukaj pride do izraza, saj borza dobiva dnevno zelo
veliko naročil za prodajo. Če mora 100 milijonov ljudi prodati svoje zaloge ob istem času, mora
sistem zagotoviti pesimistično zaklepanje, da ustreţe skladnosti podatkov. Pesimistično
zaklepanje je uporabljeno brez @Version anotacije [1].
77
4 HIBERNATE 3.0
Hibernate 3.0 je odprtokodna tehnologija za obstojnost podatkov. Hibernate 3.0 core je
68.549 vrstic kode skupaj s 27.948 vrsticami modulnih testov (unit tests). Dostopen je pod
LGPL (Lesser General Public License) prosto licenco. Hibernate je objektno-preslikovalni
mehanizem, ki preslika javanske razrede v tabele relacijske podatkovne baze. Prav tako ponuja
podporo za povpraševalne stavke ter orodja za povpraševanja po podatkih, ki znatno zmanjšajo
čas razvoja. Hibernate ni najboljša rešitev za podatkovno usmerjene aplikacije, ki uporabljajo
izključno shranjevalne procedure. Najbolj uporaben je v objektno orientiranih domenskih načinih
ter v poslovni logiki v srednjem sloju. Hibernate podpira transparentno trajnost, kar omogoča
aplikacijam preklop med katero koli podatkovno bazo. Lahko ga uporabimo v Java Swing
aplikacijah, aplikacijah temelječih na Java servletih ter J2EE aplikacijah ob uporabi EJB sejnih
zrn [6].
Osnovne značilnosti Hibernate 3.0 lako strnemo v sledeče:
Hibernate 3.0 podpira tri povpraševalne jezike:
Hibernate query language,
Hibernate criteria query API,
navaden SQL.
Okrepljena knjiţnica kriterijskega povpraševanja (Criteria query API) s polno podporo
projekcij, agregacij ter vgnezdenih povpraševalnih stavkov.
Podpora za Eclipse, vključujoč cel nabor eclipsovih pluginov za delo s Hibernateom,
prav tako vključuje mapirni urejevalnik ter orodje za povratno inţinerstvo za sheme.
Hibernate je prosto dostopen pod Lesser General Public License Velika skalabilnost,
kar pomeni, da je zelo performančen znotraj njegove dvoslojne arhitekture in je lahko
uporabljen v okoljih, ki zahtevajo grozdno porazdelitev (»clustered enviroments«) [6].
78
Krajši čas samega razvoja aplikacije: Hibernate skrajša čas razvoja za razvijalca s
podporo dedovanja, polimorfizma, kompozicije ter Java Collection frameworka.
Podpora za avtomatsko generiranje primarnih ključev.
EJB3 operacije za obstojnost podatkov: EJB 3 definira create() in merge() metodi, ki
sta pa malenkost drugačni od Hiberantovih saveOrUpdate() ter saveOrUpdateCopy().
Hibernate 3 pa podpira vse štiri operacije v Session vmesniku.
Hibernate povezovanje z XML dokumenti omogoča podatkom, da so predstavljeni
izmenično kot XML in POJO.
Podpora obstojnosti za POJO objekte ter anotacijam [6].
79
4.1 Hibernate arhitektura
Hibernate uporablja podatkovno bazo ter konfiguracijske podatke, ki omogočajo persistentno
storitev ter persistentne objekte neki aplikaciji kot prikazuje Slika 20.
Za uporabo Hibernatea je priporočljivo, da ustvarimo Java razred, ki reprezentira tabelo v
podatkovni zbirki, in nato poveţemo instanco spremenljivke tega razreda s stolpcem v
podatkovni tabeli. Če opravimo slednje, nam nato Hibernate ponuja operacije nad relacijsko
podatkovno bazo, kot so select, insert, update, in delete. Hibernate avtomatično kreira
povpraševalne stavke za te operacije [6].
Slika 20: Hibernate arhitektura [6]
80
Arhitektura Hibernatea ima tri glavne komponente:
1. Upravljanje povezav
Hibernate storitev za upravljanje povezav ponuja učinkovito upravljanje s povezovanjem
na podatkovno bazo. Povezovanje na podatkovno bazo je najdraţji del interakcije, saj zahteva
veliko virov za odpiranje in zapiranje povezave.
2. Upravljanje transakcij
Storitev upravljanje transakcij ponuja uporabniku moţnost izvajanja več kot enega
povpraševalnega stavka naenkrat.
3. Objektno-relacijsko mapiranje
Object-relational mapping je tehnika mapiranja podatkov iz objektnega modela v relacijski
podatkovni model. Ta del Hibernatea je zadolţen za izvajanje select, insert, update ter delete
operacije nad zapisi v neki pripadajoči tabeli. Ko vstavimo objekt v metodo Session.save(obj),
Hibernate prebere stanje spremenljivk tega objekta in izvede pripadajoč povpraševalni stavek.
Hibernate je zelo dobro orodje po konceptu objektno-relacijskega mapiranja, vendar kadar
govorimo o terminih upravljanja s povezavami in upravljanja transakcij, se ne odreţe ravno
najbolje [11].
Zato je ponavadi sam uporabljen z drugimi connection in transaction managment orodji. Na
primer apache DBCP se mnogokrat uporablja v kombinaciji s Hibernateom za povezovanje na
podatkovno bazo.
Hibernate ponuja mnogo fleksibilnosti pri sami uporabi. Imenuje se »Lite« arhitektura, kadar
uporabljamo samo komponento za objektno relacijsko mapiranje. Medtem ko v tako imenovani
»Full Cream« arhitekturi uporabljamo vse tri komponente (upravljanje s povezavami,
upravljanje transakcij in objektno-relacijskega mapiranja) Hibernateove arhitekture [6].
81
4.1.1 Razumevanje arhitekture
Vmesniki naj bi bili prva stvar, ki jo moramo poznati, kadar ţelimo delati aplikacije,
temelječe na Hibernate arhitekturi. Slika 21 prikazuje vloge najpomebnejših Hibernate
vmesnikov v poslovnem ter persistentnem sloju. Poslovni sloj je prikazan nad persistentnim
slojem, saj se poslovni sloj obnaša kot klient nad persistentnim v neki tradicionalni aplikaciji, ki
jo delimo na sloje [4].
Hibernateove vmesnike, ki jih prikazuje Slika 21 lahko klasificiramo sledeče:
Vmesnike, klicane od aplikacije za izvajanje osnovnih CRUD operacij ter
povpraševanj. Ti vmesniki so Session, Transaction in Query.
Vmensiki, klicani s strani infrastrukture aplikacije, namenjene za konfiguriranje
Hibernatea. Najpomebnejši tukaj je vmesnik Configuration.
Vmesnik Callback, ki omogoča, da aplikacija odreagira na dogodke, ki nastanejo
znotraj Hibernatea, kot so Interceptor, Lifecycle ter Validatable.
Vmesniki, ki omogočajo razširitev Hibernateove mapirne funkcionalnosti, kot so
UserType, CompositeUserType ter IdentifierGenerator.
Hibernate dovoljuje tudi uporabo obstoječih javanskih knjiţnic, vključojoč JDBC (Java
database conectivity), JTA (Java transaction manager) ter JNDI (Java naming directory
interface). JDBC ponuja osnovno raven abstrakcije funkcionalnosti, skupne vsem relacijskim
podatkovnim bazam, kar pomeni, da omogoča skoraj vsaki podatkovni bazi z JDBC driverjem,
da je uporabljena skupaj s Hibernateom. JNDI ter JTA pa omogočata integracijo Hibernatea z
J2EE aplikacijskimi streţniki [4].
82
Slika 21: Pregled Hibernate API, razdeljene po slojih [4]
4.1.1.1 Vmesniki, vezani na jedro
Obstaja pet vmesnikov, ki so vezani na jedro ter se uporabljajo prav v vsaki Hibernate
aplikaciji:
vmesnik Session;
vmesnik Session Factory;
vmesnik Configuration;
vmesnik Transaction,
Query in Criteria vmesnik.
Uporaba teh vmesnikov omogoča shranjevanje ter povpraševanje po persistentnih objektih ter
nadzor nad transakcijami.
83
Vmesnik Session je glavni vmesnik, uporabljen s strani aplikacij, zgrajenih s Hibernateom.
Instanca Sessiona predstavlja lahek objekt ter s tega stališča ni predraga za kreiranje ter brisanje.
To je pomebno, saj aplikacije kreirajo ter brišejo instanco Sessiona velikokrat. Instanca Sessiona
pa ţal ni »thread safe«, kar pomeni, da jo lahko uporablja le ena nit naenkrat. Na Session lahko
gledamo kot na nek predpomnilnik ali kolekcijo naloţenih objektov. Včasih Sessionu rečemo
tudi upravljalec persistentnih objektov, ker je prav tako vmesnik za persistentne operacije, kot so
pridobivanje ter shranjevanje objektov.
Aplikacija pridobiva instance Sessiona ravno iz SessionFactoryija. Če ta vmesnik primerjam z
vmesnikom Session, je ta mnogo manj zanimiv ter njegova instanca vsekakor ne predstavlja
lahkega objekta. Namenjen je temu, da njegovo instanco delimo preko mnogih niti, ki ţivijo v
aplikaciji. Tipično je, da obstaja ena sama instanca SessionFactoryija v celem sistemu kreirana
ob inicializaciji aplikacije. Vendar če bi aplikacija uporabljala več različnih podatkovnih baz, bi
za vsako od njih potrebovali svoj SessionFactory.
Aplikacije uporabljajo Configuration instanco za specificiranje lokacije XML dokumenta, v
katerem so zapisana mapiranja in Hiberanteove specifične nastavitve. S pomočjo te instance
kreirajo SessionFactory. Čeprav Configuration vmesnik igra precej malo vlogo v celotnem
spektru Hibernateove aplikacije, je to vmesnik, s katerim se srečamo najprej.
Transaction vmesnik je opcijski API. Pri načrtovanju Hibernateovih aplikacij se lahko
odločimo, da tega ne bomo uporabljali. Transaction vmesnik abstrakcira aplikacijsko kodo od
pripadajoče implementacije transakcij, ki je lahko JDBC transakcija, JTA transakcija ali celo
CORBA transakcija, vse te pa omogočajo aplikaciji, da kontrolira meje aplikacije s pomočjo
ustreznega API-ja. To pripomore, da so Hibernate aplikacije prenosne med različnimi
izvajalnimi okolji ter vsebniki.
Vmesnik Query omogoča, da izvajamo povpraševanja nad podatkovno bazo ter nadziramo
izvajanje povpraševalnega stavka. Povpraševanja so lahko v HQL-u ali SQL dialektu pripadajoče
podatkovne baze. Instanca Queryija je namenjena temu, da se poveţe s povpraševalnimi
parametri, omeji število vrnjenih rezultatov in na koncu izvede povpraševalni stavek.
Vmesnik Crtireria je zelo podoben, le da omogoča kreiranje ter izvajanje objektno-
orientiranih kriterijskih povpraševalnih stavkov [4].
84
4.2 Hibernate povpraševalni jezik (HQL)
HQL (Hibernate query language) je zelo močan in efektiven povpraševalni jezik. Hibernate je
podoben SQL-u ter je kot sam jezik case-insensitive (razen za imena Javanskih razredov ter
njegovih atributov), kar pomeni, da mu uporaba mešanosti velikih in malih črk v samem
povpraševalnem stavku ni pomembna. HQL uporabljamo za izvajanje povpraševanj nad
podatkovno bazo. Kadar uporabimo HQL povpraševalni stavek, Hibernate avtomatsko generira
SQL povpraševalni stavek, ter le-tega izvede nad pripadajočo podatkovno bazo. HQL za
povpraševanja uporablja razrede ter njegove spremenljivke namesto tabel in njenih stolpcev. Je
zelo močan povpraševalni jezik, saj podpira polimorfizem in asociacije veliko preprosteje kot
sam SQL.
S podporo Criteria API obstajajo tudi druge moţnosti ob uporabi Hibernate, kot so Query By
Criteria(QBC), Query By Example(QBE) ter Native SQL, vendar se bom v svojih primerih
osredotočil na podrobno razumevanje HQL-a [7].
Uporaba HQL:
Popolna podpora za relacijske operacije – HQL dovoljuje, da izvajamo SQL
povpraševalne stavke.
Vrnjeni rezultati so podatkovnega tipa Object – HQL povpraševalni stavki vrnejo
nek queryResult, v katerem so ţeljeni rezultati tipa Object. Ob ustrezni pretvorbi
(castu) v tip, po katerem smo povpraševali, dobimo ţeljene objekte.
Polimorfna povpraševanja – še ena odlična značilnost HQL so polimorfna
povpraševanja. Le-ta omogočajo, da so vrnjeni rezultati neki objekti skupaj z vsemi
svojimi otroki (child), če je le-to mogoče.
Preprost za učenje – je preprost za učenje ter ga je mogoče enostavno vgraditi v
aplikacije.
85
Podpora za napredne zahteve – HQL zdruţuje mnoge napredne značilnosti, kot so
pagination – kar pomeni, da lahko zahtevamo nek rang rezulatov, npr. deset vrnjenih
objektov, pričetih npr. na petnajstem elementu. Popolno podporo ima tudi za
inner/outer join – zdruţevanja, kartezijski produkt, projekcije, agregacije (min,max),
grupiranja, razvrščanja, vgnezdene povpraševalne stavke ter SQL klice funkcij.
Neodvisnost od podatkovne baze – HQL je neodvisen od podatkovne baze, kadar
seveda podatkovna baza to podpira (underlying feauture).
Vsak Hibernate povpraševalni stavek lahko vključuje naslednje:
stavke (clauses);
agregatne funkcije;
vgnezdene povpraševalne stavke.
Stavki v HQL-u so:
From;
Select;
Where;
order by, group by.
Agregatne funkcije v HQL-u so:
avg(...), sum(...), min(...), max(...);
count(*);
count(...), count(distinct...), count(all...).
Vgnezdeni povpraševalni stavki:
Vgnezdeni povpraševalni stavki niso nič drugega kot en povpraševalni stavek znotraj
drugega. Hibernate podpira vgnezdene povpraševalne stavke, če le-te podpira tudi
podatkovna baza, s katero komunicira [12].
86
4.3 Hibernate Criteria Query
Vmesnik Criteria omogoča objektno-orientirana povpraševanja. Je odlična alternativa HQL-
u, a ima svoje omejitve. Criteria Query je uporabljen večinoma v primeru multipovpraševanj,
kjer HQL ni tako efektiven. Vmesnik hibernate.org.Criteria je namenjen kreiranju kriterijev za
povpraševanja. Session, ki sem ga omenjal v prejšnjem poglavju, pa je neke vrste kreator za
instanciranje Criteria. Izsek iz kode spodaj prikazuje preprost primer Hibernate povpraševanja s
pomočjo kriterijev.
//Criteria Query primer
Criteria crit = session.createCriteria(Insurance.class);
List zavarovanja = crit.list();
for(Iterator it = zavarovanja.iterator();it.hasNext();){
Zavarovanje zavarovanje = (Zavarovanje) it.next();
System.out.println("ID: " + zavarovanje.getId());
System.out.println("Ime zavarovanja: " + zavarovanje.getImeZavarovanja());
}
Iz primera je razvidno, da je edini kriterij, ki smo ga podali tukaj, ta, da ţelimo objekte tipa
Zavarovanje. Vrstica List insurances = crit.list(); kreira SQL povpraševanje in le-to poţene nad
podatkovno bazo.
Rezultat so vsi zapisi v podatkovni tabeli ZAVAROVANJE [4].
87
4.3.1 Vmesnik Criteria ter razred Restrictions
V prejšnjem primeru sem prikazal povpraševanje s pomočjo Criteria Query, brez uporabe
kakršnih koli posebnih kriterijev. V Tabela 3 ter Tabela 4 pa prikazujem metode, ki jih podpira
vmesnik Criteria za kreiranje povpraševanj ter razred Restrictions, ki ponuja vgrajene kriterije
preko njegovih statičnih factory metod [4].
Tabela 3: Vmesnik Criteria
Metoda Opis
Add Metoda doda kriterij, da omeji rezultate, ki jih
zahteva iz podatkovne baze.
AddOrder Doda razvrščanje nad rezultati.
CreateAlias Doda asociacijo z dodelitvijo aliasa določeni tabeli.
CreateCriteria Metoda, namenjena kreiranju novega objekta
Criteria, baziranega na pripadajočo entiteto.
SetFetchSize Metoda je namenjena nastavljanju števila vrstic, ki
jih vrne pod Hibernateom JDBC query. V končni fazi
so vrnjene vse vrstice. Št. vrstic, ki jih vrne z
nastavitvijo tega atributa, je namenjeno samo
hitrejšemu nalaganju.
SetFirstResult Metoda je uporabljena, kadar ţelimo dobiti rezultate
od nekega mesta dalje.
SetMaxResults Metoda nastavi maksimalno število rezultatev, ki jih
povpraševalni stavek vrne.
uniqueResult
Metoda pove Hibernateu, da vrne samo unikatne
zapise iz podatkovne baze.
88
Tabela 4: Razred Restrictions
Metoda Opis
Restrictions.allEq
Vse vrednosti v mapi podatkovne strukture, ki jo podamo
kot parameter, morajo biti enake zahtevanemu zadetku iz
podatkovne baze.
Restrictions.between
Vrednosti, da rezultati ustrezajo pogojem, morajo biti med
podanima parametroma za določen atribut.
Restrictions.eq
Vrednost rezultatov v zadetkih mora imeti enako vrednost,
kot je podana v parametru za določen atribut.
Restrictions.ge
Vrednost rezultatov v zadetkih mora imeti vrednost večjo
ali enako, kot je podana v parametru za določen atribut.
Restrictions.gt
Vrednost rezultatov v zadetkih mora imeti vrednost večjo,
kot je podana v parametru za določen atribut.
Restrictions.idEq Vrednost rezultatov v zadetkih mora imeti enako vrednost,
kot je podana v parametru za identifikator (id).
Restrictions.ilike
Vrednost rezultatov v zadetkih mora vsebovati niz, kot je
podan parameter. Velike in male črke niso pomembne
(case-insensitive).
Restrictions.in Vrednost rezultatov v zadetkih mora biti v rangu, kot je
podan parameter.
Restrictions.isNotNull Vrednost rezultatov v zadetkih mora vsebovati rezultate, ki
niso null za podan parameter.
Restrictions.isNull Vrednost rezultatov v zadetkih mora vsebovati rezultate, ki
so null za podan parameter.
Restrictions.le Vrednost rezultatov v zadetkih mora imeti vrednost manjšo
ali enako, kot je podana v parametru za določen atribut.
Restrictions.like Vrednost rezultatov v zadetkih mora vsebovati niz, kot je
podan parameter. Velike in male črke so pomembne (case-
sensitive).
Restrictions.lt Vrednost rezultatov v zadetkih mora imeti vrednost manjšo,
kot je podana v parametru za določen atribut.
Restrictions.ltProperty Vrednost rezultatov v zadetkih mora imeti vrednost manjšo
za prvi atrbibut, kot je podana v parametru za drugi atribut.
Restrictions.ne Vrednost rezultatov v zadetkih mora imeti vrednost
različno, kot je podana v parametru za določen atribut.
Restrictions.neProperty Vrednost rezultatov v zadetkih mora imeti vrednost različno
za prvi atrbibut, kot je podana v parametru za drugi atribut.
Restrictions.not Vrne negacijo izraza.
Restrictions.or Vrne disjunkcijo dveh izrazov.
89
V spodnjem primeru je prikazana uporaba Restrictions.like metode ter omejitev
maksimalnega števila zadetkov s pomočjo Criteria.setMaxResults(). Za primer sem uporabil
preprost POJO razred Zavarovanje.
Criteria crit = session.createCriteria(Zavarovanje.class);
crit.add(Restrictions.like("imeZavarovanja", "%Življensko%")); //Like pogoj
crit.setMaxResults(2); //omejitev rezultatov na maksimalno 2 vrstici
List zavarovanja = crit.list();
for(Iterator it = zavarovanja.iterator();it.hasNext();){
Zavaraovanje zavarovanje = (Zavarovanje) it.next();
System.out.println("ID: " + zavarovanje.getId());
System.out.println("Ime: " + zavarovanje.getImeZavarovanja());
}
Tabela 5: Dobljen rezultat ob uporabi Restrictions.like
ID Ime
2 Ţivljenjsko zavarovanje
3 Ţivljenjsko zavarovanje
Iz primera je razvidno, da je kljub temu, da imamo v podatkovni tabeli tri rezultate, ki so se
ujemali s podanim kriterijem, podanega z razredom Restrictions, Hibernate vrnil samo dva kot
prikazuje Tabela 5, saj sem metodi vmesnika Criteria setMaxResults() kot parameter poslal
vrednost 2.
90
4.4 Hibernate Native SQL
Hibernate podpira, da izvjamo znotraj aplikacije tudi običajna SQL povpraševanja. Tako
lahko ob morebitnem prehodu na Hibernate uporabimo ţe napisane SQL stavke, ki smo jih
morda predhodno definirali pri razvoju same aplikacije.
Primer spodaj prikazuje, kako uporabiti Native SQL znotraj Hibernatea. V izseku iz kode
vidimo izračunavanje standardne deviacije stddev ter povprečja mean nad stolpcem
znesek_investicije: [4].
String sql ="select stddev(z.znesek_investicije) as stdErr, "+
" avg(z.znesek_investicije) as mean from zavarovanje z";
Query query = session.createSQLQuery(sql)
.addScalar("stdErr",Hibernate.DOUBLE).addScalar("mean",Hibernate.DOUBLE);
Object [] znesek = (Object [])query.uniqueResult();
System.out.println("povprečni znesek: " + amount[0]);
System.out.println("stdErr znesek: " + amount[1]);
4.5 Asociacije ter zdruţevanja
V splošnem je mapiranje asociacij znano kot zahtevno opravilo, ki ga ţelimo narediti
pravilno. V primerih, ki sledijo, je prikazano zdruţevanje najprej s pomočjo enosmernih relacij,
nato je prikazan primer z dvosmernimi relacijami. Prav tako v primerih uporabljam vrednosti
null nad primarnimi ključi, čeprav vemo, da ti v dobrih praksah niso najbolj zaţeleni.
Za primer sem uporabil druge tabele ter POJO objekte, in sicer Prodajalec ter Produkt [3].
4.5.1 HQL inner join
HQL ni sposoben upravljati z inner join on stavki. To pomeni, da kadar entiteni model
vključuje relacije, definirane med dvema tabelama, potem inner join s pomočjo HQL-a izgleda
sledeče:
Query query = session.createQuery("from Produkt p inner join p.prodajalec as prod
prod.ime=’Integra’");
91
Kot vidimo, določilo on ni navedeno. HQL informacijo o tem, po katerem ključu zdruţuje
tabele, črpa iz XML datoteke, kjer je definirano mapiranje, zato tega ni potrebno navesti v
samem povpraševanju.
Spodnja izseka iz kode prikazujeta datoteke, kjer je definirano mapiranje za konkretna
primera razredov Prodajalec ter Produkt.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="Prodajalec">
<id name="id" type="int">
<generator class="increment"/>
</id>
<property name="ime" type="string"/>
<bag name="produkt" inverse="true" cascade="all">
<key column="prodajelecid"/>
<one-to-many class="Produkt"/>
</bag>
</class>
</hibernate-mapping>
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="Product">
<id name="id" type="int">
<generator class="increment"/>
</id>
<property name="ime" type="string"/>
<property name=" prodajelecid" type="int"/>
<property name="cena" type="double"/>
<many-to-one name="prodajalec" class="Prodajalec" column="
prodajelecid"/>
</class>
</hibernate-mapping>
92
Uporaba:
String sql_query = "from Produkt p inner join p.prodajalec as prod";
Query query = sess.createQuery(sql_query);
Iterator it = query.list().iterator()
System.out.println("Ime produkta\t"+"Ime prodajalca\t"+"Cena");
while ( it.hasNext() ) {
Object[] par = (Object[]) it.next();
Produkt produkt = (Produkt) par[0];
Prodajalec prodajalec = (Prodajalec) par[1];
System.out.print(produkt.getIme());
System.out.print("\t" + prodajalec.getIme());
System.out.print("\t\t" + produkt.getCena());
System.out.println(); }
Tabela 6: Dobljen rezultat ob uporabi inner join določila
Ime produkta Ime prodajalca Cena
BMW serija 1 Integra 20.000
Mini Integra 18.000
Audi A5 Porsche Ptujska 40.000
4.5.2 Agregatne funkcije pri uporabi join
S pomočjo Hibernatea tako kot pri enostavnih povpraševanjih in pri zdruţujevanju tabel
lahko proţimo agregatne funkcije nad atributi. Ravno tako je moţno izvajati agregatne funkcije
nad Collections objekti. Uporabljamo lahko: min(...), max(...) sum(...), avg(...), count(*),
count(distinct), count(all), count(...) [2].
String sql_query = "select prodajalec.ime,sum(produkt.cena) as cena
from Produkt p join produkt.prodajalec prodajalec group by prodajalec.ime";
Query query = sess.createQuery(sql_query);
System.out.println("Ime prodajalca\t"+"Cena");
for(Iterator it = query.iterate();it.hasNext();){
Object[] row = (Object[]) it.next();
System.out.print(row[0]);
System.out.print("\t\t"+row[1]);
System.out.println();
}
93
Tabela 7: Dobljen rezultat agregatne funkcije ob uporabi join
Ime prodajalca Cena
Integra 38.000
Porsche Ptujska 40.000
4.6 Vgnezdeni povpraševalni stavki
Vgnezdene povpraševalne stavke lahko laično definiramo kot povpraševalne stavke, ki jih
obkroţajo oklepaji (). Vgnezdeni povpraševalni stavki so namenjeni grupiranju, razvrščanju in
agregaciji rezultatov ob uporabi where pogoja. Vgnezdeni povpraševalni stavki imajo prednost
pred glavnim povpraševalnim stavkom ter so izvedeni prvi. HQL podpira vgenzdene
povpraševalne stavke samo, če podpira to tudi podatkovna baza, s katero komunicira.
V spodnjem primeru bomo iz podatkovne baze s pomočjo vgnezdenega povpraševalnega
stavka dobili vse tiste rezultate, katerih cena produktov je večja od 18.000 € [12].
String sql_query = "select prodajalec.ime, produkt.ime,
produkt.cena from Produkt produkt join produkt.prodajalec
prodajalec where produkt.cena in(select produkt.cena from produkt where produkt.cena
> 18000)";
Query query = sess.createQuery(sql_query);
System.out.println("Ime prodajalca\t"+"Ime produkta\t"+"Cena");
for(Iterator it=query.iterate();it.hasNext();){
Object[] row = (Object[]) it.next();
System.out.print(row[0]);
System.out.print("\t\t"+row[1]);
System.out.print("\t"+row[2]);
System.out.println();
}
Tabela 8: Dobljen rezultat vgnezdenega povpraševanja
Ime prodajalca Ime produkta Cena
Integra BMW serija 1 20.000
Porsche Ptujska Audi A5 40.000
94
4.7 Anotacije
Hibernate potrebuje informacije o podatkih, da lahko mapira nek POJO objekt v relacijsko
tabelo ali obratno. Najbolj pogosto je ta informacija XML datoteka, ki je predstavljena v
predhodnih poglavjih. Z uvedbo Jave5 (Tiger) verzije je bil predstavljen zelo močan ter efektiven
način uporabe metainformacij za javanski virtualni stroj. Mehanizem je bolje znan kot anotacija
(Annotations). Anotacija je javanski razred, ki prebere JVM v času izvajanja skozi refleksijo ter
ga ustrezno procesira. Anotacije so močan ponudnik metainformacij za objektno-relacijsko
mapiranje. Vse metainformacije so zdruţene v sam POJO java razred, vključno z njegovimi
metodami ter atributi. To omogoča razvijalcem tudi boljše razumevanje strukture tabele ter
POJO atrbutev hkrati. Prav tako pa zreducira upravljanje z dveh datotek na eno samo, saj
zdruţuje meta informacije ter javansko kodo v eni sami datoteki.
Zahteve za uporabo anotacij:
Java 5.0 ali novejša verzija;
Hibernate core 3.2.0GA ali novejša verzija;
naloţena Hibernate-Annotations.jar datoteka v projekt v okolju, v katerem razvijamo
[4].
95
4.7.1 Primer uporabe anotacij
Edina stvar, ki se razlikuje je, da moramo prevajalniku povedati, da bomo namesto
mapirnega razreda – XML datoteke uporabili POJO razred, v katerem so s pomočjo anotacij
definirane metainformacije za mapiranje. Slednje prikazuje spodnji izsek:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD
3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<mapping resource="hbm.xml.Zaposleni"/>
<mapping resource="Zaposleni"/>
</session-factory>
</hibernate-configuration>
Kot opombo navajam, da uporaba anotacij ne pomeni, da je hbm.xml datoteka sedaj
neuporabna. Poljubno lahko mešamo uporabo hbm.xml mapiranja ter anotacij, vendar ţal ne za
isti POJO razred.
Glavna sprememba ob uporabi anotacij je vidna v POJO razredih, nad vsakim atributom so
sedaj uporabljene anotacije, ki definirajo različna določila. V našem primeru je nad razredom
določilo @Entity, kar pomeni, da je razred POJO vezan na neko podatkovno tabelo, na katero pa
nam pove @Table. Določilo @Id pove, da je ta atribut primarni ključ. @Column nam pove, na
kateri stolpec v pripadajoči tabeli se veţe določen atribut [4].
96
/** Kreiranje Pojo objekta*/
Zaposleni pojo = new Zaposleni();
pojo.setId(new Integer(5));
pojo.setIme("XYZ");
/** Shranjevanje POJO objekta*/
sess.save(pojo);
Kot zaključek pri uporabi anotacij navajam, da postaja uporaba anotacij vse bolj priljubljena.
Iz zgornjih primerov še zdaleč ne moremo povzeti vseh moţnosti, ki jih le-te ponujajo. V praksi
se uporabljajo veliko bolj kompleksne zadeve, od definiranja relacij, dedovanja, ki pa so vsaj v
grobem bile predstavljene v poglavju o JPA-ju ter bodo prav tako vidne iz primera lastne
aplikacije Kino, ki sledi.
@Entity
@Table(name = "zaposleni")
public class Zaposleni implements Serializable {
public Zaposleni() {
}
@Id
@Column(name = "id")
Integer id;
@Column(name = "ime")
String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getIme() {
return ime;
}
public void setIme(String Ime) {
this.ime = ime;
}
}
97
4.8 Uporaba Hibernatea v aplikaciji Kino
Kot cilj diplomske naloge sem si zadal prikaz Hibernate objekto-relacijskega mapirnega
mehanizma. V ta namen sem se odločil, da implementiram namizno »desktop« aplikacijo z
razlogom, da preverim, kako se Hibernate obnese izven EJB entitetnih zrn ter enterprise
aplikacij. Zahteve, ki sem si jih začrtal, so narekovale nekaj preprostih funkcionalnosti, ki sem
jih ţelel prikazati. Osnovna zamisel aplikacije Kino temelji na primeru iz realnega sveta. Kino je
aplikacija, namenjena za osnovno upravljanje (dodajanje ter brisanje) podatkov, ki so potrebni za
kinopredstavo ter navsezadnje prodajo samih kino vstopnic. Prav tako nudi nadzor nad prostimi
vstopnicami za določeno predstavo ter podrobne opise samih filmov.
4.9 Zbiranje zahtev
Aplikacija kino naj bi omogočala:
osnovne informacije o kinu;
vnos dvorane ;
brisanje dvorane;
vnos filma;
brisanje;
vnos predstave;
brisanje predstave;
prodaja vstopnic.
98
4.10 Analiza
Zajema pogled razvijalca kako in s kakšnim pristopom bi implementiral določen segment. Na
tem mestu je treba povezati znanja, pridobljena z zbiranjem zahtev z dejansko implementacijo.
Podpoglavja zajemajo podrobnejšo analizo za posamezni sklop.
4.10.1 Osnovne informacije o Kinu
So informacije (ime, ulica, info ...), včitane iz konfiguracije ali nekega zunanjega vira, na
podlagi katerega bi se podatkovna baza, ter skladno s tem tudi aplikacija, uspešno inicializirala
ob prvem zagonu. To so informacije, ki jih lahko napolni v podatkovno bazo tudi administrator.
4.10.2 Vnos dvorane
Omogoča vnos dvorane na podlagi številke ali imena dvorane. V zadnjem času se v svetu
pojavlja trend, da dvorane niso nujno poimenovane s številko, temveč vse večkrat vidimo
dvorane, poimenovane tudi imensko (3D, Lumpi, itd... ). Prav tako je obvezen podatek število
sedeţev, ki jih dvorana premore. Podatek je potreben za generiranje vstopnic ter kasnejšo
prodajo vstopnic.
4.10.3 Brisanje dvorane
Dvorano naj bo mogoče brisati, kar pripomore k temu, da je kasneje ni več mogoče povezati s
katero koli predstavo.
4.10.4 Vnos filma
Prav tako kot vnos dvorane tudi film vsebuje preproste informacije, ki so potrebne za samo
identifikacijo filma. Podatki, ki so pomebni za film, pa so ime filma, dolţina ter kratek opis, ki je
poljuben tekst in lahko vsebuje ţanr oz. klasifikcijo filma glede na vsebino.
99
4.10.5 Brisanje filma
Tudi brisanje filma je z uporabniškega vidika pomembno, saj je tudi film entiteta, ki vpliva na
predstavo in ga ob uspešnem brisanju ni več mogoče povezati s predstavo.
4.10.6 Vnos predstave
Predstava vsebuje datum ter čas, kdaj naj bi se predstava realizirala, ter podatke o filmu ter
dvorani. Prav tako zdruţuje informacije o prostih vstopnicah za to predstavo. Vstopnica je
entiteta, ki vsebuje številko sedeţa ter zase ve ali je prosta ali zasedena.
4.10.7 Brisanje predstave
Analogno z filmom ter dvorano je tudi predstava entiteta, ki jo lahko ob morebitnem
napačnem vnosu brišemo.
4.10.8 Prodaja vstopnic
Prodaja vstopnic je jedro aplikacije Kino. Na tem mestu naj bi bili zbrani ţe vsi podatki, ki so
seveda potrebni za prodajo vstopnic. Uporabniški vmesnik omogoča nabiranje kart, ki smo jih
izbrali v nek seznam. Prav tako lahko izbrani karti dodamo študentski popust. Ob prodani
vstopnici za določeno predstavo sedeţi, ki smo jih pravkar prodali v aplikaciji, niso več vidni.
100
4.11 Načrtovanje
Aplikacijo Kino sem modeliral s pomočjo UML jezika. Po tem standardu je kot razred
uporabljen kvadrat z njegovimi atributi ter tipi. Kot povezave sem uporabil usmerjene asociacije
ter kompozicije.
Naj omenim pomen slednjih:
Usmerjena asociacija – uporabimo jo takrat, kadar naj bi razred A vedel za razred B, čeprav
razred B ter njegov obstoj sam ni pogojen z obstojem razreda A. Grafični prikaz usmerjene
asociacije je prikazan na Slika 22.
Slika 22: Usmerjena asociacija
Kompozicija – ponavadi jo uporabimo, kadar gre za vsebovan razred ali kolekcijo. Torej
razred A vsebuje kolekcijo ali razred tipa B. Pri dejanskih instancah se to odraţa kot močnejša
povezava med objektoma. Saj če odstranimo objekt A, s tem odstranimo tudi vse objekte B.
Kompozicijo med dvema objektoma prikazuje Slika 23.
Slika 23: Kompozicija
101
4.11.1 Razredni diagram
Slika 24: Razredni diagram aplikacije Kino
4.11.2 Entitetno-relacijski diagram
Aplikacija Kino uporablja Hibernate kot ponudnika zagotavljanja trajnosti objektov, kar pa
seveda pomeni, da potrebuje tudi podatkovno bazo, kadar zapisuje objekte v tabele ter obratno.
Na podlagi načrtovanega razrednega diagrama, ki ga prikazuje Slika 24, sem s pomočjo orodja
Visual Database Architect kreiral tudi entitetno-relacijski diagram, kot prikazuje Slika 25, ter
pripadajočo ddl datoteko za kreiranje tabel v podatkovni bazi, imenovani Kino.
102
Slika 25: Entitetno-relacijski diagram aplikacije Kino
4.12 Implementacija
Same implementacije sem se lotil v skladu s troslojno arhitekturo, predstavljeno z naslednjimi
nivoji, kot prikazuje Slika 26:
Slika 26: Troslojna arhitektura
103
Prezentacijski nivo – vključuje razrede, ki so potrebni za sam izris uporabniških komponent,
kot so gumbi, tabele itd. Ta nivo zdruţuje tudi nekaj pomoţnih metod, ki so povezane s samim
vmesnikom, saj sluţijo kot pomoţne metode za polnjenje privzetih vrednosti itd.
Poslovni nivo – na tem mestu sem zbral vse funkcionalnosti ter implementiral dejanske
metode, kot so »vnos filma, prodaja kart ... «. Tukaj sledi tudi maniplacija objektov ter klici
Hibernateove funkcionalnosti.
Hibernate – na sliki označen kot pomoţen nivo, saj je dejansko skrit med samim poslovnim
ter podatkovnim nivojem.
Podatkovni nivo – nivo, ki sluţi kot shramba podatkov ter zagotavlja dostop do njih tudi, ko
program izbrišemo iz notranjega pomnilnika računalnika.
Slika 27 prikazuje orodje Eclipse ter organizacijo paketov v strukture.
Slika 27: Organizacija paketov
Paket orm vključuje en sam razred, in sicer osnovno implementacijo
KinematografiPersistentManagerja, ki skrbi za eno in edino instanco persistentnega upravljalca
entitet v sistemu.
104
Paketi si.brumen se hierarhično delijo na:
entity – paket, ki vključuje vse POJO razrede ter pripadajoče anotacije;
gui – uporabniški vmesnik;
gui.resources – viri za uporabniški vmesnik;
gui.resources.icons – ikone za uprabniški vmesnik;
interfaces – vmesniki za dostop do nivoja poslovne logike;
impl – implementacija poslovne logike.
Znotraj projekta se nahajata še dva pomebna paketa, in sicer:
test – paket, namenjen testiranju enot ter testom sprejemljivosti;
util – paket, namenjen razredu s pomoţnimi statičnimi metodami, ki omogočajo
pravilno pretvorbo datuma ter časa na podlagi vnešenega niza.
4.12.1 Izseki kode iz POJO objekta
Kot primer POJO objekta sem izbral razred Predstava. Ker sem v diplomskem delu izbral
pristop z anotacijami namesto XML-a, lahko iz teh vidimo obnašanje POJO razreda ter njegovih
atributov.
@Entity
@Table(name="Predstava")
public class Predstava implements Serializable {
public Predstava() {
}
@Column(name="ID", nullable=false)
@Id
@GeneratedValue(generator="moj_generator_predstav")
@org.hibernate.annotations.GenericGenerator(name=" moj_generator_predstav",
strategy="native")
private int ID;
@OneToOne(targetEntity=si.brumen.entity.Film.class)
@JoinColumns({ @JoinColumn(name="FilmID") })
@org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,
org.hibernate.annotations.CascadeType.LOCK})
@Basic(fetch=FetchType.LAZY)
private si.brumen.entity.Film film;
@OneToOne(targetEntity=si.brumen.entity.Dvorana.class)
@org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,
org.hibernate.annotations.CascadeType.LOCK})
@JoinColumns({ @JoinColumn(name="DvoranaID") })
@Basic(fetch=FetchType.LAZY)
105
private si.brumen.entity.Dvorana dvorana;
@ManyToOne(targetEntity=si.brumen.entity.Kino.class)
@org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.LOCK})
@JoinColumns({ @JoinColumn(name="KinoID", referencedColumnName="ID") })
@Basic(fetch=FetchType.LAZY)
private si.brumen.entity.Kino kino;
@Column(name="Cas", nullable=true)
private Timestamp cas;
@OneToMany(mappedBy="predstava", targetEntity=si.brumen.entity.Karta.class)
@org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,
org.hibernate.annotations.CascadeType.LOCK,
org.hibernate.annotations.CascadeType.DELETE_ORPHAN,
org.hibernate.annotations.CascadeType.DELETE})
@org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOpti
on.FALSE)
@org.hibernate.annotations.Where(clause="prosta=1")
private java.util.List<si.brumen.entity.Karta> seznamKart = new
ArrayList<si.brumen.entity.Karta>();
private void setID(int value) {
this.ID = value;
}
public int getID() {
return ID;
}
public int getORMID() {
return getID();
}
public void setCas(Timestamp value) {
this.cas = value;
}
public Timestamp getCas() {
return cas;
}
public void setKino(si.brumen.entity.Kino value) {
this.kino = value;
}
public si.brumen.entity.Kino getKino() {
return kino;
}
public void setDvorana(si.brumen.entity.Dvorana value) {
this.dvorana = value;
}
public si.brumen.entity.Dvorana getDvorana() {
return dvorana;
}
public void setFilm(si.brumen.entity.Film value) {
this.film = value;
}
106
public si.brumen.entity.Film getFilm() {
return film;
}
public void setSeznamKart(java.util.List<si.brumen.entity.Karta> value) {
this.seznamKart = value;
}
public java.util.List<si.brumen.entity.Karta> getSeznamKart() {
return seznamKart;
}
public String toString() {
return String.valueOf(getID());
}
}
Opisi uporabljenih anotacij iz primera:
@Entity – pove, da gre za razred POJO, katerega ţelim mapirati v določeno tabelo.
@Table – v katero tabelo se bodo podatki preslikali, povem z anotacijo @Table. V
mojem primeru so tabele v podatkovni bazi imele enako ime kot POJO razredi, zato bi
na tem mestu lahko to anotacijo tudi izpustil, saj Hibernate, kadar le-te ne podamo
eksplicitino, kot privzeto ime tabele vzame kar ime POJO razreda.
@Column – anotacija, uporabljena pri atributu ID, je prav tako odvečna in bi jo lahko
izpustil, saj je ime stolpca v tabeli popolnoma enako kot ime atributa v POJO razredu.
@Id – pove, kateri atribut, obravnan kot identifikator, se bo preslikal v primarni ključ
v tabeli. @GeneratedValue anotacija pove, s katerim generatorjem generiram vrednost
identifikatorja, v mojem primeru je to ime moj_generator_predstav.
@GenericGenerator – anotacija, ki sledi @GeneretedValue, pa poveţe to ime
generatorja z ustrezno strategijo, podprto s strani Hibernate 3. Atribut strategy je
odvisen od podatkovne baze, s katero delamo. Sam sem izbral strategijo native iz
razloga, ker jo podpira podatkovna baza MySQL, ki sem jo uporabil v aplikaciji.
@OneToOne – relacija omogoča, da ta razred asociiram z nekim drugim. V POJO
razredu Predstava vidimo dva takšna primera (Film, Dvorana). Kardinalnost tukaj je 1.
107
@JoinColumns – anotacija zdruţuje več anotacij @JoinColumn, ki pove, po katerem
atributu iz asociiranega POJO razreda bomo lahko razrešili (naloţili) ta razred. V
primeru je vidno, da bo Hibernate poskušal atribut film, ki je tudi instanca POJO
razreda, pridobiti iz podatkovne baze glede na ključ FilmID, zapisan v tabeli
Predstava.
@Basic anotaciji lahko priredimo, kakšno naj bo pridobivanje objekta, nad katerim je
ta anotacija zapisana. FetchType.LAZY – uporabljena v mojem primeru – zahteva
objekt šele, ko prvič zahtevamo neke podatke o tem objektu, medtem ko bi privzeta
strategija FetchType.EAGER nad @OneToOne asociacijo izvedla takoj outer join
povpraševanje za pridobivanje asociiranega objekta.
@Cascade – anotacija, uporabljena nad atributi ponuja več moţnosti. Sam sem
cascade uporabil pri shranjevanju SAVE, posodabljanju UPDATE ter brisanju
DELETE_ORPHANS asociiranih atributov. Cacsade pove, kadar proţimo neko
CRUD operacijo nad tem objektom, naj se nad atributom, nad katerim stoji anotacija
prav tako izvede ta operacija.
@ManyToOne – anotacija pove, da sem sam, v mojem primeru torej POJO razred
Predstava, nekje drugje predstavljen kot kolekcija. Kje je predstavljen kot kolekcija,
pove atribut targetEntity znotraj telesa anotacije.
@OneToMany – v telesu anotacije pove, od koga je atribut, nad katerim stoji
anotacija, mapiran (mappedBy), ter kdo je njegov ciljen POJO razred (targetEntity).
Torej Predstava vsebuje atribut karte, ki so kolekcija.
@LazyCollection – anotacija, ki vedno stoji nad kolekcijami. Kadar se znotraj nje, kot
v primeru POJO razreda Predstava, nahaja LazyCollectionOption.FALSE, pomeni to,
da se mora kolekcija nemudoma napolniti ob nalaganju Predstava objekta.
108
4.12.2 Izseki kode iz poslovnega nivoja
Kot primerka uporabe Hibernatea znotraj aplikacije kino navajam dva izseka iz
funkcionalnosti, in sicer vnos predstave ter statistika za prodane karte.
Metoda vnos predstave:
public void vnosPredstave(String date, String time, Film film, Dvorana dvorana) throws
PersistentException {
try {
Timestamp ts = HelperUtils.composeTimeStampFromDateAndTimeString(date, time);
PersistentTransaction t =
orm.KinematografiPersistentManager.instance().getSession().beginTransaction();
Predstava predstava = new Predstava();
predstava.setCas(ts);
predstava.setFilm(film);
predstava.setDvorana(dvorana);
List<Karta> setKart = new ArrayList<Karta>();
for(int i=1; i <= dvorana.getSteviloSedezev(); i++) {
setKart.add(new Karta(i,predstava));
}
predstava.setSeznamKart(setKart);
predstava.setKino(film.getKino());
orm.KinematografiPersistentManager.instance().getSession().
save(predstava);
t.commit();
} catch (Exception e) {
if (e instanceof NumberFormatException)
new OpozoriloOkno("Polje dolžina mora biti celo število !!!");
else
new OpozoriloOkno(e.getMessage());
} finally {
orm.KinematografiPersistentManager.instance().
disposePersistentManager();
orm.KinematografiPersistentManager.instance().
getSession().close();
}
}
Iz kode je razvidno, da metoda kot vhodne parametre prejme objekte tipa Film, Dvorana ter
dva atributa time ter date, ki sta podana kot tipa String. Sama metoda najprej pridobi sejo
instance Hibernateovega persistentnega upravljalca ter ustvari transakcijo. Sledi klic statične
metode, ki pretvarja iz atributov date ter time v javanski objekt Timestamp.
109
Slednji je potreben, kadar postavljam datum ter čas na sam objekt Predstava. Kreacija
objekta Predstava zahteva tudi polnjenje objekta z atributi, kot so film ter dvorana, ter na podlagi
števila sedeţev v podani dvorani tudi kreacijo seznama objektov Karta, z implicitno
prednastavljenimi vrednostmi, ki se nastavijo ţe v konstruktorju posameznega objekta Karta.
Seznam objektov kart se nato doda na objekt Predstava, prav tako pa je potrebno nastaviti še
referenco »nazaj« na objekt Kino. S tem tudi objekt Predstava ve, kateremu objektu Kino
pripada. Sledi klic metode save s podanim argumentom predstava nad hibernateovo sejo. Metoda
se zaključi tako, da se potrdi »commit« transkacija. Po uspešnem shranjevanju sledi zapiranje
seje ter sproščanje instance persistentnega upravljalca.
Metoda statistika za prodane karte:
public static List<Karta> getStatistikaProdanihKart()
throws PersistentException {
List<Karta> karte = null;
try {
PersistentTransaction t = orm.KinematografiPersistentManager
.instance().getSession().beginTransaction();
Query query = orm.KinematografiPersistentManager
.instance().getSession()
.getNamedQuery("poisciVseProdaneKarte")
.setBoolean("prosta", false);
karte = query.list();
t.commit();
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
orm.KinematografiPersistentManager.instance()
.disposePersistentManager();
orm.KinematografiPersistentManager.instance()
.getSession().close();
}
return karte;
}
110
Metoda ne sprejme nobenih argumentov, ker se ukvarja samo s pridobivanjem podatkov iz
relacijske podatkovne baze. Zgrajena je podobno kot metoda vnos predstave, saj znotraj
transkacije prav tako kliče metodo Hibernateove knjiţnice. Zanimivost tukaj je, da uporablja
poimenovan povpraševalni stavek, ki sem ga sam definiral nad POJO objektom Karta. Sintaksa,
kot je razvidno, narekuje pridobivanje Hibernateove seje, kateri z metodo getNamedQuery
povemo, da imamo nekje nad POJO razredom definiran povpraševalni stavek z imenom, ki ga
podamo v argument. Z naslednjim klicom metode, v mojem primeru je to bil setBoolean,
povemo morebitni parameter za povpraševalni stavek. V primeru metode za izpis statistike
prodanih kart je to bil parameter, ki je omejil kriterije iskanja na tiste zapise v tabeli karta,
katerih atribut prosta je postavljen na vrednost false. Nad atributom query, ki smo ga dobili z
opisano sintakso, pokličem še samo metodo list, ki pa ţe vrne rezultate pravega tipa, in sicer
takšne, kot so navedeni v poimenovanem povpraševalnem stavku.
Poimenovan stavek nad POJO razredom Karta:
@NamedNativeQueries({
@NamedNativeQuery(
name = "poisciVseProdaneKarte",
query = "select * from karta k where k.prosta = :prosta",
resultClass = Karta.class
)
})
4.13 Grafični vmesnik
Aplikacija Kino je z vidika uporabniških vmesnikov razdeljena na naslednje segmente:
kino glavno okno;
vnos filma okno;
vnos dvorane okno;
vnos predstave okno;
prodaja kart okno.
111
4.13.1 Kino glavno okno
Glavno okno aplikacije Kino, ki ga prikazuje Slika 28, je namenjeno izpisu statistike
prodanih kart ter povezavam na druga okna, katera vsaka zase sluţijo določeni funkcionalnosti,
ki se nahajajo za akcijami gumbov do teh oken.
Slika 28: Uporabniški vmesnik kino glavno okno
112
4.13.2 Vnos filma okno
Slika 29 vnos filma okno prikazuje v spustnih seznamih vse filme, ki se nahajajo v podatkovni
bazi. Na tem mestu je omogočen pregled filmov, dodajanje novih ter brisanje tistih, ki še niso
povezani z nobeno od predstav.
Slika 29: Uporabniški vmesnik za vnos ter brisanje filma
113
4.13.3 Vnos dvorane okno
Slika 30 vnos dvorane analogno z oknom za vnos filma prav tako ponuja pregled nad
vnešenimi dvoranami ter omogoča vnos nove dvorane ter njenih atributov. Omogočeno je tudi
brisanje, vendar se tudi to lahko proţi le, kadar s to dvorano ni povezana še nobena predstava.
Slika 30: Uporabniški vmesnik za vnos ter brisanje dvorane
114
4.13.4 Vnos predstave okno
Vnos predstave je iz modelskega vidika malenkost bolj kompleksna, saj pri svojem vnosu
zahteva ţe vnešene dvorane in filme. Okno prikazuje Slika 31.
. Na tem mestu se na novo ustvarjena predstava asociira z dvorano in filmom, za kreacijo
novega zapisa v podatkovni bazi pa je potrebno še vnesti atribute datum in čas predstave. Ob
uspešnem vnosu predstave se ustvari na podlagi števila sedeţev v pripadojoči dvorani prav toliko
kart s postavljenimi privzetimi vrednostmi. Prav tako pa postaneta asociiran film ter dvorana v
podatkovni bazi takšna zapisa, katera ni mogoče več izbrisati.
Slika 31: Uporabniški vmesnik za vnos ter brisanje predstave
115
4.13.5 Prodaja kart okno
Prodaja kart Slika 32 je srce aplikacije Kino. Na tem mestu lahko uporabnik poljubno iz
padajočega seznama predstave izbira predstave, nad katerimi ima v drugem padajočem seznamu
prosti sedež nadzor nad sedeţi oz. kartami, ki so še proste in jih lahko doda v seznam izbranih
sedežev. Preden se odloči, da bo dodal neko karto v seznam izbranih kart, lahko po ustrezni
identifikaciji študenta tej karti priredi še študentski popust. Pod seznamom izbranih kart se mu
sproti obračunava skupna cena izbranih kart (študentskih ter neštudentskih). Kadar je uporabnik
aplikacije Kino zadovoljen z izbrano selekcijo v seznamu izbranih sedežev, lahko s pritiskom na
gumb nakup te karte tudi dejansko kupi. Iz aplikacijskega vidika to pomeni, da ob naslednjem
pridobivanju prostih sedeţev za izbrano predstavo pravkar prodani več ne bodo vidni v
padajočem seznamu prosti sedež.
Slika 32: Uporabniški vmesnik prodaja kart
116
4.14 Testiranje
V skladu z dobrimi programerskimi praksami ter kakovostjo programske opreme, ki ji
sledimo v podjetju, v katerem sem zaposlen, sem se tudi za aplikacijo Kino v okviru
diplomskega dela odločil napisati nekaj testov, ki so potrdili robustnost ter pričakovano
delovanje aplikacije. Testiranje sem razdelil na dva sklopa, in sicer testiranje enot ter teste
sprejemljivosti.
4.14.1 Testiranje enot
Testiranje enot je zajemalo teste razredov iz paketa util. V tem paketu se nahajajo razredi, ki
sluţijo predvsem kot neki pomoţni razredi s statičnimi metodami za preverjanje vnosa polj za
datum ter uro predstave. V testu sem predvidel moţne scenarije, ki se lahko zgodijo, kadar
uporabnik vnaša nek datum ter uro predstave. Sam pričakujem kot izhod iz metod nek niz tipa
String, ki bi zadostoval za preprosto kreiranje objekta Timestamp, katerega pa bi ob uspešnem
uporabnikovem vnosu ţe ponastavil atributu Cas iz POJO razreda Predstava.
117
Izsek kode iz testa, ki preverja pravilnost vnosa ure:
public void testParseTimeInAppropriateFormat() {
try{
assertEquals("12.30.00",HelperUtils.parseTimeInAppropriateFormat("12:30:00"));
} catch (Exception e) {
}
try{
assertEquals("12.59.59",HelperUtils.parseTimeInAppropriateFormat("12:59:59"));
} catch (Exception e) {
}
try{
assertEquals("12.00.00",HelperUtils.parseTimeInAppropriateFormat("12:00:00"));
} catch (Exception e) {
}
try {
failNotEquals("",
"12.00.00",HelperUtils.parseTimeInAppropriateFormat("11:59:60"));
} catch (Exception e) {
assertEquals("Sekunde morajo biti od 0 do 59", e.getMessage());
}
try {
assertEquals("12.30.00",HelperUtils.parseTimeInAppropriateFormat("12:30"));
} catch (Exception e1) {
}
try {
assertEquals("12.00.00",HelperUtils.parseTimeInAppropriateFormat("12"));
} catch (Exception e1) {
}
try {
failNotEquals("", "12.00.00",HelperUtils.parseTimeInAppropriateFormat("-
12:00:00"));
} catch (Exception e) {
assertEquals("Ura mora biti od 0 do 23", e.getMessage());
}
try {
failNotEquals("",
"12.00.00",HelperUtils.parseTimeInAppropriateFormat("12.00.00"));
} catch (Exception e) {
assertEquals("Čas mora biti v formatu hh:mm:ss", e.getMessage());
}
}
Rezultat po testiranju prikazuje Slika 33.
Slika 33: Rezultat testa za razred HelperUtils
118
4.14.2 Testi sprejemljivosti
Testi sprejemljivosti so obsegali testiranje funkcionalnosti na poslovnem nivoju. V ta sklop so
zajeti razredi iz paketa impl. Testi sprejemljivosti dokazujejo, kako se aplikacija obnaša glede na
zastavljene scenarije. V primeru sem prikazal test sprejemljivosti nad razredom DvoranaImpl, ki
pozna implementacije metod, kot so vnosDvorane, brisiDvorano ter getDvorane. Scenarij, ki
sem si ga zamislil, je bil sledeč:
Pridobivanje števila dvoran, ki ţe obstajajo v podatkovni bazi.
Vnos nove dvorane.
Preverjanje števila dvoran v podatkovni bazi po vnosu nove dvorane.
Preverjanje obstojnosti vnešene dvorane v podatkovni bazi ter konsistenca njenih
podatkov.
Ponovno pridobivanje števila dvoran, ki ţe obstajajo v podatkovni bazi.
Brisanje dvorane iz podatkovne baze.
Preverjanje števila dvoran v podatkovni bazi po brisanju dvorane, ki mora biti sedaj
manjše.
Ter še poskus nalaganja dvorane, ki sem jo pravkar izbrisal, kaţe na to, da dvorana v
podatkovni bazi ne obstaja več.
private DvoranaImpl dvoranaService;
private Integer stevilkaDvorane;
private static final String ST_SEDEZEV = "10";
public void setUp() {
dvoranaService = new DvoranaImpl();
Random rand = new Random(Calendar.getInstance().getTimeInMillis());
stevilkaDvorane = rand.nextInt();
}
public void testVnosTerBrisanjeDvorane() {
int stDvoran=0;
//VNOS DVORANE
try{
stDvoran = dvoranaService.getDvorane().size();
dvoranaService.
vnosDvorane(Integer.toString(stevilkaDvorane),ST_SEDEZEV);
assertEquals(stDvoran+1, dvoranaService.getDvorane().size());
} catch (Exception e) {
}
119
//PREVERJANJE OBSTOJNOSTI V PODATKOVNI BAZI + KONSISTENCA PODATKOV
try{
Dvorana dvorana = dvoranaService.getDvorana(stevilkaDvorane);
assertNotNull(dvorana);
assertEquals(stevilkaDvorane, dvorana.getStevilka());
assertEquals(ST_SEDEZEV, dvorana.getSteviloSedezev().toString());
} catch (Exception e) {
}
//BRISANJE IZ PODATKOVNE BAZE
try{
stDvoran = dvoranaService.getDvorane().size();
Dvorana dvorana = dvoranaService.getDvorana(stevilkaDvorane);
dvoranaService.brisiDvorano(dvorana);
assertEquals(stDvoran-1, dvoranaService.getDvorane().size());
} catch (Exception e) {
}
//PREVERJANJE OBSTOJNOSTI V PODATKOVNI BAZI
try{
Dvorana dvorana = dvoranaService.getDvorana(stevilkaDvorane);
assertNull(dvorana);
} catch (Exception e) {
}
}
Rezultat po testiranju prikazuje Slika 34.
Slika 34: Rezultat testa za razred DvoranaImpl
4.15 Uporabljena orodja
Pri razvoju aplikacije sem si pomagal preteţno z odprtokodnimi orodji. Ostala orodja, ki so
bila plačjiva, sem bodisi uporabil v sluţbi ali jih zamenjal z odprtokodnimi in neplačljivimi
rešitvami. Uporabljena orodja:
120
Razvoj
Eclipse Galileo.
Knjiţnice iz paketov:
orm.jar – za Hibernateovo objektno-relacijsko mapiranje;
annotations.jar – knjiţnice za delo z anotacijami;
appFramework – 1.0.jar – knjiţnice za delo s Swing komponentami za potrebe
uporabniškega vmesnika.
Modeliranje
Visual Paradigm – Database visual architect.
Testiranje
Knjiţnice iz paketa JUnit.
4.16 Predlogi za izboljšave/dopolnitve
Predloge za izboljšave bi izpostavil v nekaj ključnih točkah:
Moţna razširitev aplikacije Kino tako, da bi sam model vseboval več POJO objektov,
tudi takšnih, ki bi nosili informacije o uporabniku. S tem bi poslovni nivo bil zmoţen
narediti tudi rezervacije kart.
Uvedla bi se lahko prijava v sistem, ki bi zagotovila varnost ter nadzor nad akcijami,
ki jih proţi določen uporabnik.
Implementacija dodatnih statistik, ki bi bile zmoţne procesirati različne podatke o
pogostosti zahajanja v kino, ob katerih dnevih se največ kupujejo karte, čas, ko se
karte največkrat rezervirajo itd. S tem bi lahko spremljali popularnost filmov,
popularnost dnevov in časov predstav.
Storno prodanih kart.
121
5 SKLEP
Kot zaposlen v podjetju, ki se ukvarja z razvojem informacijskih sitemov (EIS), me je
zanimalo podrobno delovanje nam dobro znane ps instance, ki smo jo na nivoju poslovne logike
vedno znova uporabljali. V povojih samega uvajanja v svet informacijske tehnologije si s tem
niti nisem delal preveč skrbi, saj so mi kolegi iz podjetja pokazali kako in predvsem kdaj to
uporabiti. Tako sem s pridom uporabljal metode save, update itd, ki se veţejo na ps instanco.
Vendar so se kot skoraj v vsakem razvijalskem okolju pojavile teţave v zvezi s perfomančnostjo
samega povpraševanja preko mehanizma za obstojnost podatkov. Naš mehanizem je bil in še je
izdelan popolnoma po meri in so ga razvijalci, ki delajo skupaj z nami, napisali sami. Na tem
mestu je odločitev pritegnila mene, da raziščem, ali na trgu ne obstaja kaj, kar bi zadostilo našim
potrebam ter ne bi povzročalo takšnih performančnih teţav. Tako sem preko raznih spletnih
strani poiskal danes najbolj uporabljen ORM mehanizem Hibernate ter proučil njegove
zmoţnosti ter omejitve.
Vse zgoraj omenjeno me je peljalo najprej do same specifikacije ORM-ov, kar sem raziskoval
v specifikacijah JPA. Tukaj bi kot glavno tezo navedel rojstvo JPA-ja s prihodom specifikacije
EJB 3.0. Znaten preskok, ki se je kazal, je bila uvedba svojega dela specifikacije za JPA znotraj
EJB 3.0. Pred tem v verziji EJB 2.1 so se razvijalci sicer lahko posluţevali metod, ki so
zagotovile stalnost podatkov. Vendar so za to morali uporabljati Java entitetna zrna ter vmesnik
EntityManager znotraj EJB vsebnika. S prihodom EJB 3.0 pa se stvar bistveno spremeni.
Specifikacije EJB 3.0 so naredile Java Persistance engine neodvisen od EJB vsebnika. V skladu s
tem vsebuje EJB vsebnik vtič (pluggable), preko katerega uporabnik poveţe po svoje izbran
kateri koli podprt ORM mehanizem. Dobra novica je tukaj namenjena razvijalcem, ki ne
uporabljajo EJB vsebnika, saj lahko sedaj uporabljajo ORM mehanizem, kot je npr. Hibernate
tudi zunaj EJB vsebnika v svojih javanskih aplikacijah. Naslednja dobra stvar, ki sem jo
ugotovil, pa je tudi manj kodiranja za samega razvijalca, saj se je obseg kode iz treh razredov, ki
so bili potrebni v EJB 2.1, zmanjšala na en sam razred Entity. Dejstvo, da je ta razred Entity
dejansko POJO razred ter 1 : 1 entiteta v podatkovni bazi, je pa naredilo samo specifikacijo še
bolj privlačno za uporabo. Kot posladek vsemu omenjenemu pa je vsekakor uporaba JDK 1.5.0
anotacij v POJO razredih ter zamenjava oz. souporaba teh z XML deployment-descriptorji.
122
V drugem ter tretjem delu diplomske naloge sem delal predvsem na konkretnih primerih
enega izmed ORM-ov in sicer Hibernateu. Tudi tukaj sem prišel do zanimivih sklepov ter
ugotovitev. S pomočjo uvoda ter razumevanjem arhitekture sem počasi raziskoval metode,
pravila ter prednosti ter slabosti, ki jih pa ni bilo prav veliko. Vsekakor moram omeniti
enostavnost in popolnoma objekten pristop, ki ga podpira Hibernate, kadar ţelimo zagotoviti
trajno stanje nekega objekta. Morda se mlajša generacija razvijalcev, kot tudi sam, sploh ne
zaveda moči tega ter nam je morda navedeno malo samoumevno. Vendar če primerjam
povpraševanja danes ter povpraševanja še ne tako dolgo nazaj, se da hitro opaziti, da so bazne
procedure, ki so lahko bile dolge tudi po več vrstic, ter preprostost, danes npr. save() metode, ki
zavzema le nekaj alfanumeričnih znakov, ob zagotavljanju enake funkcionalnosti, praktično
neprimerljive. Lahko z gotovostjo trdim, da je zagotavljanje obstojnosti objektov danes na zelo
visokem ter abstraktnem nivoju. In ne le da omenjam preprostost metode save(), Hibernate
ponuja veliko bolj napredne zmoţnosti, ki so me fascinirale, kot so npr. dedovanje,
polimorfizem, tudi svoj povpraševalni jezik HQL, ki zopet omogoča zahtevno zdruţevanje tabel,
agregatne funkcije itd. Zanimivost, in z mojega stališča gledano velik plus, je vsekakor tudi
uporaba Native SQL povpraševalnega jezika znotraj Hibernatea, kar še vzpodbudi »nejeverne
Tomaže«, da pa je morda le čas, da svoje ţe obstoječe aplikacije posodobijo s kakšnim modernim
pristopom ORM mehanizma. Sam skozi primere nisem ugotovil nekih minusov pri uporabi
Hibernatea. Res pa je, da so primerčki, na katerih sem delal, osnovni ter temeljijo na poznavanju
osnovnega dela s Hibernateom. Pojavil se mi je le dvom, za katerega pa ţal nimam dokazov, da
se Hibernate morda slabše ter počasneje obnaša pri zahtevnejših povpraševanjih ter velikih
količinah podatkov. Čeprav bi vsaj zadnje navedeno tezo morda lahko spodbil, saj Hibernate kot
tak pri povpraševanju ponuja lazy loading za pridruţene tabele, kar nedvomno vsaj delno reši
teţave z dolgim izvajanjem povpraševanj.
123
6 VIRI IN LITERATURA
[1] A. Goncalves, Beginning Java EE 6 platform with GlassFish 3. Apress, 2009.
[2] Agregatne funkcije. Dostopno: http://www.roseindia.net/hibernate/hibernate-aggregate-functions.shtml
(01. 03. 2010)
[3] Asociacije in zdruţevanja. Dostopno: http://www.roseindia.net/hibernate/associations-and-joins.shtml (01.
03. 2010) [4] C. Bauer, G. King, Hibernate in Action – the ultimate Hibernate reference. Manning Publications.
Greenwich. 2005.
[5] D. Panda, R. Rahman, D. Lane, EJB 3 in action. Manning Publications. Greenwich, 2007.
[6] Hibernate Dostopno: http://www.roseindia.net/hibernate/ (22. 03. 2010)
[7] HQL Dostopno: http://www.roseindia.net/hibernate/hibernatequerylanguagehql.shtml (10. 03. 2010)
[8] JDBC vs ORM. Dostopno: http://roseindia.net/jpa/jdbc-vs-orm.shtml (15. 02. 2010)
[9] JPA. Dostopno: http://roseindia.net/jpa/jpa-introduction.shtml (22. 02. 2010)
[10] Learning course – Hibernate. Dostopno: http://www.roseindia.net/hibernate/index.shtml (10. 03. 2010)
[11] Mapping. Dostopno: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/mapping.html
[12] Vgnezdeni povpraševalni stavki. Dostopno:, http://www.roseindia.net/hibernate/hibernate-
subqueries.shtml (25. 02. 2010)
124
7 PRILOGE
125
126