VIII. Adatbázisok kezelése

Bevezetés

Ebben a leckében a relációs adatbázisok kezelésérôl lesz szó. A NetREXX a Java nyelv JDBC (Java Database Connectivity) API-ját használja erre a célra, ezért ha valaki ezt már ismeri, akkor annak kevés újdonságot fog tartalmazni ez a lecke. Az ODBC (Open Database Connectivity) API-t ismerôk is elônyös helyzetben vannak, mivel a JDBC nagyon hasonlít az ODBC-re. Az egyszerûség kedvéért a DB2 (UDB) adatbázisból fogunk kiindulni, amely már régóta támogatja a JDBC API-t. A csak ODBC-t engedélyezô adatbázisokat is lehet NetREXX-bôl és Java-ból programozni, amennyiben rendelkezésre áll az úgynevezett JDBC-ODBC híd, amely a JDBC hívásokat fordítja ODBC-re.

A JDBC alapjai

A JDBC API tulajdonképpen nem más, mint egy szabványosított függvénygyûjtemény, amely segítségével elvileg platformfüggetlen módon lehet a különbözô típusú adatbázisokat használni. Az adatbázisokat elôállító cégek feladata arról gondoskodni, hogy termékük fogadja és saját belsô nyelvére fordítsa a JDBC hívásokat, a kért mûveleteket elvégezze és visszaküldje az ismét csak JDBC nyelvre fordított eredményt. Az adatbázisok ezen részét JDBC drivernek nevezik, s a leggyakrabban használt adatbázisokhoz mellékelni szokták. JDBC driverek és egyéb hasznos információk beszerzését segíti a SUN által üzemeltetett http://www.javasoft.com/products/jdbc oldal is.

Egy adatbázishoz történô kapcsolódáshoz szükség van az adatbázis URL-jére (vagyis címére), amely birtokában a JDBC DriverManager osztály segítségével alkothatunk egy olyan objektumot, amely rendelkezik a Connection interfésszel. A klasszikus adatbázis-kezelés korában a session felelt meg ennek a Connection objektumnak. Az adatbázisban történô keresés pedig egy olyan objektumnak felel meg, amely rendelkezik a Statement interfészt. A keresést leíró SQL parancsot az objektum karakterláncaként adjuk meg. Már senkit sem lep meg, hogy a keresés eredménye is egy objektum, amely a ResultSet interfésszel rendelkezik. A JDBC URL-ek általános alakja a következô:

jdbc:<subprotocol>:<subname>

A subprotocol az esetek többségében az adatbázis neve, vagy valamilyen hálózati név. A subname a tulajdonképpeni URL, amelyet megfosztottak a protokoll résztôl:

jdbc:db2://os2.rulez.org:8888/sample

A gyakorlat

Az elméleti tudnivalók után itt az ideje, hogy megtekintsük elsô példaprogramunkat, amely a DB2-höz adott egyik minta adatbázisból keresi ki az osztályok és azok menedzsereinek a nevét:

/* lecke08a.nrx */

import java.sql.

parse arg url prefix -- feldolgozzuk az argumenteket
if url = "" then
	url = "jdbc:db2:sample"
else do              -- ellenôrizzük az URL-t
	parse url p1 ":" p2 ":" rest
	if p1 \= "jdbc" | p2 \= "db2" | rest = "" then do
		say "Használat: java lecke08a [] []"
		exit 8
	end
end
if prefix = "" then prefix = "userid"

do -- betöltjük a DB2 JDBC drivereit
	say "A JDBC driverek betöltése folyik..."
	Class.forName("COM.ibm.db2.jdbc.app.DB2Driver").newInstance()
	-- Class.forName("COM.ibm.db2.jdbc.net.DB2Driver").newInstance()
catch e1 = Exception
	say "A DB2 drivereket nem sikerült betölteni!"
	say "Exception ("e1") történt: \n" e1.getMessage()
	exit 1
end 

do -- kapcsolódunk a DB2 kiszolgálóhoz
	say "Kapcsolódás:" url
	jdbcCon = Connection DriverManager.getConnection(url, "userid", "password")
catch e2 = SQLException
	say "SQLException történt a kapcsolódás során!"
	loop while (e2 \= null)
		say "SQLState:" e2.getSQLState()
		say "Üzenet: " e2.getMessage()
		say "Vendor: " e2.getErrorCode()
		say
		e2 = e2.getNextException()
	end
	exit 1
end

