23. JDBC adatbázis-kezelés

A Java Database Connectivity (JDBC) olyan programozói felületet (API-t) ad a programozó kezébe, amellyel a konkrét adatbázis-kezelőtől függetlenül működő, adatbázis alapú alkalmazások készíthetők.

Kezdő lépések

Első lépésként meg kell győződni, hogy minden helyes van-e beállítva. Ez a következő lépéseket tartalmazza:

  • Telepítsük a meghajtót a számítógépére
    A meghajtó tartalmazza a telepítésre vonatkozó utasításokat. A speciális DBMS-hez (adatbázis-kezelőhöz) írott JDBC meghajtók telepítése egyszerűen annyiból áll, hogy átmásoljuk a meghajtót a számítógépére; semmilyen különleges beállításra nincs szükség.
    A JDBC-ODBC híd (JDBC-ODBC Bridge) telepítése már nem ennyire egyszerű. Akár a Solaris, akár a Windows verzióját használjuk, automatikus megkapjuk a JDBC-ODBC hidat is, amely önmagában nem igényel semmilyen speciális beállítás. Azonban az ODBC igen.
  • Telepítsü a DBMS-t, amennyiben szükséges
    Amennyiben még nincs a DBMS feltelepítve, kövesse a forgalmazó utasításait a telepítésre vonatkozóan. A legtöbb esetben a DBMS telepítve van és működik az általánosan elfogadott adatbázisok bármelyikével.

23.1. Adatbázis beállítása

Tételezzük fel, hogy a Coffebreak nevű adatbázis már létezik. (Egy adatbázis létrehozása nem olyan bonyolult, de különleges jogokat igényel és általában az adatbázis adminisztrátor végzi el). A jegyzet példáiban használt táblák az alapértelmezett adatbázisban lesznek létrehozva. A könnyebb kezelhetőség érdekében szándékosan tartottuk alacsonyan a táblák számosságát és méretét.

Tételezzük fel, hogy a mintaadatbázist egy Coffee Break nevű kávéház tulajdonosa használja, ahol szemes kávét árulnak fontban mérve, és főzött kávét mérnek csészébe. Az egyszerűség kedvéért tételezzük fel azt is, hogy a tulajdonosnak csak két táblára van szüksége, az egyik a kávé típusok nyilvántartására, a másik a kávé kiegészítők adatainak tárolására.

Először azt mutatjuk meg, hogyan kell a DBMS-sel létrehozni egy kapcsolatot, majd miután a JDBC küldi el az SQL kódot a DBMS-nek, bemutatunk néhány SQL kódot. Ezután megmutatjuk, milyen egyszerű a JDBC-vel elküldeni ezt az SQL mondatot a DBMS-nek, és feldolgozni a visszakapott eredményt.

Ez a kód a legtöbb ismertebb DBMS-n tesztelve lett, azonban előfordulhat néhány kompatibilitási probléma a régebbi ODBC meghajtók és a JDBC-ODBC Híd használatakor.

Kapcsolat létesítése

Első lépésként a kapcsolatot kell létrehozni azzal a DBMS-sel, amit használni akarunk. Ez két lépést tartalmaz: a meghajtó betöltését és a kapcsolat megteremtését.

Meghajtó betöltése

A használandó meghajtó vagy meghajtók betöltése nagyon egyszerű, és csak egy kódsort tartalmaz. Például a JDBC-ODBC Híd meghajtót a következő kód tölti be:

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

A driver dokumentációja megadja, hogy melyik osztályt kell használni. Amennyiben az osztály neve jdbc.DriverXYZ, a következő kóddal tudjuk betölteni:

Class.forName("jdbc.DriverXYZ");

Nem szükséges a meghajtót példányosítani és regisztrálni a DriverManager-rel, mert a Class.forName hívás automatikusan megteszi azt. Amennyiben saját példányt hoznánk létre, egy szükségtelen duplikáció történne, igaz, ez nem okozna hibát.

Amikor a kód betöltődött, készen állunk a kapcsolat létrehozására a DBMS-sel.

Kapcsolat létrehozása

A második lépés a kapcsolat létrejöttéhez, hogy a megfelelő meghajtó hozzá legyen kapcsolva a DBMS-hez. A következő kód bemutatja az általános eljárást:

Connection con = DriverManager.getConnection(url,
         "myLogin", "myPassword");

Ez a lépés is nagyon egyszerű, a legnehezebb dolog, hogy mit adjunk meg URL-nek. Amennyiben a JDBC-ODBC híd meghajtót használjuk, a JDBC URL így kezdődik: jdbc:odbc:. Az URL többi része általában az adatbázis vagy az adatbázis séma neve. Például, ha ODBC-t használunk egy Fred nevű ODBC adatforrás eléréséhez, a JDBC URL a következő lesz: jdbc:odbc:Fred. A mylogin helyére a DBMS-ben használt felhasználói nevet, amíg a myPassword helyére a jelszót kell behelyettesíteni. Azaz, ha a DBMS-hez a bejelentkezési név Fernanda, és a jelszó J8, csak erre a két sorra van szükség, hogy a kapcsolat létrejöjjön:

String url = "jdbc:odbc:Fred";
Connection con =
    DriverManager.getConnection(url, "Fernanda", "J8");

Amennyiben egy külső cég által fejlesztett JDBC-t használunk, a dokumentáció megmondja, milyen protokollt használjunk, mit kell a jdbc: után írnunk a JDBC URL-ben. Például, ha a meghajtó fejlesztője az acme nevet regisztrálta, az első és a második része az URL-nek a jdbc:acme:. A dokumentáció útmutatót tartalmaz a JDBC URL további részéhez is. A JDBC URL utolsó része tartalmazza az adatbázis azonosítására szolgáló információkat.

Amennyiben valamelyik betöltött meghajtó azt érzékeli, hogy a JDBC URL a DriverManager.getConnection metódussal lett betöltve, a meghajtó fogja létrehozni a kapcsolatot a DBMS-sel a JDBC URL-ben megadott módon. A DriverManager osztály (hűen a nevéhez) a háttérben lekezeli a kapcsolódás összes részletét. Ha nem írunk saját meghajtót, talán soha nem is fogjuk a Driver interfész más metódusát használni, és az egyetlen DriverManager metódus, amit igazán szükséges ismernünk, a DriverManager.getConnection.

A DriverManager.getConnection metódus által visszaadott kapcsolat egy nyitott kapcsolat, amelynek JDBC mondatokat adhatunk meg, amely eljuttatja az SQL mondatot a DBMS-nek. Az előző példában con egy nyitott kapcsolat, és a következő példához fogjuk használni.

23.2. Táblák használata

Tábla létrehozása

Először létrehozzuk az egyik táblát a példa adatbázisban. Ez a tábla a Coffees, mely tartalmazza az alapvető információkat a Coffee Break által eladott kávékról, ideértve a kávé nevét, annak árát, aktuális héten eladott mennyiséget és a napi eladott mennyiséget. A Coffee tábla látható itt, melyet később részletesebben leírunk:

COF_NAME SUP_ID PRICE SALES TOTAL
Colombian 101 7.99 0 0
French_Roast 49 8.99 0 0
Espresso 150 9.99 0 0
Colombian_Decaf 101 8.99 0 0
French_Roast_Decaf 49 9.99 0 0

A COF_NAME mező, amely a kávé nevét tárolja, maximum 32 karakter hosszú VARCHAR típusú. Miután minden egyes kávétípust különböző névvel látunk el, a név egyértelműen meghatározza az egyes kávékat, így ez lehet a tábla elsődleges kulcsa. A SUP_ID a kávé-kiegészítő egyedi azonosítóját tartalmazza, SQL típusa INTEGER. A harmadik mező neve PRICE, típusa FLOAT, mert tizedes szám tárolására van szükség.

Megjegyzés: A pénz adat általában SQL DECIMAL vagy NUMERIC típusú mezőben tárolódik, de a DBMS-k közötti különbség miatt, és hogy elkerüljük az inkompatibilitást valamelyik régebbi JDBC-vel, most az általánosabb FLOAT típust használjuk.

A SALES mező INTEGER típusú adatokat tárol, és megmutatja, hogy mennyi kávé lett eladva az adott héten. Az utolsó mező a TOTAL, amely INTEGER típusú, és megadja, hogy mennyi kávét adtak el összesen az adott napig.

