Sunteți pe pagina 1din 23

Accesul la baze de date folosind JDBC

INTRODUCERE
Aplicaţiile care folosesc baze de date sunt, în general, aplicaţii complexe folosite
pentru gestionarea unor informaţii de dimensiuni mari într-o manieră sigură şi eficientă.
Crearea unei baze de date
Crearea unei baze de date se face uzual folosind aplicaţii specializate oferite de
producătorul tipului respectiv de sistem de gestiune a datelor.
Accesul la baza de date
Se face prin intermediul unui driver specific tipului respectiv de SGBD. Acesta
este responsabil cu accesul efectiv la datele stocate, fiind legatura dintre aplicaţie şi
baza de date.
Limbajul SQL
SQL (Structured Query Language) reprezintă un limbaj de programare ce permite
interogarea şi actualizarea informaţiilor din baze de date relaţionale. Acesta este
standardizat astfel încât diverse tipuri de drivere să se comporte identic, oferind astfel o
modalitate unitară de lucru cu baze de date.

JDBC (Java Database Connectivity) este o interfaţă standard SQL de acces la


baze de date. JDBC este constituită dintr-un set de clase şi interfeţe scrise în Java,
furnizând mecanisme standard pentru proiectanţii aplicaţiilor ce folosesc baze de date.
Într-o arhitectură client-server, bazele de date se pot afla pe acceaşi maşină sau pe o altă
maşină cu care clientul este conectat dintr-un intranet sau chiar Internet.
Folosind JDBC este uşor să transmitem secvenţe SQL către baze de date
relaţionale. Cu alte cuvinte, nu este necesar să scriem un program pentru a accesa o
bază de date Oracle, alt program pentru a accesa o bază de date Sybase şi asa mai
departe. Este de ajuns să scriem un singur program folosind API-ul (Application
Programming Interface) JDBC şi acesta va fi capabil să comunice cu drivere diferite,
trimiţând secvenţe SQL către baza de date dorită. Bineînţeles, scriind codul sursă în Java,
ne este asigurată portabilitatea programului.
Pentru început putem spune că JDBC furnizează acces orientat pe obiecte (OOP)
la bazele de date prin definirea de clase şi interfeţe care înfăşoară diverse concepte
abstracte, cum ar fi cele prezentate în tabelul următor:
Concepte înfăşurate Clasa/clasele sau interfeţele corespunzătoare
Conexiuni la baze de date Connection
Interogări SQL Statement, PreparedStatement,
CallableStatement
Mulţimi rezultat ResultSet
Obiecte mari binare sau Blob (eng. Binary Large Objects)
caracter Clob (eng. Character Large Objects)
Drivere Driver
Gestionari de drivere DriverManager
De asemenea, standardul JDBC defineşte o serie de interfeţe care trebuie
implementate de creatorii driverelor cu scopul de a oferi dezvoltatorilor informaţii despre
baza de date interogată, DBMS-ul (Database Management System sau SGBD) folosit.
Vom numi aceste informaţii metadate în sensul de „date despre date”. Interfeţele puse la
dispoziţie pentru obţinerea metadatelor sunt prezentate în următorul tabel:

Interfaţă Descriere
DatabaseMetaData Interfaţă folosită de către cei care produc drivere
pentru a informa utilizatorii despre capabilităţile
oferite de DBMS-uri împreună cu driverul JDBC
folosit.
ParameterMetaData Interfaţă folosită pentru a obţine informaţii despre
tipurile şi proprietăţile parametrilor dintr-un obiect
PreparedStatement.
ResultSetMetaData Interfaţă folosită pentru a obţine informaţii despre
tipurile şi proprietăţile coloanelor dintr-un ResultSet.

Pachetele care oferă suport pentru lucrul cu baze de date sunt java.sql ce
reprezintă nucleul tehnologiei JDBC şi javax.sql preluat de pe platforma J2EE. Ceea ce
trebuie remarcat este faptul că aplicaţiile care folosesc baze de date trebuie să includă
pachetul java.sql, şi nu pachetul care conţine implementarea driverului particular folosit.
Totuşi, calea spre pachetul conţinând driverul trebuie sa fie prezentă în CLASSPATH.

Interfaţa DatabaseMetaData
După realizarea unui conexiuni la o bază de date, putem apela metoda
getMetaData pentru a afla diverse informaţii legate de baza respectivă. Ca rezultat al
apelului metodei, vom obţine un obiect de tip DatabaseMetaData ce oferă metode pentru
determinarea tabelelor, procedurilor stocate, capabilităţilor conexiunii, gramaticii SQL
suportate, etc. ale bazei de date.
Programul următor afişează numele tuturor tabelelor dintr-o bază de date
înregistrată în ODBC:
import java.sql.*;
public class TestMetaData {
public static void main ( String [] args ) {
String url = "jdbc:odbc:test";
try {
Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
} catch ( ClassNotFoundException e) {
System.out.println (" Eroare incarcare driver !\n" + e);
return ;
}
try {
Connection con = DriverManager.getConnection (url);
DatabaseMetaData dbmd = con.getMetaData ();
ResultSet rs = dbmd.getTables (null , null , null , null );
while (rs.next ())
System.out.println (rs. getString (" TABLE_NAME "));
con.close ();
} catch ( SQLException e) {
e.printStackTrace ();
}
} }

Interfaţa ParameterMetaData
Metoda getMetaData a clasei PreparedStatement recuperează obiectul
ResultSetMetaData care conţine o descriere a coloanelor care vor fi întoarse când se
execută PreparedStatement. Metoda getParameterMetaData() întoarce un obiect
ParameterMetaData care conţine descrierea parametrilor IN sau OUT folosiţi de
PreparedStatement.