do -- letöltjük az osztályok és menedzserek listáját
	say "A keresés összeállítása folyik..."
	query = "SELECT deptno, deptname, lastname, firstnme" -
		"FROM" prefix".DEPARTMENT dep, "prefix".EMPLOYEE emp" -
		"WHERE dep.mgrno=emp.empno ORDER BY dep.deptno"
	stmt = Statement jdbcCon.createStatement()
	say "A keresés végrehajtása folyik:"
	loop i=0 to (query.length()-1)%75
		say " " query.substr(i*75+1,75)
	end
	rs = ResultSet stmt.executeQuery(query)
	say "Eredmények:"
	loop row=0 while rs.next()
		say rs.getString("deptno") rs.getString("deptname") -
		"menedzsere" rs.getString("lastname") rs.getString("firstnme")
	end
	rs.close()
	stmt.close()
	jdbcCon.close()
	say "Letöltöttünk" row "osztályt."
catch e3 = SQLException
	say "SQLException történt!"
	loop while (e3 \= null)
		say "SQLState:" e3.getSQLState()
		say "Üzenet: " e3.getMessage()
		say "Vendor: " e3.getErrorCode()
		say
		e3 = e3.getNextException()
	end
end 

A példaprogramból fordított Java program futtatása során a következôt kell hogy lássuk a képernyôn:

d:\>netrexx\lecke08>java lecke08a jdbc:db2:sample userid

A JDBC driverek betöltése folyik...
Kapcsolódás: jdbc:db2:sample
A keresés összeállítása folyik...
A keresés végrehajtása folyik:
	SELECT deptno, deptname, lastname, firstnme FROM userid.DEPARTMENT dep,
	userid.EMPLOYEE emp WHERE dep.mgrno=emp.empno ORDER BY dep.deptno
Eredmények:
A00 SPIFFY COMPUTER SERVICE DIV. 	menedzsere HAAS CHRISTINE
B01 PLANNING 				menedzsere THOMPSON MICHAEL
C01 INFORMATION CENTER 			menedzsere KWAN SALLY
D11 MANUFACTURING SYSTEMS 		menedzsere STERN IRVING
D21 ADMINISTRATION SYSTEMS 		menedzsere PULASKI EVA
E01 SUPPORT SERVICES 			menedzsere GEYER JOHN
E11 OPERATIONS 				menedzsere HENDERSON EILEEN
E21 SOFTWARE SUPPORT 			menedzsere SPENSER THEODORE
Letöltöttünk 8 osztályt.

A program elôször betölti a java.sql osztályt, amely a JDBC driverek kivételével az összes szükséges JDBC osztályt és interfészt tartalmazza. Ezután ellenôrizzük a paraméterek jelenlétét és azok formátumát. Az elsô paraméter az adatbázis URL-je, a második pedig az adatbázis tulajdonosa kell hogy legyen. A következô lépésként betöltjük a JDBC drivereket a Class osztály forName metódusával. Sikeres betöltés esetén regisztrálásra kerül (a DriverManager.registerDriver() metódus automatikus meghívásával) a megfelelô driver, ellenkezô esetben pedig ClassNotFoutExceptiont kapunk. A driver betöltése után kapcsolódunk az adatbázishoz a getConnection metódussal. Ezek után már nincs más hátra, mint összeállítani az SQL parancsot a createStatement metódussal, amelyet aztán az executeQuery metódussal hajtunk végre. Eredményül egy úgynevezett ResultSetet kapunk vissza, amely tartalmazza mindazon adatbázis-sorokat, amelyek megfelelnek a keresési kritériumnak. A sorokat megjelenítjük és megszakítjuk a kapcsolatot az adatbázissal.

Amint láthattuk, az eredményül kapott sorok egyes elemeit a get.String metódussal olvastuk ki. A JDBC nagyon sok más adattípus kiolvasására alkalmas metódust is tartalmaz. Az alábbi táblázatban összefoglaltuk a használható metódusokat és megadtuk azt is, hogy milyen típusú SQL illetve Java adat feldolgozása alkalmasak. Mindegyik metódusra egyaránt érvényes, hogy paraméterként meg lehet nekik adni a lekérdezendô oszlop sorszámát vagy pedig annak nevét is. Mivel az oszlopok sorszáma változhat, ezért inkább a nevet ajánlott használni.

Metódus:SQL adattípusJava adattípus
getStringCHARString
getStringVARCHARString
getStringLONGVARCHARString
getAsciiStreamLONGVARCHARInputStream
getUnicodeStreamLONGVARCHARInputStream
getBigDecimalNUMERICjava.math.BigDecimal
getBigDecimalDECIMALjava.math.BigDecimal
getBooleanBITboolean
getByteTINYINTbyte
getShortSMALLINTshort
getIntINTEGERint
getLongBIGINTlong
getFloatREALfloat
getDoubleFLOATdouble
getDoubleDOUBLEdouble
getDateDATEjava.sql.Date
getTimeTIMEjava.sql.Time
getTimestampTIMESTAMPjava.sql.Timestamp
getBinaryStreamLONGVARBINARYInputStream
getBytesminden SQL típusbyte array
getObjectminden SQL típusObject