Az adatbázis SUPPLIERS nevű táblája, amely információt tárol az egyes kávé-kiegészítőkről:

SUP_ID SUP_NAME STREET CITY STATE ZIP
101 Acme, Inc. 99 Market Street Groundsville CA 95199
49 Superior Coffee 1 Party Place Mendocino CA 95460
150 The High Ground 100 Coffee Lane Meadows CA 93966

A COFFEES és a SUPPLIERS tábla is tartalmazza a SUP_ID mezőt, ezért ez a mező használható a SELECT utasításoknál, hogy a két táblát össze tudjuk kapcsolni. A SUP_ID oszlop az egyedi azonosítója a SUPPLIERS táblának. A COFFEES táblában pedig idegen kulcsként szerepel. (Azért idegen kulcs, mert ez egy másik táblából lett importálva.) Minden egyes SUP_ID csak egyszer szerepel a SUPPLIER táblában, hiszen ez az elsődleges kulcsnál követelmény. A COFEE táblában, ahol idegen kulcsként szerepel, teljesen természetes, hogy többszörösen megjelenik, hiszen egy kiegészítő eladható több típusú kávéhoz is. A fejezet későbbi részében bemutatunk néhány példát az elsődleges és az idegen kulcs használatára a SELECT lekérdezésekben.

A következő SQL utasítás létrehozza a COFFEES nevű táblát. A két külső zárójel tartalmazza az egyes oszlopok nevét és SQL típusát vesszővel elválasztva. A VARCHAR típusnál zárójelek között megadható annak maximális hossza. A példakód megadja, hogy a COF_NAME oszlop maximum 32 karakter hosszú lehet:

CREATE TABLE COFFEES (
  COF_NAME VARCHAR(32),
  SUP_ID INTEGER,
  PRICE FLOAT,
  SALES INTEGER,
  TOTAL INTEGER )

Az utasítás végén nem szerepel DBMS záró jel, mert ez az egyes DBMS-eknél különböző. Például az Oracle pontosvesszőt (;) használ a mondatok lezárásra, a Sybase pedig a go szót. Ezért a Java kódban nem szükséges megadni, a driver automatikusan a mondat végére teszi a megfelelő jelet.

Másik dolog, ami meg kell említenünk az SQL utasításokról, azok formája. A CREATE TABLE mondatban a kulcsszavak nagybetűvel és minden rész külön sorban van írva. Az SQL nem követeli ezt meg, ez a konvenció csak az olvashatóságot teszi könnyebbé. Alapvető az SQL-ben, hogy a kulcsszavakban nem kis-nagybetű érzékeny (case sensitive), így például a SELECT szót bármilyen módon le lehetett volna írni. Példának okáért a két verzióval korábbi teljesen megegyezik az SQL illetően:

Az egyes DBMS-k különbözőek lehetnek a névazonosításban. Például néhány DBMS megköveteli, hogy az oszlop és a tábla nevek pontosan megfeleljenek a CREATE TABLE utasításban megadottaknak, míg mások nem. A biztonság kedvéért nagybetűt használunk az azonosítóknál, mint például COFFEES és SUPPLIERS, mert így definiáltuk őket.

Eddig kész vagyunk az SQL utasítás megírásával, amely létrehozza a COFFEES nevű táblát. Most tegyük idézőjelek közé (készítsünk String–et), és nevezzük el ezt a String-et createTableCoffees-nek, így ezt a változót fel tudjuk használni a Java kódban.

Amint láttuk, a DBMS nem foglalkozik a sortörésekkel, de a Java programozási nyelvben az a String objektum, ami meghaladja az egy sort, nem fog lefordulni. Következésképpen, amikor String-et adunk meg, minden egyes sort idézőjelek közé kell tenni, és pluszjelet (+) használni az összefűzéshez:

String createTableCoffees = "CREATE TABLE COFFEES " +
    "(COF_NAME VARCHAR(32), SUP_ID INTEGER, PRICE FLOAT, " +
    "SALES INTEGER, TOTAL INTEGER)";