Interfaţa ResultSetMetaData
Metadatele unui ResultSet reprezintă informaţiile despre rezultatul conţinut în
acel obiect cum ar fi numărul coloanelor, tipul şi denumirile lor, etc. Acestea sunt
obţinute apelând metoda getMetaData pentru ResultSet-ul respectiv, care va returna un
obiect de tip ResultSetMetaData ce poate fi apoi folosit pentru extragerea informaţiilor
dorite.

Unele din metodele utilizate pentru a accesa ResultSetMetaData sunt


următoarele:
• getColumnCount() — Returnează numărul de coloane din ResultSet
• getColumnDisplaySize(int coloana)— Returnează lungimea maximă a coloanei
exprimată printr-un număr de caractere
• getColumnLabel(int coloana) — Returnează titlul coloanei pentru tipărire şi afişare
• getColumnName(int coloana) — Returnează numele coloanei
• getColumnType(int coloana) — Returnează indicele tipului de date SQL asociat
coloanei
• getColumnTypeName(int coloana)— Returnează numele tipului de date SQL asociat
coloanei
• getPrecision(int coloana)— Returnează numărul de cifre corespunzător coloanei
• getScale(int coloana) —Returnează numărul de cifre după punct din coloană
• getTableName(int coloana) — Returnează numele tabelului
• isAutoIncrement(int coloana) — Returnează true dacă coloana este numerotată în mod
automat
• isCurrency(int coloana) — Returnează true dacă valoarea coloanei este frecventă
• isNullable(int coloana)— Returnează true dacă valoarea coloanei poate fi setată la
valoarea NULL

ResultSet rs = stmt.executeQuery("SELECT * FROM tabel");


ResultSetMetaData rsmd = rs.getMetaData();
// Aflam numarul de coloane
int n = rsmd.getColumnCount();

// Aflam numele coloanelor


Sring nume[] = new String[n+1];
for(int i=1; i<=n; i++)
nume[i] = rsmd.getColumnName(i);
CLASIFICAREA DRIVERELOR JDBC

Sub aspectul funcţionalităţii, există patru tipuri de drivere JDBC:


1. Puntea JDBC-ODBC: Acţionează ca o legătură dintre JDBC şi alt mecanism de
conectivitate a bazelor de date numit ODBC (Object DataBase Conectivity).
Acest driver este folosit în aplicaţii de accesare a datelor unde nu există drivere
JDBC pure. Puntea traduce metodele JDBC în apeluri de funcţii ODBC şi
necesită ca bibliotecile ODBC native şi driverele ODBC să fie instalate şi
configurate pentru fiecare client ce foloseşte un driver de tip 1. Această cerinţă
reprezintă o limitare pentru multe aplicaţii, funcţionând doar pentru sistemele de
operare Microsoft Windows şi Sun Solaris.

Clasa Java care descrie acest tip de driver JDBC este:


sun.jdbc.odbc.JdbcOdbcDriver
şi este inclusă în distribuţia standard J2SDK. Specificarea bazei de date se face
printr-un URL de forma:
jdbc:odbc:identificator
unde identificator este profilul (DSN-Data Source Name) creat bazei de date în
ODBC.

2. Java-API nativ: Aceste drivere folosesc interfaţa nativă Java(Java Native


Interface) pentru a face apeluri direct la API-ul unei baze de date locale.Ca şi
driverele de tip 1, driverele de tip 2 necesita instalarea şi configurarea biblotecilor
client bază de date native pe maşina client. Sunt foarte convenabile când există
biblioteci de accesare a datelor scrise în limbajul C, însă acestea nu sunt portabile
pe toate platformele. Driverele de tip 2 sunt dedicate unui singur DBMS şi sunt în
general mai rapide decât cele de tip 1.

Clase Java care implementează astfel de drivere pot fi procurate de la producătorii


de
SGBD-uri, distribuţia standard J2SDK neincluzând nici unul.
3. Java-protocol de reţea: Acest tip de drivere sunt drivere Java pure care folosesc
un protocol de reţea(TCP/IP) penrtu a comunica cu aplicaţia JDBC middleware.
Apoi, aplicaţia JDBC middleware traduce cererile JDBC folosind protocolul de
reţea în apeluri de funcţii specifice bazelor de date. Tipul 3 de drivere reprezintă
soluţia cea mai flexibilă, deoarece nu necesită biblioteci de bază de date native pe
client şi se poate conecta la mai multe baze de date diferite.

4. Java-protocol bază de date: Acest tip de drivere sunt drivere Java pure care
implementează un protocol de bază de date(ex. Oracle SQL Net) pentru a
comunica direct cu baza de date. De aceea, aceste drivere sunt cele mai rapide. Ca
şi driverele de tip 3, ele nu necesită biblioteci de bază de date native şi pot fi
folosite distribuit fără instalarea clientului. Driverele de tip 4 sunt specifice unui
singur DBMS.

Driverele de tip 3 si 4 oferă toate avantajele tehnologiei Java, printre care


posibilitatea download-ului acestora în timp ce aplicaţia este folosită.

Utilizarea punţii JDBC-ODBC

Puntea face legătura dintre aplicaţie si ODBC, care reprezintă un mecanism


