• email
  • facebook
  • linkedin
  • google+
  • pinteres

Java adatbáziskezelők: H2

1.   Bevezetés

Terveztem, hogy készítek egy kisebb leírást a JasperReportsról, demóval, amihez gondoltam szép lenne, ha a riportokban megjelenítendő adatokat nem kéne a programban bénán létrehozni, sokkal elegánsabb lenne adatbázisból dolgozni. Ezzel pedig, mivel nem szerettem volna, ha mindenkinek, aki ki akarja próbálni, saját PostgreSQL vagy más RDBMS-sel kell vacakolnia, remek alkalom kínálkozott, hogy körbenézzek a beágyazott adatbáziskezelők világában.

Java vonalon ezen a területen is elég gazdag a kínálat. Az Apache birodalomhoz tartozó Derby a JDK 6-nak is része lett Java DB néven, és a GlassFish alkalmazásszerver is ezt használja alapértelmezetten. Régóta létezik a HSQLDB, amelyet az OpenOffice.org irodai programcsomag és a JBoss AS is használ (illetve én is ezt ismertem már régebbről). Utóbbinak tapasztalataira építve készül a H2 adatbáziskezelő, amely így a legfiatalabb, de már ez is jócskán túl van az 1.0-ás verzión.

DB teljesítmény

A H2 holnapján található teljesítménytesztek alapján, ahol nem csak az említett Java adatbáziskezelők, hanem a MySQL és a PostgreSQL is bekerültek a tesztbe, úgy tűnt, a H2-vel igazán jó munkát végeztek a fejlesztők, így gondoltam, ezt lenne most leginkább érdemes kipróbálni. Ebben csak megerősített, az a funkciókat összehasonlító táblázat, amelyből kiderült, a H2 a Derby és a HSQLB legjobb funkcióit próbálja egyesíteni, a hátrányaik, gyengeségeik nélkül. Az alábbiakban következők legtöbb részlete ezért kisebb-nagyobb pontosításokkal a Derby-re és a HSQLDB-re is igaz.

2.   Mit is tud a H2

A H2 és a hasonló adatbáziskezelők legfőbb előnyei közé tartozik, hogy akár kliens-szerver üzemmódban akár önálló, egygépes alkalmazásainkban is használhatjuk, nem kell külön telepíteni, jól hordozható, és 100%-ban Java, azaz ha Javaban programozunk, belül maradhatunk a használt fogalmi/gondolkodási tartományon.

Beágyazott mód

A beágyazottság ebben a kontextusban azt jelenti, hogy nincs külön alkalmazásként futtatott adatbázisszerverünk, amihez az alkalmazásunk csatlakozik, egyetlen Java alkalmazás futtatásával elindul maga a -- nevezzük így -- kliens, amely magában tartalmazza az adatbáziskezelő réteget is. Használatához egyetlen jar-t kell a classpathba bemásolnunk, és már el is érhető a H2. Beágyazott módban az alkalmazásunk közvetlenül rendelkezik kapcsolattal az adatbázishoz, és az kívülről nem lesz elérhető. A használt adatbázis a legtöbb esetben (hacsak másképp nem konfiguráljuk) le is záródik, ha az utolsó kapcsolatot is bontjuk felé. A beágyazott adatbázis tárolható csak a memóriában, a futásidő erejéig (jdbc:h2:mem:<databaseName>) vagy a fájlrendszeren, perzisztens módon is (jdbc:h2:file:/data/sample).

Távoli mód