Mielőtt futtatunk egy alkalmazást, menjük végig a JDBC alapjain.

23.2.1 JDBC Statement létrehozása

A Statement objektum küldi el az SQL utasításunkat az adatbázis-kezelőnek. Egyszerűen létrehozunk egy Statement objektumot, majd végrehajtjuk az execute metódust alkalmazva. A SELECT utasítások végrehajtására az executeQuery metódus szolgál. Azoknál az utasításoknál, amelyek táblát hoznak létre, vagy módosítanak, az executeUpdate metódus használatos.

Egy már élő adatbázis kapcsolatra (Connection objektum) épül a Statement objektum létrehozására. A következő példa a már meglévő con Connections objektumot használja az stmt Statement objektum létrehozására:

Statement stmt = con.createStatement();

Ebben a pillanatban stmt létrejött, de még nincs megadva az elküldendő SQL utasítás. Az stmt execute metódusában kell ezt megadnunk. Például, a következő kódrészletben mi az executeUpdate metódusnak adjuk át a SQL utasítást:

stmt.executeUpdate("CREATE TABLE COFFEES " +
    "(COF_NAME VARCHAR(32), SUP_ID INTEGER, PRICE FLOAT, " +
    "SALES INTEGER, TOTAL INTEGER)");

Miután létrehoztunk egy createTableCoffees nevű String típusú változót, megadhatjuk ebben a formában is.

stmt.executeUpdate(createTableCoffees);

23.2.2 SQL parancs végrehajtása

Az executeUpdate metódust használtuk, mert az createTableCoffees nevű SQL utasítás egy DDL (data definiton language) utasítás. Azok a parancsok, amelyek létrehoznak, módosítanak, törölnek táblát, mind DLL utasítások és az executeUpdate metódussal hajthatók végre. Amint az a nevéből kitűnik, az executeUpdate metódus rekordok értékeinek módosítására is szolgál. Ez sokkal gyakoribb, mivel rekord módosítása többször is történhet, amíg egy tábla létrehozása csak egyszer.

A leggyakrabban használt SQL utasítást végrehajtó parancs az executeQuery. Ez a metódus használandó egy SELECT lekérdezés végrehajtásához, ami a leggyakoribb SQL utasítás.

Adatbevitel egy táblába

Megmutattuk, hogyan kell létrehozni a COFFEES nevű táblát, megadva a mezők nevét és típusát, de ez csak a tábla szerkezetét állítja be. A tábla még nem tartalmaz egyetlen adatot sem. Soronként visszük be az adatokat a táblába, megadva az egyes mezők értékét.

Megjegyezés: A beillesztendő értékeket ugyanabban a sorrendben kell megadni, mint ahogy azt deklaráltuk a tábla létrehozásakor.

A következő kód egy rekordot illeszt be: Colombian a COF_NAME oszlopba, 101 a SUP_ID oszlopba, 7.99 a PRICE, 0 a SALES és 0 a TOTAL oszlopba. (Amióta a Coffee Break működik, a heti eladási mennyiség és az eddigi összmennyiség az összes kávéfajtánál 0-val indul.) Csakúgy mint a COFFEES tábla létrehozásánál tettük, egy Statement objektumot hozunk létre, majd az executeUpdate metódust használjuk.

Mivel az SQL utasítás nem fér el egy sorba, két String-et fogunk pluszjellel összefűzni. Érdemes figyelni a szükséges szóközökre a COFFEES és a VALUES szó között. A szóköznek az aposztrófok között kell lennie vagy a COFFEES szó mögött, vagy a VALUES szó előtt; a szóköz nélkül az SQL utasítás hibásan INSERT INTO COFFEESVALUES...–nek fogja olvasni, és a DBMS a COFFEESVALUES nevű táblát fogja keresni. Szintén oda kell figyelni, hogy egyszeres idézőjelet használunk a kávé nevének megadásakor, mert az két dupla aposztróf közé van beágyazva. A legtöbb adatbázis-kezelőnél általános szabály, hogy szabadon választható, melyik aposztrófot használjuk.

Statement stmt = con.createStatement();
stmt.executeUpdate(
    "INSERT INTO COFFEES " +
    "VALUES ('Colombian', 101, 7.99, 0, 0)");