standard orientat spre programatorii C/C++, pe care sistemul de operare Windows îl
foloseşte ca interfaţă universală pentru conectarea cu baze de date de diverse tipuri.
Putem spune că, în cazul folosirii unui driver de tip 1, avem de a face de fapt cu două
drivere şi anume puntea propriu-zisă, reprezentată de driver-ul JDBC-ODBC, respectiv
driverul ODBC corespunzător DBMS-ului interogat, care trebuie instalat în prealabil.
Prezentăm în continuare un exemplu care ilustrează modul prin care se accesează
dintr-o aplicaţie Java o bază de date creată în Acces. Presupunem că baza de date deja
creată şi are numele arhiva.mdb. Pentru a putea face interogări asupra ei, trebuie mai întâi
să o introducem ca sursă de date în ODBC. Pentru aceasta, trebuie urmaţi următorii paşi:
din ControlPanel alegem ODBC Data Sources (32 bit), ceea ce determină apariţia
ferestrei ODBC DataSource Administrator. În rubrica User DNS, folosind butonul
Add… şi scenariul (eng.wizard) pe care acesta îl declanşează, selectăm pe rând driverul
ODBC (În cazul prezent, spre Access), respectiv baza de date asupra căreia se doresc a se
face interogările, şi punem numele acestei surse de date arhiva_de_cancelarie, aşa cum
se vede în exemplul următor:
Până în acest punct, am creat sursa de date. Pentru conectarea efectivă, trebuie sa
ataşăm aplicaţiei driver spre ODBC. Driverul fiind inclus în distribuţia standard, nu mai
este necesar să introducem în CLASSPATH calea spre clasele componente, ci este
îndeajuns să-l încărcăm dinamic printr-un apel
Class.forName(”sun.jdbc.odbc.JdbcOdbcDriver”).
Urmează realizarea conexiunii, care se face cel mai convenabil printr-o secvenţă
de forma:
// generarea adresei
String url=”jdbc:odbc:arhiva_de_cancelarie”;
//setarea proprietatilor
java.util.Properties prop = new java.util.Properties();
prop.put(”charSet”, ”UTF-8”);
prop.put(”user”, ”utilizator”);
prop.put(”password”, ”parola”);
//conectarea la baza de date
con = DriverManager.getConnection(url,prop);

Prin intermediul proprietăţii charset, se oferă suport pentru internaţionalizare,


fiind posibilă alegerea unui alt set de caractere, diferit de cel folosit implicit de DBMS.
Această facilitate este valabilă numai pentru driverele JDBC-ODBC care vin începând cu
distribuţia 1.4 a platformei Java.
Prezentăm în continuare o metodă prin care testează conexiunea spre baza de date
arhiva.mdb, presupunând că s-a creat mai întâi pentru aceasta sursa de date
arhiva_de_cancelarie:
static String driver = ”sun.jdbc.odbc.JdbcOdbcDriver”;
static String parola = ” ”;
static String utilizator = ” ”;
static String subProtocol = ”odbc”;
static String bazaDate = ”arhiva_de_cancelarie”;
//…
public static boolean testeazaConexiune()
{
boolean rezultat = true;
try{
Class.forName(driver);// driver-ul este încărcat dinamic
String url=”jdbc:”+subProtocol+”:”+bazaDate;
System.out.println(”A rezultat următorul url: ”+url);
// se va afisa: A rezultat următorul url: jdbc: odbc: arhiva_de_cancelarie
Connection conex = DriverManager.getConnection(url,utilizator,parola);
Statement stare = conex.createStatement();
}
catch(Exception ex){
System.out.println(”Eroare la conectare:” +ex.toString());
rezultat=false;
}
return rezultat;
}

Codul Java folosit în aplicaţii rămâne valid după tranzacţia spre alt sistem de
gestiune a bazelor de date, deoarece orice driver de orice tip respectă API-ul JDBC,
singurul pas necesar tranzacţiei fiind schimbarea driverului.

MAPĂRI DE TIPURI ÎNTRE SQL ŞI JAVA

Tipurile de date din SQL sunt diferite de tipurile de date suportate de limbajul
Java. De asemenea, există deosebiri semnificative între tipurile suportate de diferite
DBMS-uri existente pe piaţă. Pentru a evita incompatibilităţile, JDBC defineşte o serie de
tipuri generice SQL prin intermediul clasei java.sql.Types, ceea ce permite
programatorului să nu fie interesat de numele tipurilor de date folosite de DBMS-ul la
care se conectează, această sarcină revenind driverului. Programatorul interacţionează
doar cu tipurile JDBC, fiind definită o mapare standard între tipurile JDBC şi tipurile
Java. Tabelul de mai jos prezintă maparea JDBC-Java:

Tip JDBC Tip Java


CHAR String
VARCHAR String
LONGVARCHAR String
NUMERIC java.math.BigDecimal
DECIMAL java.math.BigDecimal
BIT Boolean
TINYINT byte
SMALLINT short
INTEGER int
BIGINT long
REAL float
FLOAT double
DOUBLE double
BINARY byte[ ]
VARBINARY byte[ ]
LONGVARBINARY byte[ ]
DATE java.sql.Date
TIME java.sql.Time
TIMESTAMP java.sql.TimeStamp
CLOB Clob
BLOB Blob
ARRAY Array
DISTINCT tip corespunzător
STRUCT Struct
REF Ref
JAVA_OBJECT clasa JAVA corespunzătoare

Procesul obţinerii de informaţii dintr-o baza de date folosind JDBC implică în


principiu cinci paşi:
1. înregistrarea driverului JDBC folosind gestionarul de drivere
DriverManager.
2. stabilirea uni conexiuni cu baza de date;
3. execuţia unei instrucţiuni SQL;
4. procesarea rezultatelor;
5. închiderea conexiunii cu baza de date.

