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. 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
1
Java alapú SQL API felületek hatékonyság elemzése 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önkü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.
2
Java alapú SQL API felületek hatékonyság elemzése
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
3
Java alapú SQL API felületek hatékonyság elemzése Tulajdonképpen tartalmazó
csomagok
Java
nyelven
halmaza,
íródott
osztályokat
melyek lehetővé
teszik
és
interfészeket
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.
4
Java alapú SQL API felületek hatékonyság elemzése 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áziskezelő
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. 5
Java alapú SQL API felületek hatékonyság elemzése 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.
6
Java alapú SQL API felületek hatékonyság elemzése
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.
7
Java alapú SQL API felületek hatékonyság elemzése 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.
8
Java alapú SQL API felületek hatékonyság elemzése 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.
9
Java alapú SQL API felületek hatékonyság elemzése
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á
végrehajthatunk (4. ábra).
4. ábra: a JPA API struktúrája. [9]
10
lekérdezéseket
is
Java alapú SQL API felületek hatékonyság elemzése 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);
11
Java alapú SQL API felületek hatékonyság elemzése flush():
szinkronizálja
a
perzisztencia
kontextust
a
mögöttes
zárolja
perzisztencia
adatbázissal; lock():
a
megadott
zárolási
módon
a
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.
12
Java alapú SQL API felületek hatékonyság elemzése 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ó. 13
Java alapú SQL API felületek hatékonyság elemzése
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 XMLben 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.
14
Java alapú SQL API felületek hatékonyság elemzése
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.
15
Java alapú SQL API felületek hatékonyság elemzése 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);
16
Java alapú SQL API felületek hatékonyság elemzése 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ó.
17
Java alapú SQL API felületek hatékonyság elemzése 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.
18
Java alapú SQL API felületek hatékonyság elemzése
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.); 19
Java alapú SQL API felületek hatékonyság elemzése 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.
20
Java alapú SQL API felületek hatékonyság elemzése 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 21
relatív
teljesítményének
Java alapú SQL API felületek hatékonyság elemzése ö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.
22
Java alapú SQL API felületek hatékonyság elemzése
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.
23
Java alapú SQL API felületek hatékonyság elemzése 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.
24
Java alapú SQL API felületek hatékonyság elemzése 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
25
Java alapú SQL API felületek hatékonyság elemzése 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.
26
Java alapú SQL API felületek hatékonyság elemzése 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.
27
Java alapú SQL API felületek hatékonyság elemzése
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.
28
Java alapú SQL API felületek hatékonyság elemzése 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).
29
Java alapú SQL API felületek hatékonyság elemzése 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, 30
Java alapú SQL API felületek hatékonyság elemzése 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.
31
Java alapú SQL API felületek hatékonyság elemzése
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
32
Java alapú SQL API felületek hatékonyság elemzése é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
paraméter
értéke.
Amennyiben
a
az
adatbázishoz,
Visszatérési
kapcsolódás
értéke
sikeres
amennyi
logikai
volt,
a
típusú.
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.
33
Java alapú SQL API felületek hatékonyság elemzése 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.
34
Java alapú SQL API felületek hatékonyság elemzése 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.
35
Java alapú SQL API felületek hatékonyság elemzése 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). 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; } }
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.
36
Java alapú SQL API felületek hatékonyság elemzése Ezután implementáltam a DataManager osztályt. A Connect() metódus törzsében egy ciklus annyiszor épít fel JDBC kapcsolatot, amennyi a paraméterként kapott egész szám. A getWorkspace() metódus egy String tömbbel tér vissza. Először egy Statement segítségével lekérem a Workspace tábla rekordjainak számát, ezzel meghatározva a visszatérési érték méretét. Következő lépésben lekérem a Workspace tábla Name oszlopának elemeit, amiket a létrehozott String tömbbe tárolok el, majd ezzel a tömbbel tér vissza a metódus. Az initTable() metódus törzsében szintén egy Statement-et használok, amin keresztül egy egyszerű SQL delete utasítást adok ki. Ennek eredményeképpen a teljes tábla törlésre kerül. Ha sikeres a törlés, akkor ture, egyébként false értékkel tér vissza a metódus. Az Insert() metóduson belül, a getWorkspace() metódussal először lekérem a Workspace tábla szükséges adatait. Ezután egy paraméterezett kétlépcsős PreparedStatement - utasítást implementáltam. A paramétereket egy cikluson belül állítom be, majd szintén a ciklusban hajtom végre az utasítást. Természetesen a metódus itt is logikai értékkel tér vissza aszerint, hogy a beszúrás sikeres volt-e vagy sem. Az alábbiakban az Insert() metódus kódját mutatom be (13. ábra). public boolean Insert(int record_number){ String[] workspaces = getWorkspace(); try{ PreparedStatement pstmt = conn.getConn().prepareStatement("insert into person values(?, ?, ?, ?)"); for(int i=0; i
13. ábra: az Insert() metódus megvalósítása JDBC-vel.
37
Java alapú SQL API felületek hatékonyság elemzése 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.
38
Java alapú SQL API felületek hatékonyság elemzése 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). #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; } } 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.
39
Java alapú SQL API felületek hatékonyság elemzése
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
EntityManagerFactory
paraméter
értéke.
objektumot,
majd
A
cikluson pedig
belül
bezárom
létrehozok azt.
Ehhez
egy 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.
40
Java alapú SQL API felületek hatékonyság elemzése 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. public boolean Insert(int record_number){ Person p = new Person(); Workspace w = new Workspace(); List<Workspace> list = getWorkspace(); try{ for(int i=0; i
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.
41
Java alapú SQL API felületek hatékonyság elemzése 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). 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; } }
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
Szintaktikailag sem bonyolultabb, mint a JDBC API.
42
nagyszerű
lehetőséget
ad.
Java alapú SQL API felületek hatékonyság elemzése
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). private EntityManagerFactory factory = Persistence.createEntityManagerFactory("szakdolgozat_jinq"); private JinqJPAStreamProvider provider = new JinqJPAStreamProvider(factory);
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.
43
Java alapú SQL API felületek hatékonyság elemzése Ezért a Select() metódusból tulajdonképpen négy metódust csináltam aszerint, hogy a Person vagy a Workspace táblát, illetve join vagy where művelettel kérdezek le. Az alábbi kódrészlettel a join műveletet végrehajtó metódust szemléltetem (18. ábra). public List<Pair <Workspace, Person>> selectJoin(){ JinqStream<Workspace> workspaces = provider.streamAll(em, Workspace.class); List<Pair<Workspace, Person>> pairs = workspaces.join(w -> JinqStream.from(w.getPersons())).toList(); return pairs; } 18. ábra: a selectJoin() metódus megvalósítása JINQ-val.
Elsőként lekérdeztem az összes Workspace entitást, melyeket eltároltam. Ezután a JINQ join műveletét alkalmazva egy List objektumban megkaptam a kívánt eredményeket. A kódból világosan látszik a lekérdezés Java szintaktikája is. Továbbá kiderül, hogy miért is van szükség a Java 8-as verziójára. A join metóduson belül lambda kifejezést kell használni, ami csak a Java 8-as verziójától érhető el a nyelvben. Összességében a JINQ-t viszonylag egyszerű használni. Előnye, hogy Java szintaktikát alkalmaz. Valamint tekinthető előnynek a fordítási időben történő típus meghatározás. Kevesebb a hibázás lehetősége.
44
Java alapú SQL API felületek hatékonyság elemzése
6. Tesztelés eredményei Az implementálás után elvégeztem a konkrét méréseket. Az eredményeket SQL API felületenként és műveletenként táblázatokba rendeztem. A továbbiakban ezen eredményeket és a levonható tanulságokat, következtetéseket szeretném összegezni.
6.1 Insert művelet eredményei Az alább következő táblázatokban a mért értékek másodpercben vannak megadva. JDBC Mérés sorszáma 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. Átlag: Szórás: Terjedelem:
Adat mérete (rekord) 10.000 100.000 2 2 2 2 2 2 2 2 2 2 2 0 0
500.000 1.000.000 1.500.000
13 66 15 61 14 65 13 72 13 78 13 65 13 76 14 72 13 67 15 64 13,6 68,6 0,799999 5,29528 2 17
129 126 139 128 131 134 131 133 131 146 132,8 5,54617 20
202 216 218 227 234 221 221 218 196 210 216,3 10,64941 38
1. táblázat: insert művelet végrehajtási ideje (sec) a méret függvényében JDBC esetében.
Tekintsük az 1. táblázatot. Az egyes adatméretek esetében kapott átlagértékeknél megfigyelhető, hogy az időigény közelítőleg lineáris növekedést mutat. Ugyanakkor az is kivehető, hogy az adatméret növekedésével nő a szórás és a terjedelem is. Ebből arra lehet következtetni, hogy a JDBC nem nyújt teljesen egyenletes teljesítményt. JPA esetében is nagyon hasonló időigényt mutatnak az átlagértékek. Közelítőleg itt is lineáris növekedés tapasztalható. A szórás és terjedelem esetén is - a JDBC-hez hasonlóan - elmondható, hogy a rekordok számának növekedésével ezek az értékek is nőnek. Tehát ilyen tekintetben nincs különbség a két API között. Ezt szemlélteti a 2. táblázat.
45
Java alapú SQL API felületek hatékonyság elemzése
JPA Mérés sorszáma
Adat mérete (rekord) 10.000
100.000
500.000
1.000.000 1.500.000
1. 6 23 107 202 292 2. 5 22 101 198 314 3. 6 23 113 197 314 4. 6 23 104 194 320 5. 5 22 103 212 311 6. 6 23 100 198 333 7. 5 23 104 201 320 8. 6 24 103 198 292 9. 5 23 105 200 311 10. 6 24 99 201 308 Átlag: 5,6 23 103,9 200,1 311,5 Szórás: 0,489898 0,632456 3,780212 4,548626 11,80042 Terjedelem: 1 2 14 18 41 2. táblázat: insert művelet végrehajtási ideje (sec) a méret függvényében JPA esetében.
SQLJ esetében is kirajzolódott a megközelítőleg lineáris időigény. Itt azonban a szórás és terjedelem értékek között nem mutatkozik akkora eltérés. Ez alapján az SQLJ sokkal stabilabbnak tűnik. A mért adatokat a 3. táblázat foglalja össze. SQLJ Mérés sorszáma 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. Átlag: Szórás: Terjedelem:
Adat mérete (rekord) 10.000 100.000 500.000 4 3 3 3 3 3 3 3 3 3 3,1 0,3 1
1.000.000 1.500.000
18 83 185 19 93 187 19 92 184 17 92 182 17 91 181 17 90 182 17 91 183 19 94 182 17 91 184 18 92 182 17,8 90,9 183,2 0,87178 2,844293 1,720465 2 11 6
273 276 275 277 273 273 280 273 273 274 274,7 2,238303 7
3. táblázat: insert művelet végrehajtási ideje (sec) a méret függvényében SQLJ esetében.
A három API beszúrási sebességét összevetve - az átlagértékekből egyértelműen látszik a sorrend. A leggyorsabb a JDBC, melyet az SQLJ követ, míg a leglassabban a JPA-val szúrhatunk be rekordokat. Ezt szemlélteti az 1. diagram.
46
Java alapú SQL API felületek hatékonyság elemzése
Insert művelet átlagos végrehajtási ideje az adatméret függvényében SQL API felületenként 350 311,5
300
274,7
Mért idő (sec)
250 216,3 200,1
200
183,2 132,8
150 103,9
100
90,9
68,6
50 2
5,6 3,1
13,6
23
17,8
0 10.000 JDBC
JPA
SQLJ
100.000
500.000
1.000.000
1.500.000
Adat mérete (rekord)
1. diagram: Insert művelet átlagos végrehajtási ideje az adatméret függvényében SQL API felületenként.
6.2 Update művelet eredményei Módosítás műveletnél személy szerint különbséget prognosztizáltam az időigényben aszerint, hogy egy rekordnak hány mezőjét módosítjuk. Tehát azt gondoltam, ha egy mezőt módosítunk, akkor az kevesebb időt vesz igénybe, mintha kettőt módosítanánk. Továbbá szintén különbséget feltételeztem aszerint, hogy a módosítandó adat típusa milyen. Ezért mértem külön szöveges és szám típusú adat módosítását. Az elvégzett mérések azonban ezt nem igazolták egyértelműen. Időben nem igazán van különbség abban, hogy milyen típusú adatot vagy hány mezőt módosítunk. A különbségek itt is az adat méretének köszönhetően jöttek ki. JDBC API esetében - az átlagértékeket tekintve - megközelítőleg lineráis az idő növekedése, bár 100.000 és 500.000 között egy nagyobb ugrás mutatkozik.. Az insert művelethez viszonyítva a terjedelem és a szórás mértéke is kisebb, tehát mondhatni, hogy módosítás esetén a JDBC stabilabb, mint beszúrásnál. A JPA-val történt mérések során az volt a tapasztalat, hogy megközelítőleg lineáris a műveletsor időigénye. Szintén nagyobb eltérés van a 100.000 és 500.000 rekordnál mért értékeknél. Az insert művelethez képest a JPA is stabilabbnak tűnik a szórás és terjedelem értékek alapján. A következő két táblázat a fent leírtakat támasztja alá (4. és 5. táblázat).
47
Java alapú SQL API felületek hatékonyság elemzése JDBC Mindkettő
Szám
Szöveg
1.500.000
Mindkettő
Szám
Szöveg
Mindkettő
Szám
Szöveg
0 0 0 0 0 0 0 0 0 0 0 0 0
Mindkettő
0 0 0 0 0 0 0 0 0 0 0 0 0
Szám
Mindkettő
0 0 0 0 0 0 0 0 0 0 0 0 0
Szöveg
Szám
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. Átlag: Szórás: Terjedelem:
Adat mérete (rekord) 500.000 1.000.000
100.000
Szöveg
Mérés sorszáma
10.000
0 0 0 14 9 7 34 19 19 28 28 24 3 0 3 8 7 11 16 16 19 28 27 31 0 2 1 8 8 7 27 18 20 29 23 24 0 0 0 11 8 11 19 17 20 29 25 28 0 0 1 8 8 9 20 20 20 30 26 31 0 0 1 7 8 8 22 16 21 30 26 29 1 0 1 9 7 10 21 17 18 28 28 29 0 1 0 9 8 11 20 19 19 27 24 26 0 1 1 11 8 8 18 14 21 29 24 28 0 2 0 8 7 11 21 16 21 28 25 31 0,4 0,6 0,8 9,3 7,8 9,3 21,8 17,2 19,8 28,6 25,6 28,1 0,92 0,79 0,87 2,00 0,60 1,62 4,89 1,72 0,98 0,92 1,62 2,55 3 2 3 7 2 4 18 6 3 3 5 7
4. táblázat: update művelet végrehajtási ideje (sec) a méret függvényében JDBC esetében.
JPA Mindkettő
Szám
1.500.000 Szöveg
Mindkettő
Szám
Szöveg
Mindkettő
Szám
Szöveg
0 0 0 0 0 0 0 0 0 0 0 0 0
Mindkettő
0 0 0 0 0 0 0 0 0 0 0 0 0
Szám
0 0 0 0 0 0 0 0 0 0 0 0 0
Szöveg
Mindkettő
100.000
Szám
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. Átlag: Szórás: Terjedelem:
10.000 Szöveg
Mérés sorszáma
Adat mérete (rekord) 500.000 1.000.000
0 0 0 13 9 11 17 16 28 31 24 29 0 0 0 9 6 9 16 18 20 28 22 28 2 0 0 12 8 7 16 16 18 33 25 24 0 0 0 7 7 8 20 21 17 28 24 31 2 0 1 9 8 6 18 15 16 28 25 31 0 1 0 7 8 7 20 18 17 27 26 28 0 0 0 8 7 6 19 17 16 26 24 28 0 0 0 8 8 8 16 18 20 26 26 27 2 0 0 8 7 10 17 18 20 26 21 32 1 2 0 10 9 8 16 18 16 26 22 27 0,7 0,3 0,1 9,1 7,7 8,0 17,5 17,5 18,8 27,9 23,9 28,5 0,90 0,64 0,30 1,92 0,90 1,55 1,57 1,57 3,46 2,26 1,64 2,25 2 2 1 6 3 5 4 6 12 7 5 8
5. táblázat: update művelet végrehajtási ideje (sec) a méret függvényében JPA esetében.
Az SQLJ-t tekintve szintén megfigyelhető a közelítőleg lineáris növekedés. A szórás és terjedelem értékek kisebbek, mint a beszúrás művelet esetében. Ezért az SQLJ-ről is elmondható, hogy módosításnál stabilabbnak tűnik. A mért értékeket a 6. táblázat tartalmazza.
48
Java alapú SQL API felületek hatékonyság elemzése
SQLJ Mindkettő
Szám
1.500.000 Szöveg
Mindkettő
Szám
Szöveg
Mindkettő
Szám
Szöveg
0 0 0 0 0 0 0 0 0 0 0 0 0
Mindkettő
0 0 0 0 0 0 0 0 0 0 0 0 0
Szám
0 0 0 0 0 0 0 0 0 0 0 0 0
Szöveg
Mindkettő
100.000
Szám
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. Átlag: Szórás: Terjedelem:
10.000 Szöveg
Mérés sorszáma
Adat mérete (rekord) 500.000 1.000.000
0 0 1 8 8 8 19 18 16 33 25 31 0 0 2 9 9 7 21 18 19 27 24 28 0 0 2 7 7 11 18 16 19 29 28 26 0 0 2 9 6 7 17 16 17 29 27 25 0 0 0 9 9 11 18 15 18 28 29 33 1 1 0 11 9 8 20 16 19 27 23 28 0 0 0 7 7 8 13 17 19 30 24 30 0 1 0 9 7 9 21 13 17 26 22 28 0 2 2 11 7 8 20 15 19 30 24 27 1 0 2 8 8 10 18 18 20 32 29 33 0,2 0,4 1,1 8,8 7,7 8,7 18,5 16,2 18,3 29,1 25,5 28,9 0,40 0,66 0,94 1,33 1,00 1,42 2,25 1,54 1,19 2,12 2,42 2,62 1 2 2 4 3 4 8 5 4 7 7 8
6. táblázat: update művelet végrehajtási ideje (sec) a méret függvényében SQLJ esetében.
A módosítási sebességeket összehasonlítva már nem olyan egyértelmű a sorrend, mint a beszúrás esetében volt. Igazából egyik API sem egyértelműen jobb vagy rosszabb a másiknál. Összességében módosítás esetén bármelyik jó választásnak bizonyul. Ezt támasztja alá a 2. diagram. Update művelet átlagos végrehajtási ideje az adatméret függvényében SQL API felületenként a "Mindkettő" oszlop alapján 35 28,1 28,5 28,9
Mért idő (sec)
30 25 19,8 18,8 18,3
20 15 9,3
10 5 0
0 0
0
JPA
8,7
0,8 0,1 1,1
10.000 JDBC
8
SQLJ
100.000
500.000
1.000.000
1.500.000
Adat mérete (rekord)
2. diagram: update művelet átlagos végrehajtási ideje az adatméret függvényében SQL API felületenként a "Mindkettő" oszlop alapján.
49
Java alapú SQL API felületek hatékonyság elemzése
6.3 Select művelet eredményei A lekérdezés művelet esetében időbeli különbséget feltételeztem a teljes táblás, az egykulcsos (where feltétel) és a join típusú lekérdezések között. Az elvégzett mérések ezt igazolták, ugyanis a leggyorsabb az egy kulcsos lekérdezés volt, ezt követte a teljes táblás, majd a leglassabb a join típusú volt. A mérések során tapasztalható volt, hogy az egyes API-k nem egyformán kezelik a memóriát. 500.000 vagy fölötti rekordszám esetén már nem volt elegendő a memória a 10 egymást követő mérés számára. OutOfMemory Exception keletkezett, ezért futtatás előtt konfigurálni kellett egy argumentumot, amiben a JVM számára megnöveltem a memóriát. Ezt a következő kapcsolóval tudtam megtenni: -Xmx?m, ahol a ? értéke a memória mérete megabájtban mérve. JDBC API használatánál is igazolódott, hogy az egykulcsos lekérdezés a leggyorsabb
és
a
join
művelet
a
leglassabb.
Az
adatokat
megnézve
megközelítőleg lineáris a művelet időigénye. A szórás és a terjedelem értékek alapján az API viszonylag stabilnak mondható lekérdezés terén (7. táblázat). Ugyanakkor ez az API gazdálkodik a legrosszabban a memóriával. A futtatási argumentumként 7800 MB-ot kellett beállítanom. JDBC Where feltétel
Join művelet
1.500.000 Person tábla
Where feltétel
Join művelet
Person tábla
Where feltétel
Join művelet
0 0 0 0 0 0 0 0 0 0 0 0 0
Person tábla
0 0 0 0 0 0 0 0 0 0 0 0 0
Where feltétel
0 0 0 0 0 0 0 0 0 0 0 0 0
Join művelet
Where feltétel
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. Átlag: Szórás: Terjedelem:
Person tábla
Mérés sorszáma
Join művelet
100.000
Person tábla
10.000
Adat mérete (rekord) 500.000 1.000.000
0 0 0 2 3 1 4 7 2 8 14 3 0 0 0 2 4 1 5 10 2 10 15 3 0 2 2 3 5 1 8 10 3 9 15 4 0 0 0 2 4 1 7 12 2 10 12 4 0 1 0 3 5 1 7 8 2 9 18 4 0 0 0 4 4 1 7 11 3 8 16 5 0 0 0 2 4 3 8 8 3 11 12 4 1 0 0 2 4 1 8 15 2 11 13 3 0 0 0 2 6 1 6 9 4 9 14 4 0 0 0 4 4 1 6 6 2 7 16 4 0,10 0,30 0,20 2,60 4,30 1,20 6,60 9,60 2,50 9,20 14,50 3,80 0,30 0,64 0,60 0,80 0,78 0,60 1,28 2,50 0,67 1,25 1,80 0,60 1 2 2 2 3 2 4 9 2 4 6 2
7. táblázat: select művelet végrehajtási ideje (sec) a méret függvényében JDBC esetében.
50
Java alapú SQL API felületek hatékonyság elemzése JPA-ra szintén jellemző a megközelítőleg lineáris időigény. A szórás és terjedelem értékeknél sincs kiugró adat, ezért ez az API is viszonylag stabil (8. táblázat). Memória felhasználás terén itt is 7800 MB-ot állítottam be, hogy sikeresen végre tudjam hajtani a 10 mérési sorozatot. JPA
0 0 0 0 0 0 0 0 0 0 0 0 0
Where feltétel
Join művelet
Person tábla
1.500.000
Where feltétel
Join művelet
Person tábla
Where feltétel
Join művelet
Adat mérete (rekord) 500.000 1.000.000 Person tábla
0 0 0 0 0 0 0 0 0 0 0 0 0
Where feltétel
0 0 0 0 0 0 0 0 0 0 0 0 0
Join művelet
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. Átlag: Szórás: Terjedelem:
Person tábla
Mérés sorszáma
Join művelet Where feltétel
100.000
Person tábla
10.000
0 1 0 2 6 1 7 7 1 11 10 8 0 0 0 2 4 1 4 5 2 5 8 6 0 0 0 2 3 1 4 5 1 6 7 3 0 0 0 2 2 2 4 5 1 5 7 5 0 0 0 2 3 1 4 4 2 6 14 3 0 0 0 4 2 1 4 4 3 5 7 7 0 0 0 3 2 1 4 5 3 6 14 3 0 0 0 6 2 1 4 4 2 6 7 6 0 0 0 4 2 0 4 5 3 5 10 3 0 0 0 6 2 1 4 4 6 6 10 6 0,00 0,10 0,00 3,30 2,80 1,00 4,30 4,80 2,40 6,10 9,40 5,00 0,00 0,30 0,00 1,55 1,25 0,45 0,90 0,87 1,43 1,70 2,62 1,79 0 1 0 4 4 2 3 3 5 6 7 5
8. táblázat: select művelet végrehajtási ideje (sec) a méret függvényében JPA esetében.
SQLJ-nél is mindaz elmondható, ami az előző két API esetében is igaz volt. Talán azt érdemes kiemelni, hogy a szórás és terjedelem értékeknél itt mutatkozott
a
legkisebb
eltérés,
ezért
ezt
az
API-t
mondanám
a
legegyenletesebbnek lekérdezések esetén. Memóriahasználat terén is megelőzi a JDBC-t és a JPA-t, itt ugyanis elegendő volt 7200 MB- ot beállítani argumentumként. A JINQ-n végzett méréseknél megfigyeltem, hogy a 10-es sorozatból az első mérés eredménye többször nagyságrendekkel tért el a másik 9 mérés eredményétől. Éppen ezért a szórás és terjedelem értékekben megfigyelhető némi torzítás. Az időigény, már nem egyértelműen lineáris, hanem gyorsabb ütemben nő a méret növekedésével egyetemben. Memória használatnál viszont jól szerepelt a JINQ, hiszen elegendő volt a futási argumentumot 6144 MB-ra állítanom. A következőkben az SQLJ és a JINQ esetében mért eredményeket mutatom be (9. és 10. táblázat).
51
Java alapú SQL API felületek hatékonyság elemzése
SQLJ
0 0 0 0 0 0 0 0 0 0 0 0 0
Where feltétel
Join művelet
1.500.000 Person tábla
Where feltétel
Join művelet
Person tábla
Where feltétel
Join művelet
0 0 0 0 0 0 0 0 0 0 0 0 0
Where feltétel
0 0 0 0 0 0 0 0 0 0 0 0 0
Join művelet
Join művelet Where feltétel
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. Átlag: Szórás: Terjedelem:
Person tábla
Mérés sorszáma
Person tábla
Adat mérete (rekord) 500.000 1.000.000
100.000
Person tábla
10.000
0 1 0 3 3 1 6 7 2 8 9 4 0 0 0 2 3 1 4 5 2 7 8 3 0 0 0 2 2 1 4 5 2 7 8 3 0 0 0 2 3 1 4 5 2 7 8 3 0 0 0 2 2 1 4 6 2 6 8 3 0 0 0 2 2 1 4 5 2 7 8 3 0 0 0 2 2 1 5 6 2 6 7 3 0 0 0 2 3 1 4 5 2 7 8 4 0 0 0 2 2 1 4 5 2 6 7 3 0 0 0 2 2 1 4 6 2 6 9 3 0,00 0,10 0,00 2,1 2,4 1,0 4,3 5,5 2,0 6,7 8,0 3,2 0,00 0,30 0,00 0,30 0,49 0,00 0,64 0,67 0,00 0,64 0,63 0,40 0 1 0 1 1 0 2 2 0 2 2 1
9. táblázat: select művelet végrehajtási ideje (sec) a méret függvényében SQLJ esetében.
JINQ
Átlag: Szórás: Terjedelem:
Where feltétel
Join művelet
1.500.000 Person tábla
Where feltétel
Join művelet
Person tábla
Where feltétel
Join művelet
0 0 0 0 0 0 0 0 0 0 0 0 0
Person tábla
0 0 0 0 0 0 0 0 0 0 0 0 0
Where feltétel
0 0 0 0 0 0 0 0 0 0 0 0 0
Join művelet
Where feltétel
1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
Person tábla
Mérés sorszáma
Join művelet
100.000
Person tábla
10.000
Adat mérete (rekord) 500.000 1.000.000
9 2 0 30 16 1 50 42 4 62 79 40 0 1 0 3 16 1 9 41 3 18 80 7 0 1 0 3 16 1 9 41 3 18 79 7 0 1 0 3 15 1 9 41 4 18 79 7 0 1 0 3 16 1 9 41 3 18 80 7 0 1 0 3 16 1 9 41 4 18 80 7 0 1 0 3 16 1 9 41 3 18 79 7 0 1 0 3 16 1 9 42 3 18 80 7 0 1 0 3 16 1 9 42 4 18 81 7 0 1 0 3 16 1 9 41 3 18 83 7 0,90 1,10 0,00 5,7 15,9 1,0 13,1 41,3 3,4 22,4 80,0 10,3 2,70 0,30 0,00 8,1 0,3 0,0 12,3 0,46 0,49 13,2 1,18 9,9 9 1 0 27 1 0 41 1 1 44 4 33
10. táblázat: select művelet végrehajtási ideje (sec) a méret függvényében JINQ esetében.
52
Java alapú SQL API felületek hatékonyság elemzése A grafikon elkészítésénél - a leglassabb - join műveletet vettem alapul, ezt szemlélteti a 3. diagram. Select művelet átlagos végrehajtási ideje az adatméret függvényében SQL API felületenként a "Join művelet" oszlop alapján 90
80,0
80
Mért idő (sec)
70 60 50
41,3
40 30 20 10
15,9 2,8 2,4 0,3 0,1 0,1 1,1 4,3
0 0 0 0
0 10.000 JDBC
JPA
100.000 SQLJ
9,6
500.000
5,5 4,8
1.000.000
14,5 9,4
8,0
1.500.000
Adat mérete (rekord)
JINQ
3. diagram: select művelet átlagos végrehajtási ideje az adatméret függvényében SQL API felületenként a "Join művelet" oszlop alapján.
Összességében elmondható, hogy egyértelműen a JINQ a leglassabb az API-k közül, ezután következik a JDBC, míg a JPA és az SQLJ fej-fej mellett vezet.
6.4 Kapcsolódás művelet eredményei Mivel az adatbázishoz való kapcsolódás localhoston keresztül történt, ezért másodperc alapon nem volt mérhető az egyszeri kapcsolódás ideje. Úgy döntöttem, hogy inkább azt vizsgálom, hogy többször egymás után kapcsolódok az adatbázishoz és ezt mérem. Kapcsolódás szempontjából a JDBC és SQL gyakorlatilag ugyanúgy működik. Egy ciklusban regisztráltam a drivert és meghívtam a getConnection() metódust. Azonban azt tapasztaltam, hogy a ciklus kb. 70 ismétlés szám után leáll és az adatbázistól ORA-12519 hibakódú üzenetet kapok. Megoldás után keresve, találtam egy parancsot (alter system set processes=150 scope=spfile;), mellyel megnövelhető a processzek száma és kiküszöbölhető a hiba. Viszont ez sem oldotta meg a problémát, így nem kaptam értékelhető eredményeket. A JPA és a JINQ kapcsolódás is ugyanúgy működik. Egy ciklusban létrehoztam egy EntityMaganagerFactory-t, majd bezártam azt. Itt szerencsére sikerült értékelhető adatokat kapnom. Ezeket a 11. táblázat tartalmazza.
53
Java alapú SQL API felületek hatékonyság elemzése
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. Átlag: Szórás: Terjedelem:
-
5 4 4 4 4 4 4 4 4 4 4,1 0,29 1
-
8 8 8 8 8 8 8 8 8 8 8 0 0
-
JINQ
JPA
SQLJ
JDBC
JINQ
JPA
SQLJ
JDBC
JINQ
Kapcsolódás ismétlése 10.000 10.000
JPA
SQLJ
JDBC
Mérés sorszáma
5.000
83 83 83 83 83 83 83 83 85 85 83,4 0,8 2
11. táblázat: kapcsolódási idő (sec) SQL API felületenként.
6.5 Funkciók feltárása és egyéb döntési szempontok Korábban említettem, hogy a teljesítmény mellett a másik vizsgálható terület a funkcionalitás és az egyszerűség. A funkciókat 12. táblázatban foglaltam össze. SQL API
Funkció Insert Update Delete Select Metaadatok lekérdezése Tranzakciókezelés Paraméterezés Hibakezelés Callable Prepared Connection pool
JDBC
SQLJ
JPA
JINQ
+ + + + + + + + + + +
+ + + + + + + + -
+ + + + + + + + + + +
+ + -
12. táblázat: az SQL API-k funkciói.
54
Java alapú SQL API felületek hatékonyság elemzése A táblázatból az Insert, Update, Delete, Select sorok nem szorulnak különösebb magyarázatra. Metaadatok lekérdezése alatt a táblákról nyerhető többlet információkat értem. JDBC-nél például lekérdezhetjük a tábla oszlopainak típusát vagy az oszlopok számát (getColumnCount()). JPA-nál az EntityManager getMetamodel() metódusával kérhetőek le hasonló adatok. SQLJ-nél és JINQ-nál ez a funkció közvetlenül nem érhető el. Előbbinél JDBC-n, míg utóbbinál JPA-n keresztül kérhetőek le metaadatokra vonatkozó információk. A tranzakció kezelés sem szorul különösebb magyarázatra. JINQ-nál nem találtam semmilyen erre vonatkozó információt, míg a másik három API esetében van lehetőség erre (pl. commit és rollback). Továbbá mind a négy API-nál lehet az utasításokban különböző paramétereket definiálni, melyek értéke futás időben szabható meg. Hibakezelésnél nem találtam semmiféle utalást a JINQ-nál, viszont a többi API-nál lehetséges. JDBC és SQLJ nagyon hasonló ebből a szempontból, ugyanis a jól ismert try/catch blokkban SQLException-t lehet elkapni. Valamint az alábbi metódusok segítségével informálódhatunk a hibáról: getErrorCode( ) getMessage( ) getSQLState( ) getNextException( ) printStackTrace( ) JPA-nál többféle Exception is keletkezhet: javax.persistence.PersistenceException javax.persistence.EntityExistsException javax.persistence.EntityNotFoundException javax.persistence.LockTimeoutException javax.persistence.NonUniqueResultException javax.persistence.NoResultException javax.persistence.OptimisticLockException javax.persistence.PessimisticLockException javax.persistence.QueryTimeoutException javax.persistence.RollbackException javax.persistence.TransactionRequiredException 55
Java alapú SQL API felületek hatékonyság elemzése Callable alatt a tárolt eljárások hívását értem. Fontos lehet megjegyezni, hogy JPA-nál csak a 2.1-es verziótól van erre lehetőség. Prepared alatt pedig a PreparedStatement vagy ahhoz hasonló funkció meglétét értem. Connection pool a JDBC és JPA esetében megvalósítható. SQLJ-nél önállóan nincs erre lehetősége, de mivel szorosan kapcsolódik a JDBC-vel, ezért gyakorlatban ott is megoldható, míg JINQ-nál nem találtam semmilyen erre vonatkozó információt. A funkciók után az egyszerűséget vizsgálva, az API-k használatához több .jar fájlra volt szükség. Ebből a szempontból a JDBC és az SQLJ alkalmazása a legegyszerűbb, mert mindkettőhöz csak a JDBC driver .jar fájljára volt szükség. JPA esetén már összesen 3 különböző fájlt kellett a projekthez adni, hogy működjön, míg JINQ-nál 5 különböző fájl hozzáadására volt szükség. Az osztályok számában nem volt eltérés, mindegyiket meg lehetett volna valósítani a DataManager nevű osztállyal (JDBC és SQLJ esetében a kapcsolódást külön osztályba szerveztem, bár nem feltétlen volt rá szükség). A DataManager osztály kódsorainak számában azonban már mutatkozott eltérés. JDBC-nél ez az osztály 178 soros, SQLJ esetén 157 soros volt, míg JPA-nál 148 sort számoltam. JINQ-nál csak a lekérdezést megvalósító metódusok kódhosszát hasonlítom össze a többi API ugyanezen metódusaival. Így JDBC-nél 33 sor, SQLJ-nél 32, JPA-nál 21, JINQ-nál csak 17 sort számoltam. JPA és JINQ használatánál a DataManager osztályon kívül még egy persistence.xml fájlt is szerkeszteni kell a megfelelő működéshez. Az egyszerűségnél szempont lehet az is, hogy a fejlesztő melyik API-ról mennyi információt, dokumentációt, példakódot, tutorialt talál az interneten. Innen nézve a legegyszerűbb a JDBC és a JPA, mert ezek a legelterjedtebb megoldások. Számos dokumentáció, fórum és tutorial van hozzájuk. A helyzet még SQLJ-nél sem kilátástalan, ehhez is fellelhetőek dokumentációk, példakódok. Viszont személyes megítélésem szerint már lényegesen kevesebb. Ebből a szempontból a JINQ áll a legrosszabbul. Ha beírtam a keresőbe, akkor a legtöbb találat a LINQ-ra vonatkozott. Egy weboldal van, ami viszonylag részletesen tárgyalja a JINQ használatát, de ezen kívül nincs más dokumentáció, tutorial. Kódolásnál szempont lehet továbbá a kódkiegészítés. Ez SQLJ esetében nem működött, a fejlesztő környezet nem kínált fel semmit, míg a másik 3 API-nál volt erre lehetőség. 56
Java alapú SQL API felületek hatékonyság elemzése
7.Összegzés A szakdolgozatom célja a Java gazdanyelvhez használható SQL API felületek
áttekintése
volt,
majd
pedig
egy
olyan
benchmark
program
implementálása, mely segít dönteni az SQL API választásban. A dolgozat első felében sorra vettem az egyes API-kat. Ábrák segítségével szemléltettem a struktúrájukat, majd részletes szöveges magyarázatot fűztem hozzájuk. Továbbá igyekeztem tömören összefoglalni az egyes API felületekkel végezhető műveleteket. A továbbiakban körüljártam a benchmark fogalmat, valamint az adatbázisok esetében bemutattam néhány létező benchmark tesztet. Ezek után definiáltam a tesztelés célját, a tesztelési környezetet. Megterveztem a teszt adatbázist, majd bemutattam az implementált program API-tól független részeit, metódusait. Kitértem
az
időmérésre
és
annak
megvalósítására.
Ezután
külön-külön
részleteztem minden API esetében az implementációhoz szükséges fájlokat, beállításokat. Ehhez számos kódrészletet is mellékeltem. Az elvégzett mérések eredményeit táblázatokba rendeztem, melyeket beszerkesztettem a dolgozatba. Minden táblázatból igyekeztem levonni az összefüggéseket. Az adatok alapján grafikonok segítségével szemléltettem az egyes API-k teljesítménykülönbségét. Végezetül létrehoztam egy funkciótáblát. Ugyanakkor az elvégzett mérések után személyes véleményem, hogy a tesztek pontosságát tovább javíthatná, ha még ennél is nagyobb adatméretekre tesztelnénk, illetve az időmérést finomítanánk ezredmásodperc alapúra. Beleásva magam a témakörbe, azt mondhatom, hogy a JDBC és a JPA a legstabilabb és legkomplettebb API a vizsgált négy alternatíva közül. Ezeket bátran ajánlom, hiszen rengeteg irodalom lelhető fel hozzájuk. Továbbá mindkét API megfelelően használható az adatmanipulációs és adatlekérdező utasítások végrehajtásához. Mindkettő alkalmas tranzakciók kezelésére, tárolt eljárások hívására és connection pool megvalósítására is. A hibakezelés is eléggé jól megoldott mind a két API-nál. JPA-nál talán az lehet előny, hogy az objektum orientált szemlélethez is jobban igazodik, alkalmazkodik, mint a másik három API.
57
Java alapú SQL API felületek hatékonyság elemzése Az SQLJ főleg azok számára nyújthat kiváló megoldást, akik az Oracle adatbázis-kezelő iránt köteleződtek el. Önmagában is jól megállja a helyét, hiszen az adatmanipulációs és adatlekérdező utasításokon túl, ez az API is képes a tranzakciók kezelésére, megfelelő hibakezelésre és tárolt eljárások hívására. Ugyanakkor szerintem a JDBC-vel együtt alkalmazva teljesedik ki igazán, inkább csak annak kiegészítéseként tekinthető. JINQ
esetében
szomorú
tény,
hogy
jelenleg
nem
alkalmas
az
adatmanipulációs utasítások végrehajtására, tranzakciók kezelésére és tárolt eljárások hívására sem. A hibakezelés és a connection pool sem megoldott. Adatlekérdezésre azonban remekül használható. Amiben mindenképpen újat vagy többet tud a másik három API-val szemben, az a lambda kifejezések alkalmazása. Összességében mégis azt gondolom, hogy az implementált benchmark programmal sikerült megvizsgálni az API-k teljesítményét, mutatkozott eltérés, mely segíthet a döntésben. Az áttekintő fejezetek és a funkciótábla alapján pedig a fejlesztők számára látható vált egy összegzett kép.
58
Java alapú SQL API felületek hatékonyság elemzése
8. Summary The aims of my thesis were the revision of the SQL API interfaces used with the host language of Java and the implementation of such a benchmark program that helps choosing the appropriate SQL API. In the first half of my thesis I described the different API types. With the help of diagrams I demonstrated their structures and then I appended detailed text explanations to them. Furthermore, I intended to give a concise summary of the operations that can be made by the different types of API interfaces. In the rest of my thesis I studied the concept of benchmark and in the case of databases I demonstrated some existing benchmark tests. Afterwards I defined the purpose of testing and the testing environment. I also designed the test database and demonstrated the parts and the methods of the implemented program that are independent of API. I wrote about timing and its realization. After that I specified the files and settings required for the implication in the case of each API type separately and I attached a number of code items to those specifications. I put the results of the completed measurements in charts that I edited into the text of my thesis. I intended to draw the correlations from each chart. I demonstrated the difference in the performance between the different API types on the basis of data with the help of diagrams. Finally I created a function chart. At the same time, after the measurements I had made, in my opinion the test accuracy could be improved either if the tests were made on larger data scales or if timing were made finer by using milliseconds. Dealing with the topic more deeply, I can say that JDBC and JPA are the most stable and proper API among the four examined types. I offer them courageously because a lot of references can be found in connection with them. Furthermore, both API types can be used to implement change mode and datatrieve commands. Both of them are suitable for handling transactions, developing stored processes and realization of connection pool. The errorhandling is solved quite well in case of both API types. It can be an advantage of JPA that it adapts to the oriented approach better than the other three API types. The SQLJ gives a perfect solution for those ones who commited themselves to Oracle data structure management.
59
Java alapú SQL API felületek hatékonyság elemzése It stands its ground itself because besides the change mode and datatrive commands, this API types is also able to handle transactions, errors and develop stored processes. In my opinion, it is complete applied with the JDBC. In csae of JINQ, it is a rigorous fact that at the moment it is not suitable for the implementation of change mode commands, handling transactions and developing stored processes. Error-handling and connection pool are not solved, either, buti it can be used for datatrieve perfectly. JINQ can be applied for using lambda expressions in contrast to the other API types. Though, on the whole I think the performance of the different API types managed to be tested with the use of the implemented benchmark programme. Differences were found that can help making the choice. On the basis of the units of revision and the function chart a summarized image was outlined for a software developer.
60
Java alapú SQL API felületek hatékonyság elemzése
9. Irodalomjegyzék [1] Wikipédia - Java: http://hu.wikipedia.org/wiki/Java_%28programoz%C3%A1si_nyelv%29
[2] Elek Tibor - Java nyelvi elemek: https://users.iit.uni-miskolc.hu/~elek/vedett/jegyzetekLdap/OOP1_JavaNyelviAlapok.pdf
[3] Oracle Technology Network - JDBC: http://www.oracle.com/technetwork/java/overview-141217.html
[4] Szűcs Miklós - Adatbázis Rendszerek II. Oracle - JDBC: http://users.iit.uni-miskolc.hu/~szucs/ab2/oa/1508_Oracle_JDBC.pdf
[5] Wikipédia - SQLJ: http://en.wikipedia.org/wiki/SQLJ
[6] Oracle Help Center - SQLJ: https://docs.oracle.com/cd/B28359_01/java.111/b31227/overview.htm
[7] IBM Knowledge Center - SQLJ: http://www-01.ibm.com/support/knowledgecenter/SSEPEK_10.0.0/com.ibm.db2z10.doc. java/src/tpc/imjcc_c0052042.dita
[8] Eric Jendrock, Jennifer Ball, Debbie Carson, Ian Evans, Scott Fordin Kim Haase - The Java EE 5 Tutorial: http://docs.oracle.com/javaee/5/tutorial/doc/?wp406143&PersistenceIntro.html#wp78460
[9] ObjectDB: http://www.objectdb.com/java/jpa
[10] Oracle Docs - Entity Manager: http://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html
[11] Reiter István - C# programozás lépésről lépésre: https://reiteristvan.wordpress.com/2012/10/17/c-programozas-lepesrol-lepesre-letoltheto/
[12] Dr. Ming-Yee Iu - JINQ: http://www.jinq.org
[13] Bakai Balázs - Java SE 8 – Lambda kifejezések: http://www.bakaibalazs.hu/2014/12/java-se-8-lambda-kifejezesek.html
61
Java alapú SQL API felületek hatékonyság elemzése
[14] Java SE 8 - Lambda Quick Start: http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/LambdaQuickStart/index.html
[15] PC fórum szótár - Benchmark: http://pcforum.hu/szotar/benchmark.html
[16] Transaction Processing Performance Council: http://www.tpc.org
[17] MySQL - Benchmark Tool: https://dev.mysql.com/downloads/benchmarks.html
[18] Wikipédia - YCSB: http://en.wikipedia.org/wiki/YCSB
62
Java alapú SQL API felületek hatékonyság elemzése
10. Melléklet A mellékelt DVD az alábbiakat tartalmazza: Egy dolgozat nevű mappában: o A
dolgozatot
tartalmazó
fájl,
a
használt
szerkesztő
formátumában (dobos_tamas_dolgozat.doc). o A
dolgozatot
tartalmazó
fájl,
PDF
formátumban
(dobos_tamas_dolgozat.pdf). o A
szakdolgozat
kiírása
külön
fájlban
(dobos_tamas_kiiras.doc). o A dolgozat magyar nyelvű összefoglalója, a használt szerkesztő formátumában (dobos_tamas_osszegzes.doc). o A dolgozat magyar nyelvű összefoglalója, PDF formátumban (dobos_tamas_osszegzes.pdf). o A
dolgozat
angol
nyelvű
összefoglalója,
a
használt
szerkesztő formátumában (dobos_tamas_summary.doc). o A dolgozat angol nyelvű összefoglalója, PDF formátumban (dobos_tamas_summary.pdf). Egy program nevű mappában: o Az implementált program forráskódja. o Az implementáció során használt .jar fájlok.
63