Az INSERT utasítás második sora fogja beilleszteni a rekordot a COFFEES táblába. Használjuk fel inkább újra az stmt nevű Statement objektumot, mint újat példányosítsunk minden végrehajtásnál.

stmt.executeUpdate("INSERT INTO COFFEES " +
    "VALUES ('French_Roast', 49, 8.99, 0, 0)");

A további sorokat az alábbiak szerint illesztjük be:

stmt.executeUpdate("INSERT INTO COFFEES " +
    "VALUES ('Espresso', 150, 9.99, 0, 0)");
stmt.executeUpdate("INSERT INTO COFFEES " +
    "VALUES ('Colombian_Decaf', 101, 8.99, 0, 0)");
stmt.executeUpdate("INSERT INTO COFFEES " +
    "VALUES ('French_Roast_Decaf', 49, 9.99, 0, 0)");

Adatok kinyerése a táblából

Most, hogy a COFFEES táblában már adatok is vannak, megírhatjuk a SELECT utasítást az adatok kinyeréséhez. A következő kódban a csillag (*) mondja meg, hogy az összes oszlopot akarjuk kiválasztani. Miután nincs WHERE feltétellel meghatározva, hogy mely rekordokat adja vissza, a következő SQL utasítás az összes sort vissza fogja adni.

SELECT * FROM COFFEES

Az eredmény megegyezik azzal, amit az a kimenet ad, amikor az SQL lekérdezést direkt az adatbázis-kezelőnek adjuk meg. Amikor egy Java alkalmazáson keresztül érjük el az adatbázist, vissza kell, hogy nyerjük az adatokat, hogy felhasználhassuk. A következő részben ezt mutatjuk be.

Egy másik példa a SELECT utasításra, amelyik visszaadja a kávék listáját és az árat.
SELECT COF_NAME, PRICE FROM COFFEES

A lekérdezés eredménye a következő:

A fenti SELECT utasítás visszaadta az összes rekordot, a következő pedig csak azokat, amelyek kevesebbe kerülnek, mint $9.00

SELECT COF_NAME, PRICE
FROM COFFEES
WHERE PRICE < 9.00

23.2.3 Lekérdezések eredményének feldolgozása

Nézzük meg, hogy az előző SQL parancsot hogyan lehet Javában futtatni, és hogyan lehet az eredményt kinyerni belőle.

A lekérdezést futtató executeQuery metódus egy ResultSet objektumot ad, amiből az adatokat kinyerhetjük.

ResultSet rs = stmt.executeQuery(
    "SELECT COF_NAME, PRICE FROM COFFEES");

A next metódus

Az eredményhalmaz elérhetővé tevő rs objektummal minden rekordon végig fogunk menni, hogy az egyes rekordok adatait meg tudjuk jeleníteni a képernyőn. A next metódus az úgynevezett a kurzort mindig a következő sorra mozgatja (ezt aktuális sornak hívjuk). Mivel a kurzor kezdetben még nem mutat egyetlen sorra sem (mintha a táblázat előtt lennénk), az első meghívása után érhető el az eredmény első sora. Egymást követő metódushívásokkal a kurzort a következő rekordokra léptetjük, így járjuk be a teljes halmazt.

A getXXX metódusok

A get kezdetű metódusok segítségével tudjuk az aktuális rekord egyes mezőit lekérdezni. Például az SQL VARCHAR típusú COF_NAME mezőjét a getString-el olvashatjuk ki. Az SQL FLOAT típusú PRICE mezője logikusan a getFloat metódussal kérdezhető le.

Nézzük a teljes kódot:

String query = "SELECT COF_NAME, PRICE FROM COFFEES";
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
    String s = rs.getString("COF_NAME");
    float n = rs.getFloat("PRICE");
    System.out.println(s + "   " + n);
}

A kimeneten ehhez hasonló fog megjelenni:

Colombian   7.99
French_Roast   8.99
Espresso   9.99
Colombian_Decaf   8.99
French_Roast_Decaf   9.99

Paraméterként nem csak a mező nevét, hanem oszlopindexét is használhatjuk:

String s = rs.getString(1);
float n = rs.getFloat(2);