Bonyolultabb keresések esetében (ha például nem tudjuk elôre, hogy hány oszlopot fog tartalmazni az eredménysor) a getMetaData metódust lehet használni:

md = ResultSetMetaData rs.getMetaData()

A ResultSetMetaData objektum révén ugyanis elérhetôvé válnak olyan metódusok, amelyekkel például le lehet kérdezni az eredményként kapott ResultSet oszlopainak számát (getColumnCount) vagy nevét (getColumnName). A meta-metódusokról további információt a JDBC API dokumentációjában lehet találni, itt most nem térünk ki részletesen rájuk.

További példák a gyakorlatból

Természetesen az adatbázisok kezelése nem merül ki keresések végrehajtásában és kiértékelésében. Nagyon gyakran van például szükség adatok eltávolítására, új adatok beszúrására, vagy éppen adatok frissítésére. Mivel a törlés nagyon egyszerû, a beszúrás pedig nagyon hasonlít a frissítésre, ezért a továbbiakban csak ez utóbbival fogunk foglalkozni.

Frissítések során általában több, azonos szerkezetû, ám konkrét paramétereiben különbözô SQL parancsot hajtunk végre. A sebesség növelése céljából érdemes ilyen esetekben az elôkészített SQL parancsokra hagyatkozni, amelyek olyan SQL parancsok, amelyekben egy kérdôjellel helyettesítjük a konkrét paramétereket:

updateQ = 'UPDATE USERID.EMPLOYEE SET firstname = ? WHERE empno = ?'

Az SQL parancsot ezek után a Connection objektum prepareStatement metódusával lehet elôkészíteni:

updateStmt = PreparedStatement jdbcCon.prepareStatement(updateQ)

Magától értetôdik, hogy a parancs végrehajtása elôtt meg kell adnunk a paraméterek konkrét értékét, amit a fenti táblázatban bemutatott get metódusok párjaival, a set metódusokkal tehetünk meg:

updateStmt.setString(1, 'Gyula')
updateStmt.setString(2, '000013')

A fenti kódrészletben például az alkalmazott keresztneveként Gyulát, sorszámaként pedig 13-at adtunk meg. Ezek után nincs más hátra, mint végrehajtani a konkretizált SQL parancsot. A végrehajtásra háromféle metódus áll a rendelkezésre. A Statement.executeQuery-val select, a Statement.executeUpdate-tel update, a Statement.execute-tal pedig összetett ResultSet lekérdezésére alkalmas parancsokat hajthatunk végre. Példaként a frissítés végrehajtására (update) alkalmas Statement.executeUpdate metódust mutatjuk be az alábbi programban, amely az elôzôekben már használt példa-adatbázisban kicseréli az alkalmazottak keresztnevében szereplô nagybetûket kicsire, az elsô betû kivételével.

/* lecke08b.nrx */

import java.sql.

parse arg url prefix lowup -- feldolgozzuk a megadott paramétereket

if url = "" then
	url = "jdbc:db2:sample"
else do -- ellenôrizzük az URL formátumát
	parse url p1 ":" p2 ":" rest
	if p1 \= "jdbc" | p2 \= "db2" | rest = "" then do
		say " Usage: java lecke08b [] [] [U]"
 		exit 8
 	end
end
if prefix = "" then prefix = "userid"
if lowup \= "U" then lowup = "L"

do -- a DB2 támogatás betöltése
	say "Betöltjük a DB2 drivereket..."
	Class.forName("COM.ibm.db2.jdbc.app.DB2Driver").newInstance()
	-- Class.forName("COM.ibm.db2.jdbc.net.DB2Driver").newInstance()
catch e1 = Exception
	say "A driverek betöltése nem sikerült!"
	say "Exception ("e1") történt:\n " e1.getMessage()
 	exit 1
end

do -- kapcsolódunk az adatbázishoz
	say "Kapcsolódás:" url
	jdbcCon = Connection DriverManager.getConnection(url, "userid" , "password")
catch e2 = SQLException
	say " SQLException(s) történt a kapcsolódás során!"
	loop while (e2 \= null)
		say "SQLState:" e2.getSQLState()
 		say "Üzenet: " e2.getMessage()
 		say "Vendor: " e2.getErrorCode()
 		say
 		e2 = e2.getNextException()
 	end
 	exit 1
end

