curs JDBC

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 caracter Blob (eng. Binary Large Objects) Clob (eng. Character Large Objects) Drivere Driver

Transcript of curs JDBC

Page 1: curs JDBC

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 caracter

Blob (eng. Binary Large Objects) Clob (eng. Character Large Objects)

Drivere Driver

Page 2: curs JDBC

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 "));

Page 3: curs JDBC

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);

Page 4: curs JDBC

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.

Page 5: curs JDBC

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:

Page 6: curs JDBC

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:

Page 7: curs JDBC

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

Page 8: curs JDBC

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:

Page 9: curs JDBC

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

Page 10: curs JDBC

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

Statement() Este folosită pentru trimiterea de instrucţiuni SQL simple fară parametrii

Prepared Statement

Connection.prepare Statement()

Permite folosirea instrucţiunilor SQL 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 Statement

Connection.prepare Call()

Permite folosirea procedurilor stocate pe 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’)”);

Page 11: curs JDBC

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”);

Page 12: curs JDBC

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

Page 13: curs JDBC

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

Page 14: curs JDBC

î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

Page 15: curs JDBC

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){

Page 16: curs JDBC

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

Page 17: curs JDBC

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

Page 18: curs JDBC

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());

}

Page 19: curs JDBC

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";

Page 20: curs JDBC

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();

}

}

Page 21: curs JDBC

}

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();

Page 22: curs JDBC

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();

}

}

}

Page 23: curs JDBC

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