1.Înregistrarea driverului JDBC folosind clasa DriverManager

Funcţionalitatea managerului de drivere este aceea de a menţine o referinţă către


toate obiectele driver disponibile în aplicaţia curentă. Un driver JDBC este înregistrat
automat de managerul de drivere atunci când clasa driver este încărcată dinamic. Pentru
încărcarea dinamică a unui driver JDBC, se foloseşte metoda Class.forName(). După
cum se observă, nu este nevoie de creearea unei instanţe a clasei driver o data ce aceasta a
fost încărcată. Dacă se apelează metoda newInstance(), se va realiza un duplicat
nefolositor al clasei driver. În exemplul următor vom încărca driver-ul punte JDBC-
ODBC din pachetul sun.jdbc.odbc și,mai apoi, driverul MySQL Connector/J, care
permite conectarea la serverul de baze date MySQL. Trebuie remarcat faptul că pentru
Connector/J este necesar sa includem CLASSPATH calea spre pachetul care conţine
driver-ul:
Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);
Class.forName(”com.mysql.jdbc.Driver”);
Class.forName() este o metoda statică ce permite maşinii virtuale Java să aloce
dinamic, să încarce şi să facă o legătură la clasa specificată ca argument printr-un şir de
caractere. În cazul în care clasa nu este găsită, se aruncă o excepţie
ClassNotFoundException. A se vedea Java Reflection API.
Driverele pot fi înregistrate şi folosind metoda DriverManager.registerDriver().

2. Stabilirea unei conexiuni către baza de date

O dată ce s-a încărcat un driver, putem să-l folosim pentru stabilirea unei
conexiuni către baza de date. O conexiune JDBC este identificată printr-un URL JDBC
specific. Sintaxa standard pentru URL-ul unei baze de date este:
jdbc:<subprotocol>:<nume>
Prima parte precizează că pentru stabilirea conexiunii se foloseşte JDBC. Partea
de mijloc <subprotocol > este un nume de driver valid sau al altei soluţii de conexiune a
bazelor de date. Ultima parte, <nume>, este un nume logic sau alias care corespunde
bazei de date fizice. Dacă baza de date va fi accesată prin Internet, secţiunea , <nume>,
va respecta următoarea convenţie de nume:
//numegazda : port/subsubnume
În acest sens, o adresă corectă este următoarea:
jdbc:mysql://localhost:386/arhiva
Prezentăm în continuare sintaxa particulară pentru câteva drivere JDBC:
ODBC-jdbc:odbc:<sursa_date>[;<nume_atribut>=<valoare_atribut>]
MySQL- jdbc:mysql://server[:port]/numeBazaDate
Oracle- jdbc:oracle:thin:@server:port:numeInstanta

Pentru stabilirea unei conexiuni la o bază de date, se foloseşte metoda statică


getConnection() din clasa DriverManager.