do -- a keresztnevek letöltése és frissítése
	say "Kezdôdik a frissítés..."
	updateQ = "UPDATE" prefix".EMPLOYEE SET firstnme = ? WHERE empno = ?"
	updateStmt = PreparedStatement jdbcCon.prepareStatement(updateQ)
 	say "A keresés folyik..."
	query = "SELECT firstnme, lastname, empno FROM" prefix".EMPLOYEE"
	stmt = Statement jdbcCon.createStatement()
 	rs = ResultSet stmt.executeQuery(query) -- végrehajtjuk a selectet
	loop row=0 while rs.next()
		firstname = String rs.getString("firstnme")
 		if lowup = "U" then firstname = firstname.toUpperCase()
 		else do
 			dChar = firstname.charAt(0)
 			firstname = dChar || firstname.substring(1).toLowerCase()
 		end
 	updateStmt.setString(1, firstname) -- a konkrét értékek behelyettesítése
	updateStmt.setString(2, rs.getString("empno"))
	say "Frissítem" rs.getString("lastname") firstname ": \0"
 	say updateStmt.executeUpdate() "sor frissült" -- végrehajtás
end

rs.close()
stmt.close()
updateStmt.close()
jdbcCon.close()

say "Frissítettük "row" alkalmazott adatait."
catch e3 = SQLException
	say "SQLException(s) történt!"
	loop while (e3 \= null)
		say "SQLState:" e3.getSQLState()
 		say "Üzenet: " e3.getMessage()
		say "Vendor: " e3.getErrorCode()
 		say
 		e3 = e3.getNextException()
	end
end

Ha futtatjuk a programot, akkor az alábbiakhoz hasonló kimenetet fogunk látni:

d:\netrexx\lecke08>java lecke08b jdbc:db2:sample userid
...
Frissítem MEHTA Ramlal 	: 1 sor frissült
Frissítem LEE Wing 	: 1 sor frissült
Frissítem GOUNOT Jason 	: 1 sor frissült
Frissítettük 32 alkalmazott adatait.

A program eleje egy az egyben megegyezik az elôzô példaprograméval. Az adatbázishoz történô kapcsolódás után viszont összeállítunk két SQL parancsot. Az elsô egy elôkészített SQL parancs, amely a keresztnevek frissítését végzi. A második parancs kiválasztja a táblázat alkalmazottakat tartalmazó sorait. Ezen parancs segítségével lépkedünk végig egy hurokban a teljes táblázaton. Amennyiben a parancssorban harmadik paraméterként megadtuk az U kapcsolót, akkor nagybetûsre alakítjuk az alkalmazott keresztnevét, ellenkezô esetben viszont elvégezzük a feladatként kitûzött konverziót. A konkrét adatok ismeretében behelyettesítjük az új keresztnevet és az alkalmazott sorszámát, és elvégezzük a sor frissítését. A teljes táblázat feldolgozása után felszabadítjuk a lefoglalt erôforrásokat.

Az eddig bemutatott adatokat manipuláló SQL parancsokon (Data Manipulation Language) kívül léteznek még olyan parancsok is, amelyek az adatbázis struktúráját változtatják meg (Data Definition Language). Ezeknek a parancsoknak a végrehajtása gyakorlatilag annyiból áll, hogy összeállítunk egy, a DDL parancsot tartalmazó karakterláncot, amelyet az adatbázis-menedzserhez továbbítunk az executeUpdate metódussal. A JDBC API egy másik részét képezik a tárolt procedúrák (statikus SQL parancsok), amelyeket arra használnak, hogy lefordított nyelvekbe programozzanak SQL parancsokat. Mivel a JDBC lehetôvé teszi ezen kódrészletek meghívását, ezért NetREXX-bôl is automatikusan lehetôség nyílik a statikus SQL-ek alkalmazására.

REXX GYÍK:

K1. Nem sikerül kapcsolódni egy adatbázishoz, pedig biztos, hogy jó URL-t adok meg! Mi lehet a baj?
V1. Ellenôrizze, hogy fut-e az adatbázist tartalmazó gépen a JDBC daemon program. Ez ugyanis az a program, amely kezeli a kapcsolódó klienseket. DB2-n a daemon a db2jstrt paranccsal indítható.

K2. Nem sikerül kapcsolódni a példaprogramokban megadott példa-adatbázishoz, pedig jó az URL és a kiszolgálón fut a JDBC daemon. Mit tegyek?
V2. Ellenôrizze, hogy jelen van-e a gépen a példa-adatbázis. Ha nem lenne, akkor hozza létre a db2sampl parancs kiadásával!


Gyakorlatok:

1. Készítsen egy példaprogramot, amely egy megadott sorszámú alkalmazottakat eltávolít a leckében használt adatbázisból!

2. Készítsen egy példaprogramot, amely hozzáad egy új alkalmazottakat az adatbázishoz!

Kádár Zsolt
2000. 05. 28.
[ Elôzô lecke | Következô lecke | Tartalom ]