View
0
Download
0
Category
Preview:
Citation preview
Miskolci Egyetem
Gépészmérnöki és Informatikai Kar
Általános Informatika Tanszék
Java alapú SQL API felületek hatékonyság elemzése
Szakdolgozat
Dobos Tamás
H4BW07
3580 Tiszaújváros, Munkácsy Mihály út 5. 1/1.
Java alapú SQL API felületek hatékonyság elemzése
EREDETISÉGI NYILATKOZAT
Alulírott Dobos Tamás (Neptun-kód: H4BW07) a Miskolci Egyetem
Gépészmérnöki és Informatikai Karának végzős mérnökinformatikus szakos
hallgatója ezennel büntetőjogi és fegyelmi felelősségem tudatában nyilatkozom és
aláírásommal igazolom, hogy
Java alapú SQL API felületek hatékonyság elemzése című
szakdolgozatom saját, önálló munkám; az abban hivatkozott szakirodalom
felhasználása a forráskezelés szabályai szerint történt.
Tudomásul veszem, hogy szakdolgozat esetén plágiumnak számít:
- szószerinti idézet közlése idézőjel és hivatkozás megjelölése nélkül;
- tartalmi idézet hivatkozás megjelölése nélkül;
- más publikált gondolatainak saját gondolatként való feltüntetése.
Alulírott kijelentem, hogy a plágium fogalmát megismertem, és tudomásul veszem,
hogy plágium esetén szakdolgozatom visszautasításra kerül.
Miskolc, 2015. május 4.
...................................
Dobos Tamás hallgató
Java alapú SQL API felületek hatékonyság elemzése
KÖSZÖNETNYILVÁNÍTÁS
Ezúton szeretném megköszönni a Miskolci Egyetem tanárainak, akik
tudásukat átadva lehetővé tették számomra, hogy eljuthassak szakdolgozatom
megírásához.
Köszönet illeti mindazokat, akik segítségemre voltak szakdolgozatom
elkészítésében és tanácsaikkal előrébb vitték munkámat.
Külön köszönettel tartozom konzulensemnek, dr. habil Kovács László
egyetemi docens Úrnak, akinek szakmai tanácsai nélkül nem jöhetett volna létre
ezen dolgozat.
Természetesen hálával tartozom Édesapámnak és Édesanyámnak is, akik
egyetemi éveim során mindenben támogattak és szakdolgozatom készítése alatt
is folyamatosan bátorítottak.
Miskolc, 2015. május 4.
...................................
Dobos Tamás hallgató
Tartalomjegyzék
1. Bevezető ............................................................................................................ 1
2. Java környezet ................................................................................................... 3
2.1 JDBC API áttekintés ..................................................................................... 3
2.2 SQLJ API áttekintés ...................................................................................... 7
2.3 JPA API áttekintés ...................................................................................... 10
2.4 JINQ API áttekintés..................................................................................... 14
3. Benchmarking, benchmark tesztek .................................................................. 19
3.1 Benchmark tesztek az adatbázis-kezelésben ............................................. 19
4. Tesztrendszer megtervezése ........................................................................... 23
4.1 Tesztelés célja ............................................................................................ 23
4.2 Mérhető vagy vizsgálható tulajdonságok .................................................... 23
4.2.1 Tesztelési környezet ............................................................................. 24
4.3 Tesztelés módszere .................................................................................... 26
5. Implementáció .................................................................................................. 28
5.1 Általában az adatbázis programozásról ...................................................... 28
5.2 Tesztadatbázis létrehozása ........................................................................ 28
5.3 Az alkalmazói program implementálása ..................................................... 29
5.3.1 JDBC API implementáció ..................................................................... 36
5.3.2 SQLJ API implementáció ..................................................................... 38
5.3.3 JPA API implementáció ........................................................................ 40
5.3.4 JINQ API implementáció ...................................................................... 43
6. Tesztelés eredményei ...................................................................................... 45
6.1 Insert művelet eredményei .......................................................................... 45
6.2 Update művelet eredményei ....................................................................... 47
6.3 Select művelet eredményei ......................................................................... 50
6.4 Kapcsolódás művelet eredményei .............................................................. 53
6.5 Funkciók feltárása és egyéb döntési szempontok ...................................... 54
7.Összegzés ......................................................................................................... 57
8. Summary .......................................................................................................... 59
9. Irodalomjegyzék ............................................................................................... 61
10. Melléklet ......................................................................................................... 63
Java alapú SQL API felületek hatékonyság elemzése
1
1. Bevezető
Napjainkban bármilyen szoftvert vagy alkalmazást veszünk alapul, szinte
bizonyos, hogy a háttérben kapcsolatban áll valamilyen adatbázissal. Gondoljunk
csak bele egy webshopba, ahol az eladni kívánt termék jellemzőit, árát, a raktáron
lévő mennyiséget vagy bármilyen termékhez kötődő adatot tárolnunk kell.
Szintén hatalmas méretű adatbázis állhat egy könyvtári nyilvántartó
rendszer mögött. Itt tárolni kell a kölcsönözhető tartalmak alapvető adatait.
Például: cím, szerző, kiadás éve, ISBN stb. Továbbá a beiratkozott olvasókról is
tárolni kell néhány alapvető adatot, mint a név, születési dátum, lakcím stb. Ahhoz
azonban, hogy a nyilvántartó rendszer hasznosan tudjon működni, további
információkat kell tárolni a kölcsönzésekről is. Egy könyvtárban nem árt tudni,
hogy melyik könyvet mikor és melyik olvasó kölcsönözte ki.
A fent leírt példákat tekintve látható, hogy az adatbázisok és a különböző
adatok kezelése szerves részét képezik egy-egy szoftvernek vagy alkalmazásnak.
Informatikusként az ember ismeri a legelterjedtebb adatkezelő nyelvet (SQL),
melynek segítségével viszonylag könnyedén kezelheti az adatbázist, valamint az
adatokat. Egy SQL parancsfelület nagyon hasznos a különböző SQL parancsok
kiadására, valamint az eredmények megtekintésére. Általánosságban azonban
elmondható, hogy egy webshop adminisztrátora vagy a könyvtáros nem feltétlen
"beszél" adatkezelő nyelvet. Továbbá az SQL parancsfelület nem túl
felhasználóbarát az adatmegjelenítés szempontjából. Nem alkalmas akkor sem,
ha több adatot akarunk bevinni egyszerre vagy a bevitelre kerülő értékeket egy
listából választjuk ki.
Erre a problémára nyújthat megoldást, ha egy felhasználóbarát,
rugalmasan testre szabható alkalmazói programot készítünk. Ezen az alkalmazói
programon keresztül már a webshop adminisztrátor és a könyvtáros is képes
különösebb informatikai előismeret nélkül kezelni az adatbázist és a benne lévő
adatokat.
Egy alkalmazói programnak valamilyen módon el kell érnie a külső
szerveren levő adatbázist. Fejlesztőként már az is okozhat fejtörést, hogy
egyáltalán milyen adatbázis-kezelőt válasszunk. A piacon ugyanis számos
lehetőség kínálkozik. Ha letettük voksunkat egy adatbázis-kezelő mellett, akkor
Java alapú SQL API felületek hatékonyság elemzése
2
további gondolkodásra adhat okot az, hogy alkalmazói programunk milyen módon
fog kommunikálni az adatbázissal. A paletta nagyon széles, sok SQL API létezik
erre a célra.
A szakirodalomban nem igazán kapunk átfogó képet és egyfajta
összevetést a különböző SQL API felületeket tekintve. Mindegyiknek létezik külön-
külön specifikációja, azonban kezdő fejlesztők számára hasznos lehet, ha
olvashatnak valamilyen összegzést.
Ezt az „űrt” szeretném pótolni, ezért dolgozatom első felében - a teljesség
igénye nélkül - áttekintenék néhány SQL API felületet. Ezután bemutatom a
tesztrendszer tervezésének folyamatát és annak implementációját. Végezetül
ismertetem az elvégzett teszteket és azok eredményeit, valamint értékelem a
tapasztaltakat.
Java alapú SQL API felületek hatékonyság elemzése
3
2. Java környezet
A programozás kezdetén az úgynevezett strukturális programozás volt az
irányadó. Mára az objektum orientált szemlélet van előtérben. Természetesen
több objektum orientált programnyelv létezik. Például a C++, C#, Visual Basic,
Java stb. Mivel egy szakdolgozat keretein belül nem célszerű minden egyes OOP
nyelv és a hozzá tartozó SQL API felületek bemutatás, ezért dolgozatomban egy
programnyelvre szűkítem a halmazt. Választásom a Java nyelvre esett.
A Java egy általános célú, objektum orientált programnyelv. [1] Elsődleges
célja az volt, hogy legyen egy olyan programozási nyelv, amely hálózaton
keresztül is biztonságosan használható és lehetőleg platform független.
A Java azonban több, mint egy programozási nyelv. A Java egy tisztán
szoftver megvalósítású platformként is tekinthető, melynek két része van [2]:
Java programozási interfész (Java API)
Java Virtual Machine (JVM)
Tehát azok az eszközök, amelyek képesek futtatni egy JVM környezetet,
képesek lesznek Java nyelven írt programok futtatására is. Ebből következik, hogy
Java kliensek elég széles skálája létezik. További előnye a Java nyelvnek, hogy a
fejlesztéshez szükséges környezet ingyenesen elérhető. Szintén előny, hogy a
nyelvet folyamatosan fejlesztik.
Ezek alapján úgy gondoltam, hogy a Java széles körben használt és ismert
nyelv és platform. Ezért esett erre a választásom.
2.1 JDBC API áttekintés
A Java Database Connectivity egy ipari szabvány adatbázis-független
kapcsolatok kialakítására a Java nyelven írt program és a különböző adatbázisok
között. [3] Segítségével három dolog valósítható meg:
Létrehozza a kapcsolatot egy adatbázis vagy hozzáférhető táblázatos
adatforrás között
Alkalmas SQL parancsok küldésére
Feldolgozza az eredmény halmazt
Java alapú SQL API felületek hatékonyság elemzése
4
Tulajdonképpen Java nyelven íródott osztályokat és interfészeket
tartalmazó csomagok halmaza, melyek lehetővé teszik adatbázis alapú
alkalmazások fejlesztését. Előnye abban mutatkozik meg, hogy platform független,
hiszen bárhol alkalmazható, ahol a JVM (Java Virtual Machine) futtatható.
Másik nagy előnye, hogy bármilyen adatbázis-kezelő rendszerrel
használható. Ehhez szükség van az adott adatbázis-kezelővel kompatibilis JDBC
meghajtó programra (driver). A driver tehát rendszerfüggő. Ezt az adatbázist
gyártó cég honlapjáról kell letölteni.
A JDBC API két fő interfész halmazt tartalmaz: az egyik a JDBC API az
alkalmazás fejlesztők számára, a másik pedig egy alacsonyabb szintű JDBC
driver API a driver fejlesztők részére. Maguk a JDBC driverek pedig négy
csoportba sorolhatóak, melyet az 1. ábra szemléltet.
1. ábra: a JDBC API struktúrája és driver típusai.
Az ábra bal szélső részén található az úgynevezett Direct-to Database Pure
Java Driver. Az ilyen típusú driverek átalakítják a JDBC hívásokat arra a hálózati
protokollra, amelyet az adatbázis-kezelő rendszerek közvetlenül használnak.
Java alapú SQL API felületek hatékonyság elemzése
5
Megengedik a kliens programból történő közvetlen hívásokat, ezzel praktikus
hozzáférést biztosítva.
Ha tovább haladunk az ábrán, akkor a következő driver típus a Pure Java
Driver for Database Middleware. Ezek a típusú driverek lefordítják a JDBC
hívásokat egy middleware szállító protokollra, amely aztán lefordítható adatbázis-
kezelő protokollra. A middleware összeköttetést biztosít a különböző
adatbázisokhoz.
Az ábrán a harmadik driver a JDBC-ODBC bridge. Ez a kombináció a JDBC
hozzáférést egy ODBC driveren keresztül biztosítja. A JDBC-ODBC bridge
alkalmas kísérleti használatra vagy olyan esetekben, amikor nincs más elérhető
driver.
A jobb szélső driver a Native API, mely részben Java technológiát használ.
Az ilyen fajta driverek átalakítják a JDBC hívásokat az adatbázis-kezelő
rendszeren történő kliens hívásokra.
Tehát látható, hogy a JDBC használatához minden esetben szükséges
valamilyen driver regisztrálása a Java alkalmazásunkban.
Miután letöltöttük a megfelelő meghajtót, regisztrálnunk kell azt a Driver
Managerben. Ennek feltétele, hogy a drivert - ami lényegében egy .jar
kiterjesztésű fájl - elérhetővé tegyük a Java keresési útvonalán szereplő
könyvtárban. Ha a driver elérhető a keresési útvonalon, akkor regisztrálható a
Driver Managerben. Ezután létrehozhatjuk az adatbázis kapcsolatot, ha meghívjuk
a DriverManager.getConnection(); metódust, melynek átadjuk az alábbi
paramétereket:
url (host, port és dbname)
username
password
Amint a kapcsolat sikeresen létrejött, a programozónak lehetősége van
SQL parancsokat küldeni az adatbázisnak. A JDBC API nem korlátozza az
elküldhető SQL utasítások halmazát (akár nem SQL parancsokat is
használhatunk). [4] Azt viszont a fejlesztőnek kell biztosítania, hogy az adatbázis
fel tudja dolgozni a kapott utasítást. Erre egy példa: tárolt eljárást JDBC-n
keresztül akkor hívjunk meg, ha biztosak vagyunk benne, hogy a használt
adatbázis támogatja a tárolt eljárásokat, különben kivétel fog keletkezni.
Java alapú SQL API felületek hatékonyság elemzése
6
Az API három interfészt biztosít az SQL kérések küldéséhez:
Statement: paraméter nélküli SQL utasítások hívására szolgál. A
kapcsolat objektum createStatement() metódusával hozható létre.
PreparedStatement: A Statement leszármazottja, a kapcsolat
objektum prepareStatement() metódusával hozható létre. Akkor
használjuk, ha egy utasítást többször is végre akarunk hajtani. A
parancs string paraméterezhető is, ahol a setYYY() (YYY a paraméter
típusa) metódus használható a paraméter konkrét értékének a
megadására.
CallableStatement: A PreparedStatement leszármazottja, a kapcsolat
objektum prepareCall() metódusával hozható létre. Tárolt eljárások
hívására használjuk. Lehetnek kimeneti és bemeneti paraméterei is.
Az API három végrehajtási módot biztosít a programozó számára. Ezek a
következők:
executeUpdate(): adatdefiníciós és adatkezelő utasítások
végrehajtására használjuk (INSERT, UPDATE, DELETE, CREATE,
DROP).
executeQuery(): eredményhalmazt visszaadó parancsok
végrehajtásakor használjuk (SELECT).
execute(): az előző két típus bármelyikének végrehajtására alkalmas.
Láthatjuk, hogy a JDBC egy jól használható, stabil SQL API. Talán nem
véletlen, hogy a fejlesztők körében az egyik legelterjedtebb módszer az
adatkezelés megvalósítására.
Java alapú SQL API felületek hatékonyság elemzése
7
2.2 SQLJ API áttekintés
Az SQLJ egy másik lehetséges szabvány Java környezetben arra, hogy a
programozó kezelhessen egy adatbázist. [5] Három részt tartalmaz:
SQLJ 0. rész: beágyazott, statikus SQL utasítások a Java kódban
(SQL/Object Language Bindings).
SQLJ 1. rész: Az adatbázis szerveren tárolt eljárások meghívása Java
metódusokon keresztül (SQL Routines using in the Java languages).
SQLJ 2. rész: Java osztályok a felhasználó által definiált SQL
típusokra.
A legszélesebb körben az SQLJ 0. része terjedt el, melyet az ISO
szabványosított. A továbbiakban SQLJ alatt az SQLJ 0. részét értem.
A Java fordító nem ismeri a beágyazott SQLJ utasításokat. [6] Ehelyett egy
speciális .sqlj kiterjesztést használ, mely jelzi, hogy a forrás fájl SQLJ fájl. Egy
előfeldolgozási lépésben az SQLJ fordító lefordítja az SQLJ forrás fájlt egy
közbenső Java forrás fájlra, amelyben az SQLJ parancsokat futás időben hívja
meg. A köztes Java forrás fájl már lefordítható a Java fordítóval. Ennek menetét
szemlélteti a 2. ábra.
2. ábra: az SQLJ fordítási struktúrája.
A fejlesztéshez szükség van a korábban már bemutatott JDBC meghajtóra.
Ügyelnünk kell azonban arra, hogy nem minden adatbázis-kezelő támogatja az
SQLJ-t, mely a JDBC-hez képest mindenképpen hátrány. Oracle vagy DB2
adatbázis-kezelő jó választás, míg az ismertebbek közül a MySQL nem támogatja
az SQLJ-t. A megfelelő JDBC meghajtó kiválasztása és regisztrálása után
nekiláthatunk a tényleges kapcsolat felépítésnek.
Létre kell hoznunk egy ConnectionContext objektumot. Minden beágyazott
SQL utasítás az SQLJ programban egy ConnectionContext-ben fut.
Java alapú SQL API felületek hatékonyság elemzése
8
Ez lehetővé teszi, hogy többszörös kapcsolatot alakítsunk ki egy
adatbázissal, vagy több adatbázishoz kapcsolódjunk egy időben. Ennek egyik
módja, hogy DefaultContext() objektumot hozunk létre. Ezt a getConnection()
metódus meghívásával tehetjük meg, melynek az alábbi paraméterei vannak:
url
username
password
autocommit: értéke true vagy false lehet, attól függően, hogy
automatikus commit-ot szeretnénk-e vagy a programozónak saját
magának kell gondoskodnia a commit-ról.
Ha ez sikeres volt, akkor a setDefaultContext() metódussal beállítjuk a
kapott objektumot. Ezután már írhatunk SQLJ utasításokat.
Lehetőség van többszörös adatbázis kapcsolat létrehozására is. [7] Ilyenkor
az alapértelmezett kapcsolat mellett egy nem alapértelmezett kapcsolat is van.
Hogy melyik kapcsolatot akarjuk használni, azt explicite jelezhetjük, ha
hivatkozunk a nevére. Ha nincs névhivatkozás, akkor automatikusan az
alapértelmezett kapcsolaton keresztül hajtódik végre az utasítás.
A továbbiakban szeretném bemutatni a beágyazott SQLJ utasítások
alapvető szintaktikáját. Az utasításnak minden esetben #sqlj-vel kell kezdődnie,
majd { } zárójelek között kell megadni bármilyen érvényes SQL utasítást. A végén
pontosvesszővel kell lezárni.
Léteznek különböző foglalt kulcsszavak, melyek a következők: iterator,
context és with. Ügyelnünk kell arra is, hogy a parancsok case sensitive-ek.
Alkalmazhatunk úgynevezett host változókat is a Java környezet és az SQL
kérések közötti kommunikációra. A host változó tulajdonképpen vagy egy Java
lokális változó, vagy egy osztály változó. Használata esetében kettősponttal (:) kell
kezdődnie a kifejezésnek. Ezt opcionálisan megelőzheti az IN, OUT vagy az
INOUT kifejezések egyike. Fontos, hogy a host változók case sensitive-ek.
SQLJ esetében szintén fontos nyelvi elemek az úgynevezett iterátorok.
Olyan lekérdezések esetében, melyeknél az eredményhalmaz több rekordból áll,
az iterátorok használatával lehetséges az eredmények feldolgozása.
Java alapú SQL API felületek hatékonyság elemzése
9
A fentebb részletezett két módszer együttesen is használható ugyanazon
alkalmazásban, attól függően, hogy éppen dinamikus (JDBC) vagy statikus
(SQLJ) parancsokat szeretnénk használni.
Java alapú SQL API felületek hatékonyság elemzése
10
2.3 JPA API áttekintés
A JPA (Java Persistence API) egy objektum-relációs leképzést biztosít Java
környezetben, hogy alkalmazásokban egyszerűbb módon lehessen az
objektumokat a relációs adatbázisban kezelni [8]. A Java perzisztencia kezelő
keretrendszer három fő területet foglal magában:
A Java Persistence API
Objektum-relációs metaadatok
Lekérdező nyelv (JPQL - Java Persistence Query Language)
Ahhoz, hogy a lekérdező nyelvet alkalmazhassuk, először fontos
megismerkednünk az API struktúrájával. Az alkalmazásunkban létre kell hoznunk
egy EntityManagerFactory objektumot, melynek segítségével inicializálhatjuk a
kapcsolatot az adatbázissal. Ezután szükség van egy EntityManager objektumra.
Minden EntityManager példány egy perzisztencia kontextussal társul. A
perzisztencia kontextus meghatározza azt a hatókört, amely alatt az egyes Entity
példányok létrehozhatók, állandósíthatók és törölhetők.
Tehát az EntityManager példányon keresztül tranzakciót tudunk nyitni,
melyen keresztül kezelhetjük az adatokat. Továbbá lekérdezéseket is
végrehajthatunk (4. ábra).
4. ábra: a JPA API struktúrája. [9]
Java alapú SQL API felületek hatékonyság elemzése
11
Fontos szót ejteni az objektum-relációs leképzésről is, mely nem más, mint
egy programozási technika. Lehetővé teszi az adatok konverzióját valamely
objektum orientált programozási nyelv és valamely nem kompatibilis típusos
rendszer között. A JPA-ban a leképzés az Entity objektumok segítségével valósul
meg.
Az Entity tulajdonképpen egy fő táblát és esetleg néhány melléktáblát
reprezentál az adatbázisból. Minden egyes Entity példány a tábla egy sorának,
azaz egy rekordnak felel meg. Az Entity példány tulajdonságai pedig a tábla
oszlopaival hozhatók párhuzamba.
Ebből adódóan az elsődleges programozási egység egy Entity
megvalósítására az un. Entity osztály. Egy Entity osztálynak a következő
követelményeknek kell megfelelnie:
Az osztályt a @Entity annotációval kell megjelölni.
Az osztálynak implementálnia kell egy public vagy protected paraméter
nélküli konstruktort. Ezen felül természetesen az osztály
implementálhat egyéb konstruktorokat.
Az osztály nem deklarálhatja a final módosítót.
Ha egy Entity példányt érték szerint külső objektumként adunk át,
akkor az osztálynak implementálnia kell a Serializable interfészt.
A perzisztens példányváltozók deklarációjánál csak a protected és a
private módosító használható. Továbbá csak az Entity osztály
metódusain keresztül érhető el közvetlenül.
Az Entity-k kezelésére alkalmas az EntityManager, mely számos
alapműveletet kínál a fejlesztő számára. Ezek közül a legfontosabbak: [10]
persist(): egy új entitás példány perzisztencia kontextushoz való
hozzáadását teszi lehetővé;
merge(): beleolvasztja az adott entitás objektum állapotát az aktuális
perzisztencia kontextusba;
remove(): eltávolítja a paraméterként megadott entitás objektumot;
find(): segítségével entitás objektumokat kereshetünk a perzisztencia
kontextusban (többféle paraméter szignatúra is lehetséges);
Java alapú SQL API felületek hatékonyság elemzése
12
flush(): szinkronizálja a perzisztencia kontextust a mögöttes
adatbázissal;
lock(): a megadott zárolási módon zárolja a perzisztencia
kontextusban levő entitás objektumot (többféle paraméter szignatúra is
lehetséges);
refresh(): frissíti a paraméterül kapott entitás objektum állapotát az
adatbázisból (többféle paraméter szignatúra is lehetséges);
clear(): törli a perzisztencia kontextust;
createQuery(): lekérdező utasítás végrehajtására használhatjuk
(többféle paraméter szignatúra is lehetséges);
createNativeQuery(): natív SQL utasítások végrehajtására alkalmas
(többféle paraméter szignatúra is lehetséges).
Most, hogy már képet kaptunk az Entity fogalomról, foglalkozhatunk a
lekérdező nyelvvel (JPQL), melyet a JPA biztosít. A JPQL - azaz a Java
Persistence Query Language - meghatározza egy Entity lekérdezéseit és
perzisztenciáját. A JPQL lehetővé teszi, hogy olyan hordozható lekérdezéseket
írjunk, amelyek a mögöttes adattárolástól függetlenül működnek. Szintaktikailag
hasonló az SQL nyelvhez. Tehát a JPQL nem közvetlenül az adattáblákat kezeli,
hanem az entitás objektumokat.
A JPQL egészen az alapokon át az összetettebb, finomabb lekérdezések
megvalósítására is lehetőséget ad. Számos olyan hasonló eleme van, melyek már
jól ismertek az SQL nyelvből.
A lekérdezés a select kulcsszóval kezdődik. Ez meghatározza a lekérdezés
által visszaadott elemek típusát vagy értékét. Az SQL-hez és a JINQ-hoz
hasonlóan itt is van lehetőségünk aggregációs kifejezések megadására. Ezek a
következők: sum, count, min, max, avg. Hiányzik azonban a JINQ-ban megismert
többszörös aggregációs kifejezés.
Java alapú SQL API felületek hatékonyság elemzése
13
A lekérdezés tartományának kijelölésére a from kulcsszó szolgál. Ahhoz,
hogy ebből a tartományból szűrni tudjuk az adatokat a where kulcsszó után kell
megadnunk a megfelelő feltételeket. A feltételek megadására rendkívül sok
lehetőség van:
Between: meghatározza, hogy egy aritmetikai kifejezés a megadott két
érték közé esik-e.
In: meghatározza, hogy a vizsgált elem eleme-e a megadott
halmaznak. Alkalmas stringek és numerikus értékek vizsgálatára.
Like: meghatározza, hogy a helyettesítő karakterek illeszkednek-e a
stringre. A vizsgált kifejezésnek stringnek vagy numerikus adatnak kell
lennie. Ha az érték NULL, akkor a Like kifejezés értéke ismeretlen.
Null: azt vizsgálja, hogy egy egyértékű kifejezésnek vagy egy
bemeneti paraméternek van-e NULL értéke.
Empty: azt vizsgálja, hogy egy gyűjtemény üres-e, azaz van-e eleme
vagy sem.
JPQL is támogatja a csoportképzést, melyet a group by utasítással
adhatunk meg. A csoportokban további szűrést végezhetünk a having feltétel
megadásával. Ha a lekérdezés során valamilyen rendezésre van szükségünk,
akkor az order by opciót érdemes használnunk. Az order by esetén két további
kulcsszót kell megismerni:
asc: növekvő sorrendbe rendezi az eredményhalmaz elemeit a
megadott tulajdonság alapján. Ez az alapértelmezett.
desc: csökkenő sorrendbe rendezi az eredményhalmaz elemeit a
megadott tulajdonság alapján.
Az SQL-ben megszokott allekérdezések is megvalósíthatók. Továbbá
lehetséges az adatok módosítása, valamint törlése. Előbbire az update, utóbbira a
delete utasítás használható. Ezen utasítások meghatározzák a módosítandó vagy
kitörlendő Entity típusát. A where feltétel megadásával pedig meghatározható
azon elemek köre, amelyeket módosítani vagy törölni szeretnénk.
Összességében elmondható a JPA, illetve a JPQL használatáról, hogy
szintaktikája nagyon hasonló az SQL nyelvhez. Az SQL által megismert és
használt elemek túlnyomó többsége itt is megtalálható és alkalmazható.
Java alapú SQL API felületek hatékonyság elemzése
14
2.4 JINQ API áttekintés
A .NET világban már évek óta létezik az úgynevezett LINQ (Language
Integrated Query), amely lehetővé teszi, hogy a különböző adatforrásokat
könnyedén, egységesített módon kezelhessük. [11] Napjainkban számos
adatforrás áll rendelkezésünkre, melyek kezeléséhez különböző eszközök
használatát és új nyelveket kell megtanulnunk (SQL, XQuery stb.). A LINQ egyik
nagy előnye, hogy segítségével ugyanolyan módon kezelhetjük az adatelemek
halmazát, függetlenül attól, hogy az adatbázisban, memóriában vagy egy XML-
ben tárolódik. Másik előnye, hogy erősen típusos nyelv, vagyis a legtöbb hiba még
fordítási időben felderíthető és kijavítható.
Jelenleg a LINQ-nak három fő iránya van, melyek a következők:
LINQ to XML: XML dokumentumok kezelését és lekérdezését
biztosítja.
LINQ to Object: memóriában lévő gyűjtemények, listák, tömbök
feldolgozására szolgál.
LINQ to SQL: relációs adatbázisok menedzselését teszi lehetővé az
alkalmazói programok számára.
A fentiekből egyértelműen látszik a LINQ előnye. A Java világban sokáig
nem volt hozzá hasonló alternatíva, azonban a Java 8-as verziójától kezdve
megjelent a JINQ.
Alapvetően a JINQ erősen épít a korábban bemutatott JPA-ra. [12] Éppen
ezért a struktúrája is nagyon hasonló hozzá. A JINQ jelenleg csak annyival tud
többet a JPA-nál, hogy használatával adatbázis lekérdezéseket írhatunk egyszerű
Java szintaktikát alkalmazva. Ugyanaz a kód használható az adatok szűrésére,
mint normál Java adatok esetén. Tehát az adatmanipulációs utasítások esetén a
JPA struktúrája érvényes. A leképzés itt is az Entity objektumokon keresztül
történik, melyeket az EntityManager segítségével kezelhetünk. A struktúra csak a
lekérdezéseknél bővül egy JinqJPAStreamProvider objektummal. Tulajdonképpen
ezen keresztül alkalmazhatóak a JINQ nyújtotta szolgáltatások. Ezt szemlélteti az
5. ábra.
Java alapú SQL API felületek hatékonyság elemzése
15
5. ábra: a JINQ API struktúrája.
Adatbázis tesztelésnél a programozó először megírja a lekérdezést, majd
elindítja az adatbázist és lefuttatja a lekérdezést. Ez körülményes és lassú
folyamat. A JINQ további előnye, hogy a Java fordító már fordítási időben elkapja
a hibás lekérdezéseket, ezzel gyorsítva a fejlesztés menetét. Továbbá, mivel a
lekérdezések Java szintaktikával készültek, így az SQL injection támadások
lehetetlenek, tehát nő a biztonság.
Fontos tisztázni, hogy mi az, ami lefordítható. A JINQ normál Java kódot
használ, de nem minden Java kód fordítható le JINQ adatbázis lekérdezésekre. A
JINQ automatikusan lefordítja a Java kódot adatbázis lekérdezésekre, ezért olyan
műveleteket kell használnunk, amelyek elérhetőek az adatkezelő nyelvben is. Ha
olyan műveleteket használunk, melyek nem lefordíthatóak, akkor a JINQ
egyszerűen futtatja a kódot. A programozó fog eredményhalmazt visszakapni,
azonban a kód nem használja ki a lehetséges teljesítmény előnyeit.
Java alapú SQL API felületek hatékonyság elemzése
16
Ahhoz, hogy a fordítás zökkenőmentes legyen, az alábbi megszorításokat
kell betartani:
A kódunk nem tartalmazhat ciklusokat.
A kódunkban meghívhatunk más metódusokat, de csak azokat, melyek
egy szűkített lista elemei, ismert mellékhatásokkal.
A kódunkban olvashatunk és módosíthatunk lokális változókat (mivel
ezek a változtatások elvesznek, miután a metódus lefutott).
A kódunkban olvashatunk nem lokális változókat, de nem
módosíthatjuk azokat.
Egy alkalmazásban szükség lehet a lekérdezések dinamikus
összeállítására. Erre a JINQ kétféle megoldást kínál. Az egyik a paraméterezés. A
paraméterek lehetővé teszik, hogy egy lekérdezésbe különböző értékeket
helyettesítsünk be. Ezzel különböző lekérdezések jöhetnek létre. A paraméter
változónak lokálisnak kell lennie, mert a JINQ a Java szerializációt használja. Ha
nem lokális változót használunk paraméterként, az problémákhoz vezethet.
A másik dinamikus lehetőség a futásidejű lekérdezés összetétel. Mivel a
JINQ lekérdezés Java kódot használ, nem lehet kivitelezni, hogy futás időben
módosítsuk azt. Arra viszont van lehetőség, hogy a lekérdezés különböző részeit
futás időben rakjuk össze.
A JINQ továbbá képes arra, hogy lefordítson különféle Java metódusokat
ekvivalens JPQL függvényekre. A teljesség igénye nélkül néhány ilyen metódus:
Math.sqrt(); String.toUpperCase(); String.trim(); String.length(); stb.
Mielőtt megvizsgálnám az alapvető JINQ lekérdezéseket, célszerű
betekinteni a lambda kifejezések világába, mivel a JINQ is használ ilyeneket. A
lambda kifejezések tulajdonképpen névtelen metódusok, amelyeket ott írunk, ahol
ténylegesen használjuk is. [13] Lambda kifejezések esetén nem egy olyan
objektumot adunk át, ami megvalósítja a kívánt interfészt, hanem magát a
viselkedést, azaz magát az implementációt. A lambda kifejezések szintaktikája
három részből tevődik össze [14]:
argumentum lista;
nyíl jelkép (arrow token);
Java alapú SQL API felületek hatékonyság elemzése
17
body rész: lehet egyszerű kifejezés vagy utasítás blokk is. Kifejezés
formula esetén a kifejezés egyszerűen kiértékelődik és visszatér az
eredménnyel. Blokk formula esetén a body rész egy metódus
törzséhez hasonlóan működik.
Egy alapvető JINQ lekérdezésben a where() metódus szűri az adatbázis
adatokat. Minden egyes elemre megvizsgálja a feltételként megadott kifejezést.
Ha igaz, akkor visszaadja az elemet, egyébként kihagyja a szűrésből. Továbbá
lehetőség van az or és az and operátorok használatával összetett szűrési
feltételek megadására is.
Előfordulhatnak olyan esetek, amikor nincs szükség a teljes rekord
megjelenítésére, csak bizonyos mezőit szeretnénk lekérni. Erre alkalmas a
select() metódus. A metódusnak van egy olyan funkciója is, mely képes átalakítani
az adatokat, például számításokat végez el velük. Lehetőség van arra is, hogy a
select() metóduson belül elágazásokat használjunk, összetettebb lekérdezéseket
megvalósítva ezzel.
A tipikus és alap JINQ lekérdezéseken kívül sokkal összetettebb és
finomabb lekérdezések írására is alkalmas az API. Az alábbiakban
összefoglalnám a különböző aggregációs lehetőségeket:
sum(): több fajtája van, aszerint, hogy milyen típusú értékeket
szeretnénk összegezni. Például int-sumInteger(); long-sumLong();
double-sumDouble(); stb. A sum() metódus bemenetként egy
függvényt kap. Ez a függvény alkalmazható a stream minden elemére,
és azokkal az értékekkel tér vissza, amelyeket össze kell adni.
count(): a stream-ben lévő tételek megszámlálására használható.
Visszatérési értéke egy Long típusú érték.
min() és max(): a stream-ben a minimum vagy maximum érték
meghatározására használható. Bementként egy függvényt kap. Ez a
függvény alkalmazható a stream minden elemére és azzal az értékkel
tér vissza, amit minimumként vagy maximumként megtalált. Az értékek
szám típusúak vagy más ismert összehasonlítható típus (például
dátum) lehetnek.
avg(): az átlagos érték meghatározására használható.
Java alapú SQL API felületek hatékonyság elemzése
18
aggregate(): többszörös aggregációra használható akkor, ha
ugyanazon a stream-en kell egyidejűleg több különböző aggregációt
elvégezni.
A lekérdezéseknél előfordulhat, hogy az eredményhalmazt nem egy
táblából várjuk. Természetesen a JINQ-ban is van lehetőség az SQL nyelvben
már jól ismert joinra. Szintén kivitelezhető a csoportképzés (group()). A kapott
eredményeket rendezhetjük is (sort()).
Összességében látható tehát, hogy a JINQ még egy viszonylag új SQL API,
hiszen a Java 8-as verziójától kezdődően használható (a lambda kifejezések
miatt), melyet 2014-ben adtak ki. Ennek ellenére rendkívül gazdag és széleskörű
funkciókkal rendelkezik, megkönnyítve ezzel a Java fejlesztők mindennapjait.
Java alapú SQL API felületek hatékonyság elemzése
19
3. Benchmarking, benchmark tesztek
Egy ember bármilyen fontosabb döntés előtt áll, igyekszik körüljárni az adott
témakört, alternatívákat, megoldási lehetőségeket. Számos döntést támogató
algoritmus, technika létezik az élet több területén. Ilyenek például a SWOT
analízis, ABC elemzés vagy a Benchmarking.
Az informatikában állandó kérdés és tényező a teljesítmény. Szinte
valamennyi esetben befolyásolja a fejlesztő vagy a felhasználó döntését. Ugyanis
minden felhasználó azt szeretné, hogy az adott szoftver vagy hardver gyors,
megbízható, magas rendelkezésre állású legyen, azaz jó teljesítményt nyújtson.
A benchmark programok tehát a szoftver- és hardvereszközök, valamint
komplett rendszerek effektív teljesítményének megbecsülésére alkalmas
programok. Ezek a programok megmérik, hogy egy előre definiált műveletsor
végrehajtásához mennyi időre van szükség a tesztelt környezetben. A mért
eredmények alapján pedig következtetnek a vizsgált rendszer effektív
teljesítményére.
Az, hogy a becsült teszteredmények mennyire relevánsak a tényleges
felhasználás mellett, attól függ, hogy a benchmark teszt alatt elvégzett műveletek
jellemzői mennyire állnak közel a valós életbeli felhasználási körülményekhez.
Fontos tehát az ilyen teszteknél, hogy a valós élethez minél közelebbi feltételeket
teremtsünk elő. [15]
3.1 Benchmark tesztek az adatbázis-kezelésben
Adatbázisok és adatbázis kezelés világában is léteznek benchmark tesztek.
Alapvetően két fő irány tesztelése lehetséges: az egyik az adatbázis-kezelő
rendszerek összevetése, a másik pedig a különböző API-k komparációja.
Az adatbázis-kezelő rendszerek esetén többféle elemzés és
összehasonlítás létezik. Az interneten többek között találhatóak összehasonlító
táblázatok arról, hogy az RDBMS:
mely operációs rendszert támogatja;
milyen alapvető funkciókkal rendelkezik;
milyen adat méretre vonatkozó korlátozásai vannak (adatbázis mérete,
táblák mérete, sorok mérete stb.);
Java alapú SQL API felületek hatékonyság elemzése
20
támogatja-e a view kezelést;
milyen adattípusokat és egyéb objektumokat támogat (string, date,
triggerek, eljárások, függvények stb.);
milyen módon korlátozza a hozzáférést és milyen biztonsági
funkciókkal rendelkezik.
A fentiek elsősorban az adatbázis-kezelő rendszerek funkcionalitására
összpontosító elemzések. Természetesen léteznek teljesítményre vonatkozó
benchmark tesztek is.
A TPC (Transaction Processing Performance Council) tranzakció
feldolgozás és adatbázis benchmark teszteket határoz meg. [16] Továbbá
megbízható adatokat és eredményeket szállít az ipar számára. Az alábbiakban - a
teljesség igénye nélkül - szeretnék röviden néhány TPC benchmarkot ismertetni.
A TPC-C egy teljes számítógépes környezetet szimulál, ahol a felhasználók
tranzakciókat hajtanak végre az adatbázison. A teszt egy order-entry környezet
főbb tevékenységei (tranzakciók) köré épül. Ezek a tranzakciók tartalmazzák a
kifizetések rögzítését, ellenőrzik a rendelések állapotát és figyelemmel kísérik a
felhalmozódott készletet a raktárban. A TPC-C öt különböző típusú és
bonyolultságú konkurens tranzakciót foglal magában.
TPC-E: egy OLTP (On-Line Transaction Processing) munkaterhelést
tesztel. Egy brókercéget modellező adatbázist használ, ahol a felhasználók
tranzakciókat generálnak a kereskedéshez és a piackutatáshoz. A TPC-E metrika
tps (transaction per second) értéket ad vissza, mely megmutatja, hogy a szerver
adott idő alatt mennyi tranzakciót képes kezelni.
TPC-DS egy döntést támogató benchmark, amely számos, általánosan
alkalmazható szempontból modellez egy döntéstámogató rendszert, beleértve a
lekérdezéseket és az adatkarbantartást. Ez a benchmark a döntéstámogató
rendszereket illusztrálja, melyekre jellemző, hogy:
nagy mennyiségű adatokat elemeznek;
való üzleti kérdésekre adnak választ;
magas CPU és I/O terhelésnek vannak kitéve;
a végrehajtott lekérdezések széles üzemeltetési követelménnyel és
bonyolultsággal bírnak.
Java alapú SQL API felületek hatékonyság elemzése
21
TPC-H szintén egy döntést támogató benchmark. Az általa jelzett
teljesítménymérési mutató a Query-per-Hour Performance metrika. Ez
meghatározza, hogy az adott méretű adatbázison, melyen a lekérdezéseket
végrehajtjuk, mekkora a lekérdezés végrehajtási sebessége, ha a lekérdezések
egyetlen szálon vagy konkurens módon hajtódnak végre.
TPC-VMS célja egy virtuális környezet reprezentálása, ahol 3 adatbázis fut
egy szerveren. Teszteléskor választanak egy benchmarkot a fentebb bemutatottak
közül, majd futtatják mind a három adatbázison. A három adatbázisnak minden
paraméterben egyeznie kell. A 3 példányon lefuttatott benchmark eredményeinek
minimum értéke adja meg az elsődleges teljesítménymutatót.
TPCx-HS a Big Data technológia tesztelésére alkalmas. A benchmark a hét
minden napján 24 órában folyamatosan rendelkezésre álló rendszert modellez.
Lényegében arra használható, hogy felmérjék a széles körű rendszer-topológiákat
és végrehajtható módszereket technikailag szigorú és közvetlenül
összehasonlítható, gyártótól független módon.
A TPC teszteken kívül léteznek más teljesítmény tesztek is. A MySQL a
saját adatbázis-kezelőihez kínál különböző benchmark tool-okat. Például a DBT2,
ami egy nyílt forráskódú benchmark. Lényegében egy OLTP alkalmazást szimulál
egy nagy raktárkészlettel rendelkező cég számára. Öt különböző tranzakciót
tartalmaz, melyek között van olvasási és írási is. Tesztelhető vele egy MySQL
Server példány, vagy végezhetőek vele nagy, elosztott tesztek számos MySQL
Cluster node-al és MySQL Server példánnyal. Ehhez a DBT2 szkripteket biztosít,
amelyek automatizálják a benchmark folyamatot.
MySQL esetén másik alternatíva a FlexAsynch, amely speciálisan a MySQL
Cluster-ek skálázhatóságának tesztelésére lett kifejlesztve. Továbbá létezik a
SysBench is, amely egy népszerű nyílt forráskódú benchmark nyílt forráskódú
adatbázis-kezelők tesztelésére. Segítségével egyetlen MySQL Server példány
tesztelhető InnoDB vagy MyISAM futási módban. [17]
NoSQL adatbázis-kezelők esetében is léteznek benchmark tesztek. Például
az YCSB (Yahoo Cloud Serving Benchmark), melyet a Yahoo kutatási részlegén
dolgozók fejlesztettek ki 2010-ben. Az eredeti célja az volt, hogy megkönnyítse az
új generációs felhő adattároló rendszerek teljesítmény összehasonlítását, különös
tekintettel a tranzakció feldolgozásból adódó terhelésre. Manapság azonban
gyakran használják NoSQL adatbázis-kezelők relatív teljesítményének
Java alapú SQL API felületek hatékonyság elemzése
22
összehasonlítására. Az interneten konkrét eredményeket, diagramokat lehet
találni NoSQL adatbázisokon elvégzett benchmark tesztekről. [18]
Látható tehát, hogy az adatbázis-kezelő rendszerek esetében számos adat
áll a fejlesztő rendelkezésére, melyek segítségével ki tudja választani a számára
megfelelőt.
Az SQL API felületeket tekintve viszont ez nem mondható el
maradéktalanul. Információk után kutatva arra jutottam, hogy ezen a területen az a
jellemzőbb, hogy egy adott SQL API-n belüli különböző lehetőségeket vizsgálják.
Erre példa a JDBC driverek összevetése vagy az egyes ORM (Object-relational
mapping) programkönyvtárak elemzése.
Tehát a különböző SQL API-k esetében nem igazán található
összehasonlítás és teszt. A továbbiakban a korábban általánosságban ismertetett
Java alapú SQL API felületeket szeretném tesztelni.
Java alapú SQL API felületek hatékonyság elemzése
23
4. Tesztrendszer megtervezése
Bármilyen tevékenység előtt fontos meghatározni és kitűzni különböző
célokat. Természetesen ez a teszteléseknél sem lehet másképpen. Ha nem
definiálunk célokat, akkor tulajdonképpen nem határozzuk meg, hogy a tesztek
végén mit szeretnénk elérni. Ez mindenképpen hiba volna.
4.1 Tesztelés célja
A teszt fő célja a Java gazdanyelven rendelkezésre álló SQL API modulok
teljesítmény- és funkcióvizsgálata. Másodlagos cél, a teszt végén a kapott
eredmények összehasonlítása és ezek alapján valamiféle döntéstámogatás.
Másképpen kifejezve, segítségnyújtás azok számára, akik SQL API felület
választása előtt állnak a saját alkalmazói programjuk fejlesztésénél.
Ki kell emelni, hogy ezen teszt nem lesz teljes körű. Egy-egy API
önmagában is széleskörű szolgáltatásokkal bír, melyeket teljes mértékben
körüljárni, kipróbálni nagy feladat lenne. Egy szakdolgozat keretén belül erre nincs
lehetőség.
Emiatt azt a módszert választottam, hogy néhány lényeges elemre
helyezem a hangsúlyt, melyeket megvizsgálok minden egyes API esetében. Ezzel
szeretnék egy viszonylag átfogó képet adni, ami talán megkönnyítheti a döntést
egy fejlesztő számára.
4.2 Mérhető vagy vizsgálható tulajdonságok
A célok meghatározása után, érdemes konkretizálni azt a néhány elemet,
melyet a teszt során kiemelek.
Véleményem szerint minden egyes SQL API felület esetében két fő
irányban érdemes vizsgálódni. Az egyik lehetőség a hatékonyság, és azon belül a
teljesítmény mérése és összevetése. A másik követhető út pedig a funkcionalitás
elemzése lehet.
Teljesítmény kifejezésére többféle mértékegység létezik (pl. Watt). Az
informatikában releváns teljesítménymutató az idő. Érdemes tehát vizsgálni, hogy
milyen gyorsan, azaz mennyi idő alatt hajtódnak végre az előre definiált
műveletsorok.
Java alapú SQL API felületek hatékonyság elemzése
24
Az SQL API felületekre konkretizálva, egy lehetséges szempont az, hogy
mennyi idő alatt jön létre sikeres kapcsolat az adatbázis és az alkalmazói program
között attól függően, hogy melyik API segítségével kapcsolódunk.
Továbbiakban mérhetjük az adatmanipulációs műveletek végrehajtási idejét
is a méret függvényében. Tehát attól függően, hogy melyik SQL API-val
dolgozunk, eltérés adódhat a beszúrás és módosítás műveletek sebességében.
Ez a sebesség függhet attól is, hogy mennyi adatot kell egyszerre beszúrni vagy
módosítani. Módosítás esetén további befolyásoló tényező lehet a módosítás
mértéke is. Ezalatt azt értem, hogy - egy rekordra nézve - hány mezőt kell
módosítanunk.
Szintén mérhető az adott API-k esetében az adatlekérdező utasítások
végrehajtási ideje. Eltérő eredményeket kaphatunk, ha egy teljes táblát,
összekapcsolt táblákat (join művelet) kérdezünk le vagy valamilyen feltétel alapján
szűrjük az adatokat. Természetesen ez is függhet a mérettől. Ugyanis nem
mindegy, hogy a lekérdezéskor hány rekordon kell végigfutni, illetve hány rekord
lesz az eredményhalmazban.
Az eddigiekben a teljesítmény szempontjából vizsgálható paramétereket
vettem sorra. Korábban említettem, hogy a másik követhető út a funkcionalitás.
Ide tartozhat az adott SQL API-val megvalósítható műveletek köre. Például van-e
lehetőségünk az adott API-val tárolt eljárásokat, függvényeket meghívni.
Lehet vizsgálni az adott API használatának egyszerűségét is. Ez egy
nagyon szubjektív szempont, ugyanis nem minden embernek jelenti ugyanazt az
egyszerűség. Jelen esetben az egyszerűség "mérésére" feltérképezhetjük a
működéshez szükséges osztályok és metódusok számát, valamint összevethetjük
a kód hosszát is.
4.2.1 Tesztelési környezet
Egy jégkocka olvadási ideje szobahőmérsékleten valószínűleg lassabb
lesz, mint egy 90 °C-ra felfűtött szaunában. Éppen ezért minden teszt vagy
kísérlet előtt fontos tisztázni a tesztelési körülményeket.
Java alapú SQL API felületek hatékonyság elemzése
25
A teszt szempontjából nem közömbös, hogy milyen operációs rendszeren,
milyen CPU teljesítmény stb. mellett fut. Éppen ezért, meghatározó tényező lehet
annak a hardver- és szoftverkörnyezetnek a jellemzői, melyen a tesztelés zajlik.
A vizsgálataim során alkalmazott hardver specifikációja:
CPU: Intel Core i7-4790 processzor (4 mag; 8 szál; 3,6 GHz)
Alaplap: Gigabyte H97M-HD3
Memória kapacitás: 8 GB
Szoftver oldali specifikáció:
Operációs rendszer: Windows 7 Professional, 64 bit
Fejlesztő környezet:
o Eclipse Luna
o IBM Data Studio 4.1.1
o Oracle Database 11g Express Edition
Választásom azért esett az Eclipse-re, mert ezt a fejlesztő környezetet
használtam már, ezt ismerem a legjobban. Természetesen az Eclipse-nek több
verziója is van (Juno, Kepler, Luna), de mivel a JINQ API csak a Java 8-as
verziójával kompatibilis, ezért olyan Eclipse-re volt szükségem, amely 1.8-as
fordítóval rendelkezik. Jelenleg erre - bármiféle egyéb trükközés nélkül - csak a
Luna verzió képes.
Az IBM Data Studio használatát az SQLJ felület tesztelése indokolja.
Alapvetően az Eclipse fejlesztő környezet nem ismeri fel a .sqlj kiterjesztésű
fájlokat, mert nem rendelkezik integrált SQLJ fordítóval. Első körben próbáltam
megoldásokat keresni arra, hogy az Eclipse-ben tudjak SQLJ-t használni. Nem
találtam semmilyen alkalmas plugint ehhez. Csupán egy Maven pluginra
bukkantam, de ennek használatát bonyolultnak tartottam. Ekkor akadtam rá az
IBM Data Studiora, amely lényegében egy Eclipse környezet, kiegészítve néhány
plusz funkcióval az adatbázisok és adatkezelés terén. Ez a megoldás
egyszerűbbnek tűnt, ezért választottam az IBM Data Studio használatát.
Jogosan merülhet fel a kérdés, hogy ha az IBM Data Studio is Eclipse
alapú, akkor miért van szükség külön Eclipse környezetre is és miért nem elég
csak az IBM Data Studio. Erre a magyarázat szintén a JINQ, ugyanis az IBM Data
Java alapú SQL API felületek hatékonyság elemzése
26
Studioba integrált Eclipse csak 1.7-es fordítóval rendelkezik, ami a JINQ
kipróbáláshoz nem elegendő.
Szintén fontos tényező az adatbázis-kezelő rendszer kiválasztása. A
jelenlegi esetben viszonylag egyszerű dolgom volt, ugyanis az SQLJ felületet csak
az Oracle adatbázis-kezelő rendszer támogatja. A gyártó honlapján elérhető
egyetlen ingyenes verzió az Oracle Database 11g Express Edition volt, így más
lehetséges alternatíva nem jöhetett szóba.
4.3 Tesztelés módszere
Először egy tesztadatbázist kell létrehozni, majd egy alkalmazói programot.
Az alkalmazói programnak képesnek kell lennie a különféle műveletek
kiválasztására és azok végrehajtására. Ezek után az alkalmazói programot
összekapcsoljuk a teszt adatbázissal a különböző SQL API felületeket használva.
Ha ez sikerült, akkor valamilyen módon meg kell tudnunk mérni a műveletsorok
végrehajtási idejét.
Az idő mérését szintén az alkalmazói programban valósítom meg, minden
esetben a rendszeridőt alapul véve. Mielőtt elindul a műveletsor végrehajtása
lekérem a rendszeridőt, majd miután teljesült a műveletsor, ismét lekérem a
rendszeridőt. A két idő különbsége fogja megadni a műveletsor végrehajtási idejét.
A mért értékek minden esetben másodperc mértékegységben lesznek megadva.
A mérés hibájának kiküszöbölése - pontosság javítása - érdekében minden
mérést 10 egymást követő alkalommal fogok megismételni. A mért értékeket
táblázatban rögzítem, majd különféle statisztikai mutatókat számolok belőlük.
Ezen mutatók legyenek a következők:
mért értékek számtani középértéke (átlaga);
mért értékek szórása;
mért értékek terjedelme.
Java alapú SQL API felületek hatékonyság elemzése
27
Számtani vagy aritmetikai középértéken n darab szám átlagát, vagyis a
számok összegének n-ed részét értjük. Kiszámítása:
A szórás az egyes értékek számtani átlagtól vett eltéréseinek négyzetes
átlaga, vagyis megmutatja, hogy az ismert értékek mennyivel térnek el átlagosan
az átlagtól. Kiszámítása:
A legnagyobb illetve a legkisebb mért érték különbségét terjedelemnek
nevezzük. Kiszámítása:
A mérés és a statisztikai mutatók kiszámítása után, összegzésként készítek
néhány szemléltető diagramot. Ezt követően értékelem a kapott eredményeket.
Java alapú SQL API felületek hatékonyság elemzése
28
5. Implementáció
Ebben a fejezetben a tényleges megvalósításról, az alkalmazói program
kialakításáról és az egyes SQL API felületekkel történő működtetéséről lesz szó.
5.1 Általában az adatbázis programozásról
Az alkalmazói programon keresztül történő adatkezelés lépései (6. ábra):
1. Kapcsolódás az adatbázishoz: kapcsolódás paramétereinek
megadása.
2. Lekérdezés: magába foglalja az SQL kérés összeállítását és
elküldését.
3. Eredmények feldolgozása: ebben a lépésben végezzük el az
eredményhalmaz rekordonkénti bejárását, valamint az értékeket
programváltozókhoz rendeljük.
4. Hibakezelés: feldolgozzuk az esetleges alkalmazás vagy adatbázis
hibákat, illetve elkapjuk a keletkezett kivételeket.
5. Kapcsolat bontása, erőforrások felszabadítása: lezárjuk az
eredményhalmazt, az SQL kérést és a kapcsolatot.
6. ábra: alkalmazói program kapcsolódása az adatbázishoz SQL API-val.
5.2 Tesztadatbázis létrehozása
A teljesítménymérés szempontjából lényegtelen az adatbázis szerkezete és
a benne lévő adatok jelentéstartalma. Az egyetlen kritérium a join művelet
teszteléséből fakad, azaz az adatbázisban legalább két táblának kell lennie.
Java alapú SQL API felületek hatékonyság elemzése
29
Azért, hogy mégse legyen teljesen "értelmetlen" az adatbázis, egy egyszerű
sémát találtam ki, melynek relációs modelljét a 7. ábra szemlélteti.
7. ábra: a tesztadatbázis relációs modellje.
Fontos az ábrán jelzett mezők típusa, hiszen a különböző műveletek
tesztelésénél ügyelnünk kell arra, hogy a véletlenszerűen generált adatok típusa
és a mezők típusa megegyezzen. Például a Person tábla Name mezőjénél a
generált adatoknak szöveges típusúaknak, míg az Age mező esetében egész
számoknak kell lenniük, különben hiba keletkezik.
Az ábrán az is látható, hogy a táblák között 1:N kapcsolat áll fent, amely
lehetővé teszi majd a későbbiekben a join művelet tesztelését. Az 1:N kapcsolat
azt jelenti, hogy a Person tábla Workspace mezője csak olyan értéket vehet fel,
amely már létezik a Workspace tábla Name oszlopában, egyéb esetben hibát
kapunk. Ha az adatokat véletlenszerűen generáljuk, akkor előfordulhat olyan eset,
amikor a két érték nem egyezik meg. Annak érdekében, hogy az adategyezés
mindig teljesüljön, a Workspace táblát inicializáltam néhány rekorddal. A Person
táblába történő beszúrás előtt, lekérdezem a Workspace tábla Name oszlopát,
majd az eredményhalmazból véletlenszerűen kiválasztok egyet. A kiválasztott
adat lesz a beszúrandó rekord Workspace mezőjének értéke.
Tehát a teszt elején az induló adatbázis két táblával rendelkezik. A
Workspace táblában található néhány rekord, míg a Person tábla teljesen üres.
5.3 Az alkalmazói program implementálása
Mivel szerettem volna, hogy a négy API használata élesen elkülönüljön
egymástól, ezért mindegyik API-hoz külön hoztam létre egy-egy alkalmazói
programot. Ezt a döntést a kód átláthatóságával, valamint a JINQ és az SQLJ által
igényelt körülményekkel indokolnám (vö.: 4.1 Tesztelési környezet).
Java alapú SQL API felületek hatékonyság elemzése
30
Ahhoz azonban, hogy a teszt szempontjából lényeges időt ne befolyásoljam
(vagy annak mértékét minimálisra csökkentsem) az utasítások számával, a
program API-tól független részeit ugyanúgy implementáltam.
Ilyen független rész maga a program felépítése, beleértve a felhasználói
felületet és a vezérlő szerkezeteket is. Igyekeztem egy letisztult, bárki számára
könnyen kezelhető GUI-t összerakni. Ehhez a SWING widget toolkitet hívtam
segítségül. A sikeres használathoz az Eclipse-hez telepíteni kellett egy
Windowbuilder plugint. Ezután már könnyedén alkalmazhattam a SWING nyújtotta
grafikus komponenseket.
Az alkalmazás felhasználói felülete lényegében három osztályból áll,
melyeket a frames csomag tartalmaz:
FrmMain: az alkalmazás főablaka;
FrmTable: a lekérdezés eredményeit megjelenítő ablak;
FrmStatistic: a mért eredményeket és a statisztikai mutatókat
megjelenítő ablak;
FrmLogin: JDBC és SQLJ API esetében a kapcsolódáshoz szükséges
felhasználónév és jelszó megadására szolgáló ablak.
8. ábra: az alkalmazás főablakának (FrmMain) képe.
A 8. ábra az alkalmazás fő ablakának felépítését (FrmMain) szemlélteti,
amely három menüpontot tartalmaz a beszúrás, módosítás és lekérdezés
műveletek kiválasztásához. Ha kiválasztjuk a tesztelni kívánt műveletet, akkor
rádió gombok segítségével jelölhetjük ki, hogy hány rekordot szeretnénk beszúrni,
Java alapú SQL API felületek hatékonyság elemzése
31
melyik táblát szeretnénk lekérdezni vagy mely mezőket szeretnénk módosítani.
Mindhárom műveletnél megtalálható egy gomb is a felületen, melyre rákattintva
elindíthatjuk a kiválasztott műveletsor végrehajtását.
További közös GUI elem a lekérdezések eredményeit megjelenítő táblázat
ablak (FrmTable). Az ablakban a táblázat fejléce aszerint változik, hogy a
lekérdezés műveletet az adatbázis mely tábláján vagy tábláin hajtottuk végre.
Tehát, ha a lekérdezés a Person táblára vonatkozott, akkor a táblázat fejléce a
Person tábla oszlopainak nevét fogja tartalmazni. Ugyanez mondható el a
Workspace tábla esetében is. Join művelet végrehajtása során a tábla fejléce a
Person és Workspace tábla oszlop neveinek együtteséből fog állni. Ezt
szemléltetem a 9. ábra segítségével.
9. ábra: a Workspace tábla lekérdezésének eredményét megjelenítő ablak (FrmTable).
Az ablak fejlécében látható felirat megmutatja, hogy melyik SQL API-t
használtuk a lekérdezéshez, valamint jelzi az eredményhalmaz számosságát is.
A harmadik közös GUI elem (FrmStatistic) a mért adatok táblázatban való
megjelenítését, valamint az ezen adatokból számolt statisztikai mutatókat jeleníti
meg. Ahhoz, hogy a mutatók megjelenjenek, a felhasználónak meg kell nyomnia a
"Mutatók kiszámítása" feliratú gombot. Az ablak fejlécében szintén olvasható,
hogy melyik API statisztikáját látjuk. Ezt szemlélteti a 10. ábra.
Java alapú SQL API felületek hatékonyság elemzése
32
10. ábra: a statisztikai eredményeket megjelenítő ablak (FrmStatistic).
A korábban említett frames csomagon kívül, minden alkalmazásban van
egy technológia_api csomag is, például: jdbc_api, jpa_api. Ebben a csomagban
minden esetben megtalálható az alábbi két osztály:
StatisticMaker osztály;
DataManager osztály.
A StatisticMaker osztály minden SQL API esetében azonos kivitelezésű és
ugyanazokat a metódusokat tartalmazza. A metódusok ugyanúgy vannak
megvalósítva minden API esetében, hiszen az osztály feladata független attól,
hogy melyik SQL API felületet használjuk. Az osztály metódusai a következők:
getAvarage(): paraméterként megkapja a mért értékek listáját (egész
számokra tipizált List objektumot). A paraméter alapján, egy ciklus
segítségével összegzi a lista elemeit, majd elosztja az elemszámmal.
Tehát ez a metódus a mért értékek átlagával tér vissza.
getDeviation(): paraméterként ugyanazon mért értékek listáját kapja
meg, mint a getAvarage(). A metódus törzsén belül meghívódik a
getAvarage() metódus, mely visszatér az átlaggal. Az átlag alapján
pedig egy ciklus segítségével kiszámoljuk a szórást. Tehát ez a
metódus lényegében a mért értékek szórásával tér vissza.
getRange(): paraméterként szintén a mért értékek listáját kapja meg.
Ezután ciklusok segítségével megkeresi a maximum és minimum
Java alapú SQL API felületek hatékonyság elemzése
33
értékét, majd veszi ezek különbségét. Tehát a metódus lényegében a
mért értékek terjedelmével tér vissza.
A DataManager osztály már nem független az egyes SQL API felületektől,
hiszen ebben az osztályban valósítom meg az egyes műveleteket. Ennek ellenére
az egyes SQL API felületekre nézve, az osztály felépítése ugyanolyan.
Ugyanazon metódusokat tartalmazza, az eltérés a metódusok törzsében van,
kivétel az adatok generálásért felelős metódus. Tehát az osztály az alábbiakat
tartalmazza:
Kapcsolódást tesztelő metódus:
o Connect(): paraméterként egy egész számot kap. Ezután
annyiszor kapcsolódik az adatbázishoz, amennyi a
paraméter értéke. Visszatérési értéke logikai típusú.
Amennyiben a kapcsolódás sikeres volt, akkor true
konstanssal tér vissza, egyéb esetben false konstanst ad
vissza.
Adatok generálásáért felelős metódus:
o stringGenerator(): paraméterként egy egész számot kap.
Ezután egy előre definiált karakterláncból annyi
véletlenszerű elemet választ ki, mint a paraméterként kapott
szám. Lényegében az adatbázis szöveges típusú mezőinek
értékét állítja elő és azzal tér vissza.
o Szám típusú adatok előállítása: erre a Java nyelvbe
beépített Math osztály random() metódusát használom. Ez a
metódus 0-1 közötti véletlen valós számot állít elő. Ezen
értéket szorzom 100-al, ekkor 0-100 közötti valós számot
kapok, majd az értéket egész számmá konvertálom.
Java alapú SQL API felületek hatékonyság elemzése
34
Adatmanipulációért és adatlekérdezéséért felelős metódusok
általánosan:
o getWorkpace(): nincs paramétere. Lekérdezi a Workspace
tábla Name oszlopát, majd visszatér az eredménnyel. A
metódusra azért van szükség, hogy mindig megfelelően
történjen az adategyezés a táblák közötti 1:N kapcsolat miatt
(vö.: 5.1 Tesztadatbázis létrehozása).
o initTable(): nincs paramétere. A Person táblába történő
beszúrás előtt inicializálja a táblát. Lényegében az adatok
törlésével üres táblát biztosít a beszúráshoz. Ha az
inicializálás (törlés) sikeres volt, akkor true értékkel tér
vissza. Ha az inicializálás valami miatt nem történt meg,
akkor false értéket ad vissza.
o Insert(): paraméterként egy egész számot kap. Ezután a
Person táblába annyi rekordot szúr be, amennyi a paraméter
értéke. Ha a művelet sikeres volt, akkor true értékkel,
egyébként false-al tér vissza.
o Update(): paraméterként egy szöveget kap. Ez a szöveges
paraméter megmutatja, hogy a Person tábla mely oszlopát
vagy oszlopait szeretnénk módosítani. Ha a módosítás
sikeres volt, akkor true-val tér vissza, egyébként pedig false
értékkel.
o Select(): paraméterként szöveget kap. Ez a szöveges
paraméter megmutatja, hogy a lekérdezés mely táblára
vonatkozik, illetve jelzi, hogy teljes táblás, egykulcsos (where
feltétel) vagy join típusú lekérdezésről van-e szó. Ha a
lekérdezés sikeres volt, akkor a metódus visszatér az
eredményhalmazzal, ha viszont sikertelen volt, akkor null
értéket ad vissza.
Szintén független az SQL API felületektől a műveletek végrehajtási idejének
mérése. Erre nem hoztam létre külön osztályt, hanem mindig a rendszeridőt alapul
véve határozom meg egy műveletsor végrehajtási idejét.
Java alapú SQL API felületek hatékonyság elemzése
35
boolean flag = dm.initTable();
if(flag == true){
long start = System.currentTimeMillis();
long passed;
if(rdbtnTenThousand.isSelected()){
boolean bool = dm.Insert(10000);
if(bool == true){
long end = System.currentTimeMillis();
passed = (end-start)/1000;
}
}
}
11. ábra: az időmérést szemléltető kódrészlet.
A 11. ábrán lévő kódot megnézve látható, hogy először törlöm a táblát
(dm.initTable() metódus). Ha ez sikeres volt, akkor lekérem a rendszeridőt és
eltárolom egy változóba (System.currentTimeMillis() értékének start változóba
történő tárolása). Mivel csak a tábla törlése után kérem le a rendszeridőt, ezért a
törlés ideje nem lesz benne a végrehajtási időben. Annak értéke ténylegesen csak
a beszúrás művelet idejét fogja tükrözni. Ezután megvizsgálom, hogy hány
rekordot kell beszúrni a táblába, majd meghívom a beszúrásra alkalmas Insert()
metódust. Amint ez a metódus sikeresen végrehajtódik, akkor ismételten lekérem
a rendszeridőt és tárolom egy változóba (System.currentTimeMillis() értékének
end változóba történő tárolása).
Ezen a ponton érdemes kitekintést ejtenünk a rendszeridőt lekérdező
metódusról:
currentTimeMillis(): a metódus visszaadja a hívás pillanatában, 1970.
január 1. óta eltelt időt milliszekundumban mérve. A visszatérési érték
típusa egy long típusú szám.
Visszakanyarodva, jelenleg megvan a két időpillanat milliszekundumban
mérve. Az eltelt idő - azaz a művelet végrehajtási ideje - a későbbi és a korábbi
idő különbsége lesz. Mivel az eredményt másodpercben szeretnénk megkapni,
ezért gondoskodnunk kell a megfelelő átváltásról. Egy milliszekundum egy
másodpercnek az 1/1000-ed része, tehát a milliszekundum értéket osztani kell
1000-el, ekkor másodpercben kapjuk meg az értéket.
Java alapú SQL API felületek hatékonyság elemzése
36
public boolean Connect(String host, String port, String db, String username,
String password){
this.host = host;
this.port = port;
this.db = db;
this.username = username;
this.password = password;
try{
DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
String connstr = "jdbc:oracle:thin:@" + this.host + ":" +
this.port + ":" + this.db;
conn = DriverManager.getConnection(connstr, username, password);
return true;
}
catch(SQLException ex){
return false;
}
}
A következőkben szeretném röviden végigvenni minden egyes SQL API
felület esetében a konkrét implementáció menetét. Továbbá rátérnék - a korábban
általánosságban - említett adatmanipulációért és lekérdezésért felelős metódusok
SQL API specifikus részletezésére.
5.3.1 JDBC API implementáció
Az Eclipse Luna megfelelő beállítása után, letöltöttem az Oracle 11g
Express Edition adatbázis-kezelő rendszerrel kompatibilis drivert az Oracle
honlapjáról. Ezután létrehoztam egy normál Java projektet az Eclipse-ben, majd a
számítógépen a projekt mappájába bemásoltam a letöltött drivert (ojdbc6.jar).
Ezzel a drivert elérhetővé tettem a Java keresési útvonalán.
Következő lépésben létrehoztam egy Connect osztályt, amely az
adatbázishoz történő kapcsolatért felelős. Itt regisztráltam a drivert, melyet az
alábbiakban egy rövid példakóddal szemléltetnék (12. ábra).
12. ábra: JDBC kapcsolat felépítését szemléltető kódrészlet.
A Connect() metódus paraméterként megkapja a kapcsolat létrehozásához
szükséges adatokat. Ezután a try blokkban regisztráljuk a drivert. Majd a
getConnection() metódussal kapcsolatot kérünk a Driver Manager-től.
Amennyiben ez sikeres volt, akkor true értékkel térünk vissza. Ha a kapcsolat
felépítése sikertelen volt, akkor a catch ágban false értékkel térünk vissza.
Java alapú SQL API felületek hatékonyság elemzése
37
public boolean Insert(int record_number){
String[] workspaces = getWorkspace();
try{
PreparedStatement pstmt = conn.getConn().prepareStatement("insert
into person values(?, ?, ?, ?)");
for(int i=0; i
Java alapú SQL API felületek hatékonyság elemzése
38
Az Update() metódus a paramétertől függően dönti el, hogy melyik mezőt
vagy mezőket módosítja. Itt is egy PreparedStatement utasítást használok a
módosítás végrehajtásához. Ez a metódus is logikai értékkel tér vissza.
A Select() metódus ugyancsak a kapott paraméter alapján dönti el, hogy
melyik táblán vagy milyen módon szeretnénk végrehajtani a lekérdezést. Ha ez
sikerül, akkor egyszerű Statement utasítást használok a lekérdezés
végrehajtásához. Az eredményt egy ResultSet típusú objektumban tárolom el és
ez lesz a metódus visszatérési értéke is.
Összességében a JDBC API használatához egyedül a fent említett .jar fájl
volt szükséges.
5.3.2 SQLJ API implementáció
Az IBM Data Studio letöltése és telepítése után - a JDBC analógiájára -
csináltam egy normál Java projektet, majd hozzáadtam a projekthez a JDBC
drivert és a fent leírt Connect() osztály segítségével csatlakoztam az
adatbázishoz.
Ezután az sqlj_api csomagban létrehoztam egy DataManager.sqlj fájlt.
Fontosnak tartom kiemelni a fájl kiterjesztését. Ugyanis a fejlesztés során végig a
DataManager.sqlj fájlt szerkesztettem, ebbe írtam a Java kódot és az SQLJ API
segítségével az egyes műveleteket. A fejlesztő környezet pedig ezen fájl alapján
automatikusan generált egy DataManager.java kiterjesztésű fájlt.
A DataManager.sqlj fájlban elsőként létrehoztam egy DefaultContext
objektumot. A Connect() metódus implementálása egy az egyben megegyezik a
JDBC API-nál leírtakkal.
A getWorkspace() metódus itt is egy String tömbbel tér vissza. Először
lekérdezem, hogy hány rekord van a Workspace táblában. Ezt egy normál SQL
utasítás segítségével lehet megtenni. Mindössze arra kell figyelni, hogy #sqlj-vel
kezdődjön, majd pedig meg kell adni a kontextust. Ezután lekérdeztem a Name
oszlop értékeit. Ahhoz, hogy az eredményhalmaz elemeit el tudjam tárolni a String
tömbbe, egy iterátorra volt szükségem. Az iterátort még a metódus előtt
definiáltam.
Java alapú SQL API felületek hatékonyság elemzése
39
#sql public iterator PersonIt(int id, String name, int age, String workspace);
#sql public iterator WorkspaceIt(String name, String city, int numofemp,
String address);
#sql public iterator JoinIt(int id, String name, int age, String workspace,
String city, int numofemp, String address);
public ResultSet Select(String table){
try{
ResultSet rs;
if(table.equals("person")){
PersonIt pit = null;
#sql[ctx] pit = {select * from person};
rs = pit.getResultSet();
}
else if(table.equals("workspace")){
WorkspaceIt wit = null;
#sql[ctx] wit = {select * from workspace};
rs = wit.getResultSet();
}
else if(table.equals("join")){
JoinIt jit = null;
#sql[ctx] jit = {select * from person inner join workspace
on person.workspace = workspace.name};
rs = jit.getResultSet();
}
else{
PersonIt pit = null;
#sql[ctx] pit = {select * from person where age >50};
rs = pit.getResultSet();
}
return rs;
}catch(SQLException ex){
return null;
}
}
Az initTable() metóduson belül egy egyszerű SQL utasítást használtam,
szintén ügyelve arra, hogy #sqlj-vel és a kontextus megadásával kezdődjön. A
visszatérési érték logikai típusú.
Az Insert() metódus szinte ugyanúgy működik, mint a JDBC esetében. Az
Update() metódus esetében is ugyanez mondható el.
A Select() metódus ResultSet objektummal tér vissza. Ahhoz azonban,
hogy ez megvalósulhasson plusz lépéseket kellett beiktatni a JDBC-hez képest.
Elsőként iterátorok definiálására volt szükség. Ezt követően az iterátorkból sikerült
ResultSet típusú objektumban eltárolni az eredményhalmazt. Ezt az alábbi
példakóddal szemléltetem (14. ábra).
14. ábra: a Select() metódus megvalósítása SQLJ-vel.
Összességében elmondható, hogy az SQLJ nagyban hasonlít a JDBC
használatához. Talán az iterátorok jelentik az egyetlen különbséget.
Java alapú SQL API felületek hatékonyság elemzése
40
5.3.3 JPA API implementáció
Az Eclipse-ben egy JPA projektet hoztam létre, melyhez az eddigieknél
több előkészületre volt szükség. Le kellett töltenem egy objektum-relációs
leképzést biztosító programkönyvtárat. Az ingyenesen elérhető alternatívák közül
az Eclipselink-et választottam. A projekten belül létrehoztam egy User Library-t
eclipselink néven, melybe bemásoltam az alábbi két .jar fájlt: eclipselink.jar és
javax.persistence_2.1.0.v201304241213.jar. Továbbá itt is szükség volt a
korábban is használt JDBC driver alkalmazására.
A projekten belül létezik egy META-INF könyvtár, amiben van egy
persistence.xml állomány. Ebben az XML fájlban inicializáltam az adatbázis
kapcsolatot.
Ezután létrehoztam egy entities nevű csomagot. Ebbe a csomagba, az
Eclipse segítségével generáltam két Entity osztályt. Az egyik a Person osztály,
amely az adatbázisban levő Person táblának felel meg, a másik pedig a
Workspace osztály, ami a Workspace tábla leképzése.
A következőkben a DataManager osztályt implementáltam. Itt elsőként egy
EntityManagerFactory-t, majd egy EntityManager-t kellett létrehozni. Ezek után
következett az egyes metódusok implementálása az osztályban.
Elsőként a Connect() metódust definiáltam. Itt is egy ciklus fut le annyiszor,
amennyi a kapott paraméter értéke. A cikluson belül létrehozok egy
EntityManagerFactory objektumot, majd pedig bezárom azt. Ehhez a
persistence.xml fájlban definiált kapcsolódási adatokat használom. Ha sikeres volt,
akkor true értékkel tér vissza a metódus.
Következőként a getWorkspace() metódust definiáltam, mely egy List
objektummal tér vissza. A lista Workspace típusú entity objektumokat tárol. A
lekérdezést az EntityManager createQuery metódusával valósítottam meg.
Az initTable() metódus törzsében az EntityManager segítségével egy
tranzakció folyamot nyitok. Ezután az előbb említett createQuery metódus
segítségével kijelölöm a törlés utasítást. Ez csak akkor hajtódik végre, ha az
executeUpdate utasítást is kiadjuk, továbbá a tranzakció folyamot commit()
paranccsal zárjuk. Amennyiben sikeres volt a művelet, akkor a metódus true
értékkel tér vissza, egyéb esetben pedig false-al.
Java alapú SQL API felületek hatékonyság elemzése
41
Az Insert() metódus paraméterként egy számot kap, mely alapján eldönti a
beszúrandó rekordok számát. A metóduson belül elsőként létrehozok egy-egy
Workspace és Person entity példányt, majd eltárolom a getWorkspace() metódus
visszatérési értékét. Következő lépésben ciklust indítok, amelyen belül az
EntityManager segítségével tranzakció folyamot nyitok, majd beállítom a beszúrni
kívánt Person entity értékeit. Ezek után az EntityManager persist() metódusával
beszúrom az adott entity-t és a commit() utasítással véglegesítem. A művelet
sikerességétől függően a metódus logikai értékkel tér vissza. Ezt szemléltetem a
15. ábrával.
15. ábra: az Insert() metódus megvalósítása JPA-val.
Az Update() metódus a kapott paramétertől függően dönti el, hogy melyik
mezőt vagy mezőket módosítja. Ezután az EntityManager segítségével ismét egy
tranzakció folyamot nyitok. Ha ez sikeres volt, akkor a következő lépésben a
creatQuery() metódussal kijelölöm az SQL update utasítást, majd a
setParameter() metódus segítségével beállítom az SQL update utasítás
paramétereit. Végül az executeUpdate() és commit() utasításokkal végrehajtom a
módosítási műveletet. Ez a metódus is logikai értékkel tér vissza a végrehajtott
művelet sikerességétől függően.
public boolean Insert(int record_number){
Person p = new Person();
Workspace w = new Workspace();
List list = getWorkspace();
try{
for(int i=0; i
Java alapú SQL API felületek hatékonyság elemzése
42
A Select() metódus paraméterként egy szöveges értéket kap. Ez alapján
meghatározza, hogy melyik táblákat vagy milyen módon (join vagy where) kell
elvégezni a lekérdezést. A lekérdezésre itt is a createQuery() metódust
használom, kivéve a join típusú lekérdezések esetében. Join végrehajtása során a
creatNativeQuery() utasítást használom, melynek segítségével normál SQL join
utasítást lehet végrehajtani. Ahhoz, hogy ezt használhassam, létrehoztam egy
Join entity osztályt, melynek adattagjai a Person és Workspace entity objektumok
adattagjainak összessége. A metódus minden esetben egy Query típusú
objektummal tér vissza. Ezt szeretném bemutatni az alábbi kódrészlettel (16.
ábra).
16. ábra: a Select() metódus megvalósítása JPA-val.
Összességében a JPA jól használható az objektum orientált Java nyelvvel,
hiszen az objektum-relációs leképzés erre nagyszerű lehetőséget ad.
Szintaktikailag sem bonyolultabb, mint a JDBC API.
public Query Select(String table){
try{
Query query;
if(table.equals("person")){
query = em.createQuery("Select p from Person p",
Person.class);
}
else if(table.equals("workspace")){
query = em.createQuery("Select w from Workspace w",
Workspace.class);
}
else if(table.equals("join")){
query = em.createNativeQuery("select * from person inner
join workspace on person.workspace =
workspace.name", Join.class);
}
else{
query = em.createQuery("Select p from Person p Where p.age
> 50", Person.class);
}
return query;
}
catch(Exception ex){
return null;
}
}
Java alapú SQL API felületek hatékonyság elemzése
43
private EntityManagerFactory factory =
Persistence.createEntityManagerFactory("szakdolgozat_jinq");
private JinqJPAStreamProvider provider = new JinqJPAStreamProvider(factory);
5.3.4 JINQ API implementáció
A JINQ erősen épít a JPA-ra, ezért az Eclipse-ben itt is egy JPA projektet
hoztam létre, az előző pontban ismertetett módon. Ahhoz azonban, hogy JINQ
működjön, további .jar fájlok letöltésére és hozzáadására volt szükség. Ezért jinq
néven létrehoztam egy User Library-t, amibe bemásoltam az alábbi két .jar fájlt:
jinq-all.jar és asm-all-5.0.1.jar.
Ezután létrehoztam a JINQ használatához szükséges provider-t, melyet az
alábbi kódrészlettel szemléltetek (17. ábra).
17. ábra: provider létrehozásának kódrészlete.
Látható, hogy elsőnek létre kell hozni egy EntityManagerFactory
objektumot, akárcsak JPA esetében. Majd az így létrehozott factory objektumot át
kell adni paraméterül a JingJPAStreamProvider osztály konstruktorának. Ekkor
már közvetlenül tudunk kapcsolódni az adatbázishoz és tudjuk használni a JINQ
nyújtotta lehetőségeket.
Fontos megemlíteni a JINQ jelenlegi óriási hiányosságát. Ugyanis nem
támogatja a beszúrás, módosítás és törlés műveleteket. Tehát JINQ-val csak a
lekérdezés lehetséges. Az adatmanipulációs utasításokat pedig JPA-val kell
megoldania a fejlesztőnek. Ezért a DataManager osztályban, csak a Select()
metódus használ JINQ API-t, a többi JPA-val dolgozik.
Maga a JINQ használata nagyon kényelmes, ugyanis egy sort kellett írni,
ahhoz, hogy egy teljes táblát lekérdezhessek. Az eredményhalmazt egy tipizált
List objektumban kaptam meg, ami azonban némi gondot okozott. A Java fordító
kötelezi a fejlesztőt a List objektum generikus típusának fordítási időben történő
megadására. Ez jelen esetben azt jelenti, hogy fordítási időben meg kell határozni
azt, hogy az List objektumban milyen típusú Entity objektumok lesznek. Tehát - a
korábbiakkal ellentétben - nem volt arra lehetőség, hogy a Select() metóduson
belül, futás időben döntsem el, melyik táblán akarok lekérdezést futtatni, vagyis
milyen típusú Entity objektumokból fog állni az eredményhalmaz.
Java alapú SQL API felületek hatékonyság elemzése
44
public List selectJoin(){
JinqStream workspaces = provider.streamAll(em,
Workspace.class);
List pairs = workspaces.join(w ->
JinqStream.from(w.getPersons())).toList();
return pairs;
}
Ezért a Select() metódusból tulajdonképpen négy metódust csináltam
Recommended