Szerver módban a H2 igazi adatbázisszerverként fut, amelyhez TCP porton keresztül csatlakozhatnak az alkalmazások (jdbc:h2:tcp://<server>[:<port>]/<databaseName>). Ilyen használat mellett ugyanúgy működik, mint bármely megszokott RDBMS. Kevert módban (dbc:h2:<url>;AUTO_SERVER=TRUE) a két elérési mód bármelyike használható, párhuzamosan. (A képek a http://h2database.com oldalról származnak.)

Kevert mód

Különböző alkalmazásszerverek is azért szállítják az említett adatbáziskezelők valamelyikét alapértelmezetten, mert rugalmasak, nem kell őket külön telepíteni, és mivel funkcionálisan felveszik a versenyt komolyabb társaikkal, illetve mivel ORM eszközökkel (Hibernate, Toplink) is elérhetők, tökéletesek kisebb (pl. demó) alkalmazások futtatásához.

Mivel az egész Javaban íródott, az adattípusoknál is könnyen megtaláljuk az ileszkedő változatot (TINYINT > java.lang.Byte; SMALLINT > java.lang.Short...), ha pedig függvényeket, tárolt eljárásokat szeretnénk használni, akkor azokat is megírhatjuk Javaban.

A H2-t emellett kiegészítették néhány hasznos funkcióval, mint például konzol és webes kezelőfelület SQL parancsok futtatásához és a szerver menedzseléshez, CSV támogatás, egyszerű Backup/Restore vagy fulltext keresés.

A teljesítmény adatokat, és a funkcionális gazdagságot látva az emberben felmerül, hogy komolyabb, nagyobb terhelést jelentő alkalmazások kiszolgálására is használja a H2-t vagy valamelyik hasonló eszközt. Ez ellen sok reflex működik rutinos fejlesztőben, de bizonyos szintig talán mégis érdemes lehet kacérkodni a gondolattal.

3.   Pár példa

Az itt bemutatott példák futtatható változata megtalálható az SVN-ből letölthető demóban. Lásd a forrás DataBase és CreateDB osztályait!

3.1.   Beágyazott működés

Indítás beágyazottan és csatlakozás az adatbázishoz JDBC-vel:

dbClassForName = "org.h2.Driver";
//Névtelen adatbázis a memóriában
//dbURL = "jdbc:h2:mem:;
//Test nevű adatbázis a memóriában
dbURL = "jdbc:h2:mem:test";
//Test nevű adatbázis a fájlrendszeren a /tmp kvtárban
//dbURL = "jdbc:h2:/tmp/test";
dbUser = "sa";
dbPassword = "";

Class.forName(dbClassForName);
connection = DriverManager.getConnection(dbURL, dbUser, dbPassword);

A csatlakozással az adatbáziskezelő elindul és a megadott nevű adatbázis létre is jön, ha nem talál ilyen néven létezőt. A connectionön keresztül innen pedig már nyugodtan szerezhetünk egy statementet és futtathatjuk a kívánt SQL parancsokat.

3.2.   Kliens-szerver

A kliens-szerver futtatás esetén az URL formátuma kicsit más (jdbc:h2:tcp://<server>[:<port>]/<databaseName>), illetve előtte el kell indítanunk a szervert, amit megtehetünk parancssorból (java -cp h2.jar org.h2.tools.Server) vagy Java kódból (Server server = Server.createTcpServer(args).start();). Az alábbi példán azt mutatom meg, hogyan tudunk Antból létrehozni egy adatbázist és bele tesztadatokat vinni. (Ez a példa is megtalálható az SVN-ben található demó build.xml-ében.)

Először definiáljuk a targetet, amely elindítja a H2 szervert. Mint látjuk ez tulajdonképpen egy sima Java futtató taskot használ:

<!-- H2db adatbázis elindítása -->
<target name="h2db_start">
  <echo message="h2db indítása"/>
  <mkdir dir="${h2db.dir}"/>
  <java classname="org.h2.tools.Server"
        dir="${h2db.dir}"
        fork="yes">
    <arg value="-tcp"/>
    <classpath refid="lib_classpath"/>
  </java>
</target>

Utána következik a target, amelyet futtatni fogunk, s amely a futó adatbázisba belenyomja az SQL fájljainkban található parancsokat. Itt a szekvenciális és párhuzamos futtatásokkal való ügyeskedésre volt szükség, hogy a következő lépéseket megfelelő módon tudjuk végrehajtani:

  1. H2 szerver elindítása (a task tovább fut)
  2. kicsit várunk, hogy a szerver biztosan elinduljon, majd kapcsolódunk az adatbázishoz, és lefuttatjuk az SQL szkripteket
  3. Leállítjuk a H2 szervert

Az szerver indítása és leállítása Java alkalmazásból is így történne.

<!-- H2db adatbázis elkészítése -->
<target name="createdb_h2db">
  <delete dir="${h2db.dir}"/>
  <parallel>
    <ant target="h2db_start"/>
    <sequential>
      <sleep seconds="3"/>
      <echo message="Adatbázis létrehozása"/>
      <sql driver="org.h2.Driver"
           url="jdbc:h2:tcp://localhost/test"
           userid="sa"
           password=""
           encoding="UTF-8"
           classpathref="lib_classpath">
        <transaction src="${sql.dir}/create_tables.sql"/>
        <transaction src="${sql.dir}/testdata.sql"/>
      </sql>
      <ant target="h2db_stop"/>
    </sequential>
  </parallel>
</target>

És íme a task, amely leállítja a szervert:

<!-- H2db adatbázis leállítása -->
<target name="h2db_stop">
  <echo message="h2db leállítása"/>
  <java classname="org.h2.tools.Server"
        dir="${h2db.dir}"
        fork="yes">
    <arg value="-tcpShutdown"/>
    <arg value="tcp://localhost:9092"/>
    <classpath refid="lib_classpath"/>
  </java>
</target>

3.3.   Backup és Restore

Ha nem fut éppen a H2, akkor az adatbázis mentéséhez elég a megfelelő könyvtárat átmásolni valahova. Futásidőben történő backup készítéséhez hasznos segédmetódusokat kapunk. SQL formátumban történő mentéshez hívjuk a következő statikus metódust:

Script.execute(String url, String user, String password, String fileName)

Zip fájlba történő teljes backuphoz pedig:

Backup.execute(String zipFileName, String directory, String db, boolean quiet)

Helyreállításhoz ugyanabból a zip fájlból:

Restore.execute(String zipFileName, String directory, String db, boolean quiet)

Standalone alkalmazásunkban, amely beágyazott adatbázist használ, így könnyen implementálhatunk adatmentési funkciót.

3.4.   CSV támogatás

A H2 CSV támogatásának lényege, hogy CSV fájlokat ResultSetként tudunk kezelni, lekérdezhetünk egy CSV fájlt, vagy kiírhatjuk CSV formátumban egy lekérdezés eredményeit.

//Lekérdezés CSV fájlból
ResultSet rs = Csv.getInstance().read("data/test.csv", null, null);

//Resultset kiírása CSV fájlba
Csv.getInstance().write("data/test.csv", rs, null);

Mivel a H2 SQL függvényeiként is elérhető a CSVREAD és CSVWRITE, így könnyen tudjuk SQL-ből is exportálni egy lekérdezés eredményeit, vagy importálni egy CSV fájl tartalmát (hasonlóan mint mondjuk a PostgreSQL COPY parancsával).

-- exportálás:
CALL CSVWRITE('test.csv', 'SELECT * FROM test_table');
-- importálás:
INSERT INTO test_table (name, value)
  SELECT * FROM CSVREAD('test.csv', 'name;value', 'UTF-8', ';')

Utóbbinak implementálására is található egy működő példa a demóban, a JasperReports demóhoz kapcsolódóan. A program elég egyszerű: az első riport elkészítésekor létrehozunk egy memóriában tárolt (azaz a fájlrendszerre ki sem írt) adatbázist, és a két mellékelt CSV fájlban lévő részvény forgalmi adatokat beolvastatjuk. Ezeket az adatokat olvassa fel és jeleníti meg a JasperReportsos kis próbaalkalmazás.