Connection con = DriverManager.getConnection(


”jdbc:mysql://localhost/arhiva”,nume_utilizator,parola);

Pentru a afla informaţii despre DBMS, va trebui să apelăm


Connection.getMetadata() pentru a obţine o instanșă DatabaseDetaData, căreia îi putem
aplica diverse metode a afla informaţii despre tabelele bazei de date, gramatica SQL
suportată, dacă suportă sau nu proceduri stocate etc.
Folosind JNDI(Java name and Directory Interface) putem sa ne conectăm la o
bază de date considerând-o ca o sursă de date având un nume logic, în loc să codăm
direct în aplicaţie numele ei şi driver-ul pe care îl vom folosi.

3 Execuţia unei instrucţiuni SQL


După ce s-a stabilit conexiunea, se pot trimite instucţiuni SQL către baza de date.
API-ul JDBC nu verifică corectitudinea instrucţiunii şi nici apartenenţa ei la un anumit
standard SQL, permiţându-se astfel trimiterea chiar de instrucţiuni non-SQL.
Programatorul este cel care ştie dacă DBMS-ul interogat suportă interogările pe care le
trimite şi, dacă nu, el va trata excepţiile primite drept răspuns. Dacă instrucţiunea este SQL,
atunci aceasta poate face anumite operaţii asupra bazei de date, cum ar fi cautare, inserare,
actualizare sau ştergere.
API-ul JDBC specifică trei interfeţe (cărora cel ce creează driverul trebuie sa le
dea implementare) pentru trimiterea de interogări către baze de date, fiecăreia
corespunzându-i o metodă specială în clasa Connection, de creeare a instanţelor
corespunzătoare. Acestea sunt prezentate în tabelul ce urmează:

Clasa Metoda de creare Explicaţii


Statement Connection.create Este folosită pentru trimiterea de
Statement() instrucţiuni SQL simple fară parametrii
Prepared Connection.prepare Permite folosirea instrucţiunilor SQL
Statement Statement() precompilate şi a parametrilor de intrare în
interogări. Acestă metodă de a face
interogări care diferă doar printr-un număr
de parametrii.
Callable Connection.prepare Permite folosirea procedurilor stocate pe
Statement Call() server-ul DBMS

Pentru execuţia unei instrucţiuni SQL neparametrizate, se foloseşte metoda


createStatement(), aplicată unui obiect Connection. Acestă metodă întoarce un obiect din
clasa Statement.
Statement instrucţiune = con.createStatement();

Putem aplica apoi una din metodele executeQuery(), executeUpdate() sau


execute() unui obiect de tip Statement pentru a trimite DBMS-ului instrucţiunile SQL.
Metoda executeQuery() este folosită în cazul interogărilor care returnează mulţimi
rezultat (instanţe ale clasei ResultSet), aşa cum este cazul instrucţiunilor SELECT. Pentru
operaţiile de actualizare sau ştergere, cum ar fi INSERT, UPDATE sau DELETE, se
foloseşte metoda executeUpdate() aplicată unui obiect de tip Statement, rezultând un
întreg care reprezintă numărul de înregistrări afectate. Aceeaşi metodă este folosită pentru
interogările SQL DDL, cum ar fi CREATE TABLE, DROP TABLE şi ALTER TABLE,
în acest caz returnând întotdeauna zero. Metoda execute() este folosită în cazul în care se
obţine mai mult de o mulţime rezultat sau un număr de linie.

ResultSet rs = instructiune.executeQuery(”select * from arhive ”);


String sql = ”insert into arhive values(‘Popescu’,’Ion’,’Iasi’)”;
int raspuns = instrucţiune.executeUpdate(sql);

JDBC 2.0 permite trimiterea spre execuţie a mai multor instrucţiuni SQL grupate
(batch) împreună, această facilitate mărind uneori performanţele. Prezentăm în
continuare un astfel de caz:
Statement inter = con.createStatement();
con.setAutoCommit(false);
inter.addBatch(”INSERT INTO archive VALUES’(1,’Popescu’,’Ioan’)”);
inter.addBatch(”INSERT INTO cantitati VALUES’(260,’file’)”);
inter.addBatch(”INSERT INTO localitati VALUES’(’iasi’,’oras’)”);
int [] actualizari = inter.executeBatch();

Se observă dezactivarea modului autocommit. Acest lucru determină ca


modificările rezultate în tabele după execuţia metodei executeBatch() să nu fie automat
salvate permanent (eng. commit ) sau anulate (eng. Rollback), aceste operaţiuni
rămânând la alegerea clientului. Se poate astfel ca în cazul în care una din interogari
eşuează, clientul sa anuleze efectele tuturor. Metoda executeBatch() returnează un tablou
în care elementele corespund interogărilor SQL efectuate şi reprezintă numărul de linii
afectate de instrucţiunile SQL corespunzătoare.
Pentru a executa independent instrucţiunile SQL, se poate păstra modul
autocommit folosind captarea excepţiilor BatchUpdateException aruncate in caz de
eroare, aşa cum se poate vedea în exemplul următor:
try{
inter.addBatch(”INSERT INTO archive VALUES (1,’Popescu’,’Ioan’)”);
inter.addBatch(”INSERT INTO cantitati VALUES (260,’file’)”);
inter.addBatch(”INSERT INTO localitati VALUES (’iasi’,’oras’)”);
int [] actualizări = stmt.executeBatch();
} catch(BatchUpdateException b){
System.err.println(”Actualizări realizate: ”);
int [] eroriActualizari = b.getUpdateCounts();
for (int i = 0; i < eroriActualizari.length; i ++){
System.err.print(eroriActualizari[i]+ ” ”);
}
System.err.println(””);
}

Ştergerea comenzilor dintr-un grup se realizează cu metoda clearBatch();


Un driver JDBC poate să nu ofere suport pentru execuţia grupată de instrucţiuni
SQL. Pentru a afla dacă se întâmplă sau nu acest lucru, se va folosi metoda
supportsBatchUpdates() din clasa DatabaseMetaData.

Dacă se doreşte realizarea de apeluri SQL având date variabile drept intrare, se va
folosi clasa PreparedStatement care moşteneşte clasa Statement. Prezentăm în
continuare modul de construcţie a unei astfel de instrucţiuni, unde con reprezintă o
instanţă Connection:

PreparedStatement instrucţiune = con.prepareStatement(”update archive set


nume = ? where prenume like ?”);

După cum se observă, prin construcţie se primeşte drept intrare o instrucţiune


SQL (spre deosebire de cazul Statement). În momentul construcţiei, instrucţiunea SQL
primită ca parametru este trimisă direct spre DBMS unde este precompilată. Mai rămâne
să dăm valori variabilelor folosind metode setXX() corespunzătoare tipurilor de date şi
executăm efectiv interogarea după cum se poate vedea în continuare:

instrucţiune.setString(1,”Popescu”);
instrucţiune.setString(2,”Ion”);
instrucţiune.executeUpdate();

Această metodă de a face interogări este mai rapidă decât folosirea clasei
Statement în cazul în care dorim execuţia repetată a unei instrucţiuni SQL, deoarece
aceasta este compilată o singură dată în momentul creării instanţei clasei
PreparedStatement şi folosită apoi repetat, eventual cu valori diferite pentru parametrii.
Se poate folosi şi în cazul în care nu avem parametri, aşa cum se poate observa în
continuare:

PreparedStatement instrucţiune = con.prepareStatement(”select * from


archive”);
ResultSet rs = instrucţiune.executeQuery();

Metodele fară parametri de intrare puse la dispoziţie de clasa Connection pentru


cele tri interfeţe prezentate în tabelul precedent produc mulţimi rezultat fără cursor
deplasabil şi nesenzitive la modificări. O mulţime rezultat are cursor pentru poziţionare
deplasabil, proprietate care poartă numele de scrolling, dacă deţine un pointer interior
care indică înregistrarea accesată la momentul current, pointer care se poate deplasa
programatic. De asemenea, mulţimea rezultat nu este senzitivă la modificările care pot
surveni între timp în tabela integrată. Prin senzitiv se întelege actualizarea automată a
mulţimii rezultat pentru a ilustra dinamic modificările survenite între timp în tabelă.
Driverele care sunt conforme cu vresiunea 2.0 a specificaţiei JDBC permit
obţinerea de mulţimi rezultat sensitive şi având cursor deplasabil. Acest lucru este posibil
prin specificarea în constructorul instanţei Statement a uneia dintre constantele
prezentate în tabelul care urmează:

Constantă Explicaţii
TYPE_FORWARD_ONLY Acest tip de ResultSet este cel din JDBC 1.0.
Mulţimea rezultat nu are cursor deplasabil decât
dinspre început spre sfârşit. Modificările făcute în
tabelă nu se reflectă in mulţimea rezultat .
TYPE_SCROLL_INSENSITIVE Mulţimea rezultat scrollable, deci cursorul poate fi
mutat înainte şi înapoi sau pe orice poziţie diferită de
poziţia curentă. De asemenea eventualele modificări
făcute între timp în tabelă nu sunt reflectate în
ResultSet.
TYPE_SCROLL_SENSITIVE Mulţimea rezultat scrollable. Senzitive la modificari.

De asemenea, driverele conforme cu specificaţia 2.0 a JDBC oferă şi posibilitatea


actualizării programatice a tabelelor prin intermediul obiectelor ResultSet. Tipul de
actualizare se specifică prin intermediul uneia dintre constantele prezentate în continuare:

Constantă Explicaţii
CONCUR_READ_ONLY ResultSet-ul nu poate fi modificat
programatic. Oferă posibilitatea unui
număr nelimitat de conectări concurente la
baza de date,deoarece nu se fac modificări
asupra tabelei care ar putea genera
conflicte. Acest tip de ResultSet este
specific driverelor conforme specificaţiei
JDBC 1.0.
CONCUR_UPDATE ResultSet-ul şi baza de date pot fi
modificate programatic. Sunt posibile un
număr limitat de conexiuni concurente dat
de tipul de concurenţă ales. Acest tip de
ResultSet este specific driverelor conforme
specificaţiei JDBC 2.0.

Prezentăm un exemplu de cod care va determina crearea unei mulţimi rezultat


având cursor deplasabil şi senzitivă la modificări (ordinea parametrilor metodei
createStatement() este importantă).

Statement declare =
con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rez = declare.executeQuerry(”select nume,prenume from archive”);

4 Procesarea rezultatelor
Pentru parcurgerea simplă a înregistrărilor unui obiect din clasa ResultSet
folosind JDBC 1.0, putem folosi metoda next(), aşa cum se poate observa în următoarea
secvenţă de cod.

while(rs.next())// implicit cursor pozișionat înainte de prima linie


{
System.out.println(rs.getString(”nume”)+”,”+rs.getString(”prenume”)+”,”+
rs.getString(”oras”));
}

În cazul JDBC 2.0, o dată obţinută mulţimea rezultat rez având cursor deplasabil,
poate fi folosită pentru a poziţiona cursorul în interiorul ResultSet-ului. Iniţial, cursorul
este poziţionat înaintea primei linii din mulţimea rezultat. În JDBC 1.0, după cum
aminteam, cursorul putea fi mutat doar înainte folosind metoda next(). Driverele care sunt
conforme cu JDBC 2.0 mai permit poziţionarea absolută a cursorului pe orice linie,
folosind metoda absolute (int nr_linie), poziţionari relative ale cursorului relativ la linia
curentă folosind relative(int nr_salt), respective deplasări înapoi folosind metoda
previos(). Dacă argumentul metodei absolute() este pozitiv, se va realize poziţionarea pe
linia corespunzătoare. Dacă argumentul este negativ, poziţionarea se va face prin
deplasare de la sfârşit la început cu un număr egal cu valoarea absolută din argument.
Astfel, absolute(1) va determina poziţionarea pe prima linie, iar absolute(-1) va determina
întotdeauna poziţionarea pe ultima linie. Pentru determinarea liniei curente, se poate
folosi metoda getRow(). De asemenea există metodele first() beforeFirst() pentru
poziţionarea pe prima, respective înainte de prima linie, last şi afterLast pentru
poziţionarea pe ultima, respective după ultima linie. Pentru testarea poziţiei, avem
metodele isFirst, is Last, isBeforeFirst(), isBeforeLast(). De remarcat că toate
poziţionările se referă la ResultSet, şi nu la baza de date interogată.
Prezentăm aceste metode folosite pentru a citi în ordinea inversă liniile din
ResultSet-ul rezultat în urma interogării precedente:

if(rez.isAfterLast()==false){
rez.afterlast();
}
while(rez.previous()){
String nume = rez.getString(2);
//nume va deșine conșinutul celulei aflată la intersecșia liniei curente din
//ResultSet cu prima coloană tot din ResultSet
String prenume = rez.getString(”prenume”);
//prenume va deșine conșinutul celulei aflată la intersecţia
//liniei curente din ResultSet cu, coloana prenume din ResultSet
System.out.println(rez.getRow()+”nume: ” + nume + ”prenume: ” +
prenume );
}
absolute(-1)//poziíonarea cursorului pe ultima linie

Dacă nu cunoaştem tipul coloanelor ResultSet-ului, putem folosi un apel


ResultSet.getMetaData() pentru a obţine o instanţă ResultSetMetaData, pentru care mai
apoi se aplică metoda getColumType().
În cazul în care driverul folosit este conform JDBC 2.0, clasa ResultSet oferă
suport pentru actualizări programatice. Prin actualizări programatice, vom înţelege
actualizări aplicate ResultSet-ului, care sunt automat efectuate şi asupra bazei de date. În
acest fel, baza de date va putea fi modificată fără a efectua instrucţiuni SQL. Metodele pe
care clasa ResultSet le pune la dispoziţie pentru astfel de actualizări sunt: updateXXX(),
unde XXX reprezintă un tip de date Java.
Pe lângă actualizări, API-ul JDBC 2.0 oferă şi posibilitatea ştergerii de linii
folosind metoda deleteRow().

rez.last();
rez.deleteRow();

Ştergerea se aplică atât ResultSet-ului,cât şi tabelei înfăşurate. De observat că în


exemplul precedent se va şterge ultima linie din ResultSet, şi nu din tabela înfăşurată.
Poziţia pe care se află în tabelă linia ResultSet ce se vrea ştearsă rămâne întotdeauna
nedeterminată.
Ultima operaţie pe care o vom prezenta este inserarea unei linii într-o tablă.
Această operaţie se realizează prin intermediul unei linii speciale care poartă numele
insert row. Pentru a obţine accesul spre insert row, clasa ResultSet pune la dispoziţie
metoda moveToInsertRow(). Urmează apelul metodelor de tipul updateXXX pentru a
completa linia insert row cu informaţii. După ce linia a fost completată cu date, se va
insera efectiv în tabela curentă printr-un apel insertRow(). Pentru a ne pozţiona pe
înregistrarea pe care eram înainte de a insera o linie, se va apela metoda
ResultSet.moveToCurrentRow().
Apelurile de forma updateXXX effectuate când suntem poziţionaţi pe insert row
nu au nici un efect asupra mulţimii reyultat şi nici asupra bayei de date din care a fost
generat ResultSet-ul. Apelul insertRow() va returna o excepţie SQLException dacă
numărul de coloane din ResultSet este mai mic decât numărul de coloane din tabela
înfăşurată. De asemenea, putem obţine informaşii despre datele din insertRow folosind
metode getXXX, ceea ce obţinem drept rezultat este nedefinit.
Atunci când inserăm o înregistrare in tabelă, JDBC nu oferă nicio modalitate de a
afla pe ce poziţie a fost introdusă acea înregistrare. Acest lucru este gestionat intern de
DBMS.
Pe lângă tipurile de date corespunzătoare specificaţiei SQL-92, prin intermediul
unei instanţe ResultSet se poate lucra şi cu date având tipuri SQL3. Prezentăm în
continuare un exemplu de citire a unui tablou dintr-o tabelă:

ResultSet rs = stmt.executeQuerry(”select rezultate from tabela where nr_curent=


3”);
rs.next();
Array tablou = rs.getArray(”rezultate”);

Un exemplu de stocare în mod pragmatic a unui obiect Clob:

Clob observaţii = rs.getClob(”observaţii”);


PreparedStatement prep = con.prepareStatement(
”update alta_tabela set comentarii = ? where nr_curent < 1000”,
ResultSetTYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
prep.setClob(1,observaţii);

Dintr-un obiect Clob putem obșine un flux de caractere:


java.io.Reader citire =observaţii.getCharacterStrem();
Trebuie remarcat că în cazul tipurilor CLOB, BLOB şi ARRAY, prin interogări se
obţin referinţe spre obiecte de acest tip stocate în bazele de date, în loc ca acestea să fie
aduse din DBMS în aplicaţie, soluţia fiind adoptată pentru optimizare.Cu toate acestea,
folosirea acestor tipuri, datorită cantităţii mari de date conţinute, determină scăderi în
ceea ce priveşte performanţele. Accesul spre datele de tip BLOB şi CLOB stocate în baya
de date se realizează prin fluxuri de citire scriere. Prezentăm în continuare operaţia de
citire folosind un flux:

String sir = ” ”;
java.sql.Blob blob = rezultat.getBlob();
InputStream inn = blob.getBinaryStream();
InputStream in = new InputStreamReader(inn);
int b;
while (in.read()>-1){
b=in.read();
sir+=b;
}
in.close();
inn.close();

În cazul tipurilor definite de utilizator (tipuri distincte şi structurate), prezentăm


modalitatea de interogare şi obţinere a rezultatului:
ResultSet rs = stmt.executeQuery(
”SELECT PUNCT FROM FIGURI WHERE DIM > 3”);
While (rs.next()){
Struct punct = (Struct)rs.getObject(”PUNCT”);
//operaţii cu obiectul punct }

În ambele cazuri, şi pentru tipuri distincte, şi pentru cele structurate, returnarea se face
prin valoare, şi nu prin referinţă.

5. Închiderea unei conexiuni la o bază de date


Atunci când este vorba despre resursele exterioare, aşa cum este cazul accesului
spre DBMS-uri via JDBC, ”colectorul de gunoaie” nu ştie nimic despre starea acestor
resurse, dacă este sau nu cazul să le elibereze. De aceea, este recomandat ca, după ce
procesarea datelor s-a încheiat, programatorii să închidă explicit conexiunile către baza
de date. Pentru aceasta se foloseşte metoda close() aplicată obiectului Connection. În
plus, trebuie închise (înaintea conexiunii) şi obiectele Statement şi ResultSet folosind
metodele lor close(), aşa cum se poate vedea în exemplul care urmează:

try {
//…
rs.close();
instrucţiune.close();
}
catch (SQLException e){
System.out.println(”Eroare la închidere interogare: ” + e.toString());}
finally{
try {
if ( conexiuneBazaDate != null){
conexiuneBazaDate.close();
}
}
catch (SQLException ex){
System.out.println(”Eroare la închidere conexiune: ” + ex.toString());
}
}

Aplicaţii
Se creează o bază de date denumită Studenţi în Microsoft Access. Ea conţine
tabelul Detalii ce are coloanele Nume, Prenume şi Nota. Se înregistrează aceasta ca sursă
de date (ODBC Data Source) în Administrative Tools. Numele sursei de date, care va fi
folosit şi în programul Java va fi DB.

Exemplul 1:

import java.sql.*; // imports the JDBC core package

public class JDBCDemo{

public static void main(String args[]){

int nota;

String nume, prenume;

// SQL Query string

String query = "SELECT * FROM Detalii";

try {

// load the JDBC driver

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

catch(java.lang.ClassNotFoundException e) {

System.err.print("ClassNotFoundException: ");

System.err.println(e.getMessage());

try{

// get a connection

Connection con =

DriverManager.getConnection ("jdbc:odbc:DB");

Statement stmt = con.createStatement();

ResultSet rs = stmt.executeQuery(query); // execute


query

while (rs.next()) { // parse the results


nume = rs.getString("Nume");

prenume = rs.getString("Prenume");

nota = rs.getInt("Nota");

System.out.println(nume+ " "+ prenume + " " +


nota);

stmt.close();

con.close();

catch(SQLException e){

e.printStackTrace();

Exemplul 2: PreparedStatement

import java.sql.*;

public class PreparedStmt{

public static void main(String args[]){

String nume;

Integer[] note={5,10};

String query = ("SELECT * FROM Detalii WHERE nota = ?");

try {

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

catch(java.lang.ClassNotFoundException e) {

System.err.print("ClassNotFoundException: ");

System.err.println(e.getMessage());

}
try{

Connection con =

DriverManager.getConnection ("jdbc:odbc:DB");

PreparedStatement pstmt = con.prepareStatement(query);

for (int i=0; i<2;i++){

pstmt.setInt(1, note[i]);

ResultSet rs = pstmt.executeQuery();

System.out.println(note[i]);

while (rs.next()) {

nume = rs.getString("Nume")+"
"+rs.getString("Prenume");

System.out.println(nume);

pstmt.close();

con.close();

catch(SQLException e){

e.printStackTrace();

Exemplul 3: ResultSet care are cursor ce se poate deplasa (eng. Scrollable)

import java.sql.*;

public class ScrollableResultSet{

public static void main(String args[]){

Integer nota;

String nume, prenume;

String query = "SELECT * FROM Detalii";


try {

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

catch(java.lang.ClassNotFoundException e) {

System.err.print("ClassNotFoundException: ");

System.err.println(e.getMessage());

try{

Connection con = DriverManager.getConnection


("jdbc:odbc:DB");

// get a connection

Statement stmt = con.createStatement(

ResultSet.TYPE_SCROLL_INSENSITIVE,

ResultSet.CONCUR_READ_ONLY);

ResultSet rs = stmt.executeQuery(query);

rs.afterLast(); //cursor after last line of the table

// parse the results in reverse order

while (rs.previous()) {

nume = rs.getString("Nume");

prenume = rs.getString("Prenume");

nota = rs.getInt("Nota");

System.out.println(nume+ " "+ prenume+" "+nota);

stmt.close();

con.close();

catch(SQLException e){

e.printStackTrace();

}
}

Exemplul 4: ResultSet care poate fi modificat

import java.sql.*;

public class UpdatableResultSet{

public static void main(String args[]){

int nota;

String nume, prenume;

String query = "SELECT * FROM Detalii";

try {

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

catch(java.lang.ClassNotFoundException e) {

System.err.print("ClassNotFoundException: ");

System.err.println(e.getMessage());

try{

Connection con = DriverManager.getConnection


("jdbc:odbc:DB");

// get a connection

Statement stmt = con.createStatement(

ResultSet.TYPE_SCROLL_INSENSITIVE,

ResultSet.CONCUR_UPDATABLE);

ResultSet rs = stmt.executeQuery(query);

ResultSetMetaData md = rs.getMetaData();

if(rs.getConcurrency()==ResultSet.CONCUR_UPDATABLE)

System.out.println("UPDATABLE");

else

System.out.println("READ_ONLY");

int nColumns = md.getColumnCount();


System.out.println("Tabelul Detalii are " + nColumns + "
coloane");

System.out.println("Acestea sunt:");

for(int i=1;i<=nColumns;i++){

System.out.print(md.getColumnLabel(i)+" ");

System.out.println();

while (rs.next()) {

rs.updateInt("Nota", 7);

rs.updateRow();

for(int i=1;i<=nColumns;i++){

System.out.print(rs.getString(i)+" ");

System.out.println();

catch(SQLException e){

e.printStackTrace();

}
Observaţii:

Pentru mai multe informaţii despre JDBC şi MySQL, a se vedea:


http://www.developer.com/java/data/article.php/3417381/Using-JDBC-with-MySQL-
Getting-Started.htm

Pentru mai multe informaţii despre JDBC şi Oracle a se vedea:

O'Reilly - Java Programming with Oracle JDBC.pdf

Mai multe despre JDBC puteţi găsi aici:

1. http://java.sun.com/docs/books/tutorial/jdbc/basics/index.html - Exemplele de
aici se găsesc în codeExamples_3.0.zip
2. Cartea Java de la 0 la Expert
3. Cartea Java Database Programming Bible (2002)

De studiat:
Tranzacţii şi instrucţiuni grupate (eng. batch) – din documentaţia de mai sus.

De făcut:
Încercaţi realizarea unei conexiuni:

 la o bază de date MySQL


 la o bază de date Oracle

S-ar putea să vă placă și