Aplicaţiile care folosesc baze de date sunt, in general, aplicaţii complexe folosite pentru gestionarea unor informaţii de dimensiuni mari intr-o manieră sigură şi eficientă.
Ce este o bază de date ?
La nivelul cel mai general, o bază de date reprezintă o modalitate de stocare a unor informaţii (date) pe un suport extern, cu posibilitatea regăsirii acestora.
Uzual, o bază de date este memorată intr-unul sau mai multe fişiere. Modelul clasic de baze de date este cel relaţional, in care datele sunt memorate in tabele. Un tabel reprezintă o structură de date formată dintr-o mulţime de articole, fiecare articol având definite o serie de atribute – aceste atribute corespund coloanelor tabelului, in timp ce o linie va reprezenta un articol. Pe langa tabele, o bază de date mai poate conţine: proceduri şi funcţii, utilizatori şi grupuri de utilizatori, tipuri de date, obiecte, etc.
Dintre producătorii cei mai importanţi de baze de date amintim companiile Oracle, Sybase, IBM, Informix, Microsoft, etc. fiecare furnizând o serie intreagă de produse şi utilitare pentru lucrul cu baze de date. Aceste produse sunt in general referite prin termenii DBMS (Database Management System) sau, in traducere, SGBD (Sistem de Gestiune a Bazelor de Date). In acest articol vom analiza lucrul cu baze de date din perspectiva programării in limbajul Java, fără a descrie particularităţi ale unei soluţii de stocare a datelor anume. Vom vedea că, folosind Java, putem crea aplicaţii care să ruleze fără nici o modificare folosind diverse tipuri de baze care au aceeaşi structură, ducând ı̂n felul acesta noţiunea de portabilitate şi mai departe.
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, dar există şi posibilitatea de a crea o baza folosind un script SQL. Acest aspect ne va preocupa ı̂nsă mai puţin, exemplele prezentate presupunând că baza a fost creată deja şi are o anumită structură specificată.
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 limaj 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
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 in Java, furnizând mecanisme standard pentru proiectanţii aplicaţiilor ce folosesc de baze de date.
Folosind JDBC este usor sa transmitem secvenţe SQL catre 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 Sybase şi asa mai departe. Este de ajuns să scriem un singur program folosind API-ul 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ă in Java, ne este asigurată portabilitatea programului. Deci, iată două motive puternice care fac combinaţia Java – JDBC demnă de luat in seamă.
Pachetele care oferă suport pentru lucrul cu baze de date sunt java.sql ce reprezintă nucleul tehnologiei JDBC şi, preluat de pe platforma J2EE, javax.sql.
API-ul JDBC oferă următoarele facilităţi:
- Stabilirea unei conexiuni cu o bază de date.
- Efectuarea de secvenţe SQL.
- Prelucrarea rezultatelor obţinute.
Conectarea la o bază de date
Procesul de conectare la o bază de date implică efectuarea a două operaţii:
- Inregistrarea unui driver corespunzător.
- Realizarea unei conexiuni propriu-zise.
O conexiune (sesiune) la o bază de date reprezintă un context prin care sunt trimise secvenţe SQL şi primite rezultate. Intr-o aplicaţie pot exista simultan mai multe conexiuni la baze de date diferite sau la aceeaşi bază.
Clasele şi interfeţele responsabile cu realizarea unei conexiuni sunt:
- DriverManager – este clasa ce se ocupă cu ı̂nregistrarea driverelor ce vor fi folosite in aplicaţie;
- Driver – interfaţa pe care trebuie să o implementeze orice clasă ce descrie un driver;
- DriverPropertyInfo – prin intermediul acestei clase pot fi specificate diverse proprietăţi ce vor fi folosite la realizarea conexiunilor;
- Connection – descrie obiectele ce modelează o conexiune propriu-zisă cu baza de date.
Inregistrarea unui driver
Primul lucru pe care trebuie să-l facă o aplicaţie ı̂n procesul de conectare la o bază de date este să inregistreze la maşina virtuală ce rulează aplicaţia driverul JDBC responsabil cu comunicarea cu respectiva bază de date. Acest lucru presupune ı̂ncărcarea ı̂n memorie a clasei ce implementează driver-ul şi poate fi realizată ı̂n mai multe modalităţi.
a. Folosirea clasei DriverManager:
DriverManager.registerDriver(new TipDriver());
b. Folosirea metodei Class.forName ce apelează ClassLoader-ul maşinii virtuale:
Class.forName(“TipDriver”);
Class.forName(“TipDriver”).newInstance();
c. Setarea proprietăţii sistem jdbc.drivers, care poate fi realizată in două feluri:
– De la linia de comandă:
java -Djdbc.drivers=TipDriver Aplicatie
– Din program:
System.setProperty(“jdbc.drivers”, “TipDriver”);
Folosind această metodă, specificarea mai multor drivere se face separând numele claselor cu punct şi virgulă.
Dacă sunt inregistrate mai multe drivere, ordinea de precedenţă in alegerea driverului folosit la crearea unei noi conexiuni este:
- Driverele inregistrate folosind proprietatea jdbc.drivers la iniţializarea maşinii virtuale ce va rula procesul.
- Driverele inregistrate dinamic din aplicaţie.
Specificarea unei baze de date
O dată ce un driver JDBC a fost inregistrat, acesta poate fi folosit la stabilirea unei conexiuni cu o bază de date. Având in vedere faptul ca pot exista mai multe drivere incărcate ı̂n memorie, trebuie să avem posibilitea de a specifica pe lângă un identificator al bazei de date şi driverul ce trebuie folosit. Aceasta se realizează prin intermediul unei adrese specifice, numită JDBC URL, ce are următorul format:
jdbc:sub-protocol:identificator
Câmpul sub-protocol denumeşte tipul de driver ce trebuie folosit pentru realizarea conexiunii şi poate fi odbc, oracle, sybase, db2 şi aşa mai departe.
Identificatorul bazei de date este un indicator specific fiecărui driver corespunzător bazei de date cu care aplicaţia doreşte să interacţioneze. In funcţie de tipul driver-ului acest identificator poate include numele unei maşini gazdă, un număr de port, numele unui fişier sau al unui director, etc., ca in exemplele de mai jos:
jdbc:odbc:test
jdbc:oracle:thin@persistentjava.com:1521:test
jdbc:sybase:test
jdbc:db2:test
Subprotocolul odbc este un caz specical, in sensul că permite specificarea in cadrul URL-ului a unor atribute ce vor fi realizate la crearea unei conexiuni. Sintaxa completa subprotocolului odbc este:
jdbc:odbc:identificator[;atribut=valoare]*
jdbc:odbc:test
jdbc:odbc:test;CacheSize=20;ExtensionCase=LOWER
jdbc:odbc:test;UID=duke;PWD=java
La primirea unui JDBC URL, DriverManager-ul va parcurge lista driverelor inregistrate in memorie, pâna când unul dintre ele va recunoaşte URL-ul respectiv. Dacă nu exista nici unul potrivit, atunci va fi lansata o excepţie de tipul SQLException, cu mesajul “no suitable driver”.
Tipuri de drivere
Tipurile de drivere existente ce pot fi folosite pentru realizarea unei conexiuni prin intermediul JDBC se ı̂mpart ı̂n următoarele categorii:
Tip 1. JDBC-ODBC Bridge
Acest tip de driver permite conectarea la o bază de date care a fost inregistrată ı̂n prealabil ı̂n ODBC. ODBC (Open Database Conectivity) reprezintă o modalitate de a uniformiza accesul la baze de date, asociind acestora un identificator DSN (Data Source Name) şi diverşi parametri necesari conectării. Conectarea efectivă la baza de date se va face prin intermediul acestui identificator, driver-ul ODBC efectuând comunicarea cu driverul nativ al bazei de date.
Deşi simplu de utilizat, soluţia JDBC-ODBC nu este portabilă şi comunicarea cu baza de date suferă la nivelul vitezei de execuţie datorită multiplelor redirectări ı̂ntre drivere. De asemenea, atât ODBC-ul cât şi driver-ul nativ trebuie să existe pe maşina pe care rulează aplicaţia.
Clasa Java care descrie acest tip de driver JDBC este:
sun.jdbc.odbc.JdbcOdbcDriver
şi este inclusă in distribuţia standard J2SDK. Specificarea bazei de date se face printr-un URL de forma:
jdbc:odbc:identificator
unde identificator este profilul (DSN) creat bazei de date ı̂n ODBC.
Tip 2. Driver JDBC – Driver nativ
Acest tip de driver transformă cererile JDBC direct in apeluri către driverul nativ al bazei de date, care trebuie instalat ı̂n prealabil. 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.
Tip 3. Driver JDBC – Server
Acest tip de driver transformă cererile JDBC folosind un protocol de reţea independent, acestea fiind apoi transormate folosind o aplicaţie server intr-un protocol specfic bazei de date. Introducerea serverului ca nivel intermediar aduce flexibilitate maximă ı̂n sensul că vor putea fi realizate conexiuni cu diferite tipuri de baze, fără nici o modificare la nivelul clientului. Protocolul folosit este specific fiecărui producător.
Tip 4. Driver JDBC nativ
Acest tip de driver transformă cererile JDBC direct ı̂n cereri către baza de date folosind protocolul de reţea al acesteia. Această soluţie este cea mai rapidă, fiind preferată la dezvoltarea aplicaţiilor care manevrează volume mari de date şi viteza de execuţie este critică. Drivere de acest tip pot fi procurate de la diverşi producători de SGBD-uri.
Realizarea unei conexiuni
Metoda folosită pentru realizarea unei conexiuni este getConnection din clasa DriverManager şi poate avea mai multe forme:
Connection c = DriverManager.getConnection(url);
Connection c = DriverManager.getConnection(url, username, password);
Connection c = DriverManager.getConnection(url, dbproperties);
Stabilirea unei conexiuni folosind driverul JDBC-ODBC
String url = “jdbc:odbc:test” ;
// sau url = “jdbc:odbc:test;UID=duke;PWD=java” ;
try {
Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);
} catch(ClassNotFoundException e) {
System.err.print(“ClassNotFoundException: ” + e) ;
return ;
}
Connection con ;
try {
con = DriverManager.getConnection(url, “duke”, “java”);
} catch(SQLException e) {
System.err.println(“SQLException: ” + e);
} finally {
try{
con.close ;
} catch(SQLException e) {
System.err.println(SQLException: ” + e) ;
}
}
Stabilirea unei conexiuni folosind un driver MySql
Folosirea diferitelor tipuri de drivere implică doar schimbarea numelui clasei ce reprezintă driverul şi a modalităţii de specificare a bazei de date.
String url = “jdbc:mysql://localhost/test” ;
// sau url = “jdbc:mysql://localhost/test?user=duke&password=java”;
try {
Class.forName(“com.mysql.jdbc.Driver”) ;
} catch(ClassNotFoundException e) {
…
O conexiune va fi folosită pentru:
- Crearea de secvenţe SQL utilizate pentru interogarea sau actualizarea bazei.
- Aflarea unor informaţii legate de baza de date (meta-date).
De asemenea, clasa Connection asigură facilităţi pentru controlul tranzacţiilordin memorie către baza de date prin metodele commit, rollback, setAutoCommit.
Inchiderea unei conexiuni se realizează prin metoda close.
Efectuarea de secvenţe SQL
O dată facută conectarea cu metoda DriverManager.getConection, se poate folosi obiectul Connection rezultat pentru a se crea obiecte de tip Statement,PreparedStatement sau CallableStatement cu ajutorul cărora putem trimite secvenţe SQL către baza de date. Cele mai uzuale comenzi SQL sunt cele folosite pentru:
- Interogarea bazei de date: SELECT
- Actualizarea datelor: INSERT, UPDATE, DELETE
- Actualizarea structurii: CREATE, ALTER, DROP – acestea mai sunt numite instrucţiuni DDL (Data Definition Language)
- Apelarea unei proceduri stocate: CALL
După cum vom vedea, obţinerea şi prelucrarea rezultatelor unei interogări este realizată prin intermediul obiectelor de tip ResultSet.
Interfaţa Statement
Interfaţa Statement oferă metodele de bază pentru trimiterea de secvenţe SQL către baza de date şi obţinerea rezultatelor, celelalte două interfeţe:
PreparedStatement şi CallableStatement fiind derivate din aceasta.
Crearea unui obiect Statement se realizează prin intermediul metodei createStatement a clasei Connection, fără nici un argument:
Connection con = DriverManager.getConnection(url);
Statement stmt = con.createStatement();
Execuţia unei secvenţe SQL poate fi realizată prin intermediul a trei metode:
1. executeQuery
Este folosită pentru realizarea de interogări de tip SELECT. Metoda returneazăun obiect de tip ResultSet ce va conţine sub o formă tabelară rezultatul interogării.
String sql = “SELECT * FROM persoane”;
ResultSet rs = stmt.executeQuery(sql);
2. executeUpdate
Este folosită pentru actualizarea datelor (INSERT, UPDATE, DELETE) sau a structurii bazei de date (CREATE, ALTER, DROP). Metoda va returna un intreg ce semnifică numărul a structurii bazei de date (CREATE, ALTER, DROP). Metoda va returna unde linii afectate de operaţiunea de actualizare a datelor, sau 0 ı̂n cazul unei instrucţiuni DDL.
EFECTUAREA DE SECVENŢE SQL
String sql = “DELETE FROM persoane WHERE cod > 100”;
int linii = stmt.executeUpdate(sql);
// Nr de articole care au fost afectate (sterse)
sql = “DROP TABLE temp”;
stmt.executeUpdate(sql); // returneaza 0
3. execute
Această metodă va fi folosită doar dacâ este posibil ca rezultatul unei interogări să fie format din două sau mai multe obiecte de tip ResultSet sau rezultatul unei actualizări să fie format din mai mule valori, sau o combinaţie intre aceste cazuri. Această situaţie, deşi mai rară, este posibilă atunci când sunt executate proceduri stocate sau secvenţe SQL cunoscute abia la momentul execuţiei, programatorul neştiind deci dacă va fi vorba de o actualizare a datelor sau a structurii. Metoda intoarce true dacă rezultatul obţinut este format din obiecte de tip ResultSet şi false dacă e format din intregi.
In funcţie de aceasta, pot fi apelate metodele: getResultSet sau getUpdateCount pentru a afla efectiv rezultatul comenzii SQL. Pentru a prelua toate rezultatele va fi apelată metoda getMoreResults, după care vor fi apelate din nou metodele amintite, până la obţinerea valorii null, respectiv −1. Secvenţa completă de tratare a metodei execute este prezentată mai jos:
String sql = “comanda SQL necunoscuta”;
stmt.execute(sql);
while(true) {
int rowCount = stmt.getUpdateCount();
if(rowCount > 0) {
// Este o actualizare datelor
System.out.println(“Linii afectate = ” + rowCount);
stmt.getMoreResults();
continue;
}
if(rowCount = 0) {
// Comanda DDL sau nici o linie afectata
System.out.println(“Comanda DDL sau 0 actualizari”);
stmt.getMoreResults();
continue;
}
// rowCount este -1
// Avem unul sau mai multe ResultSet-uri
ResultSet rs = stmt.getResultSet();
if(rs != null) {
// Proceseaza rezultatul
…
stmt.getMoreResults();
continue;
}
// Nu mai avem nici un rezultat
break;
}
Folosind clasa Statement, ı̂n cazul ı̂n care dorim să introducem valorile unor variabile ı̂ntr-o secvenţă SQL, nu avem altă soluţie decât să creăm un şir de caractere compus din instrucţiuni SQL şi valorile variabilelor:
int cod = 100;
String nume = “Popescu”;
String sql = “SELECT * FROM persoane WHERE cod=” + cod +
” OR nume=’” + nume + “’”;
ResultSet rs = stmt.executeQuery(sql);
Interfaţa PreparedStatement
Interfaţa PreparedStatement este derivată din Statement, fiind diferită deaceasta in următoarele privinţe:
- Instanţele de tip PreparedStatement conţin secvenţe SQL care au fost deja compilate
- O secvenţă SQL specificată unui obiect PreparedStatement poate să aibă unul sau mai mulţi parametri de intrare, care vor fi specificaţi prin intermediul unui semn de intrebare (”?”) ı̂n locul fiecăruia dintre ei. Inainte ca secvenţa SQL să poată fi executată fiecărui parametru de intrare trebuie să i se atribuie o valoare, folosind metode specifice acestei clase.
Execuţia repetată a aceleiaşi secvenţe SQL, dar cu parametri diferiţi, va fi in general mai rapidă dacă folosim PreparedStatement, deoarece nu mai trebuie să creăm câte un obiect de tip Statement pentru fiecare apel SQL, ci refolosim o singură instanţă precompilată furnizându-i doar alte argumente.
Crearea unui obiect de tip PreparedStatement se realizează prin intermediul metodei prepareStatement a clasei Connection, specificând ca argument o secvenţă SQL ce conţine căte un semn de intrebare pentru fiecare parametru de intrare:
Connection con = DriverManager.getConnection(url);
String sql = “UPDATE persoane SET nume=? WHERE cod=?”;
Statement pstmt = con.prepareStatement(sql);
Obiectul va pstmt conţine o comandă SQL precompilată care este trimisă imediat către baza de date, unde va aştepta parametri de intrare pentru a putea fi executată.
Trimiterea parametrilor se realizează prin metode de tip setXXX, unde XXX este tipul corespunzător parametrului, iar argumentele metodei sunt numărul de ordine al parametrului de intrare (al semnului de intrebare) şi valoarea pe care dorim să o atribuim.
pstmt.setString(1, “Ionescu”);
pstmt.setInt(2, 100);
După stabilirea parametrilor de intrare secvenţa SQL poate fi executată. Putem apoi stabili alte valori de intrare şi refolosi obiectul PreparedStatement pentru execuţii repetate ale comenzii SQL. Este insă posibil ca SGBD-ul folosit să nu suporte acest tip de operaţiune şi să nu reţină obiectul precompilat pentru execuţii ulterioare. In această situaţie folosirea interfeţei PreparedStatement in loc de Statement nu va imbunătăţi in nici un fel performanţa codului, din punctul de vedere al vitezei de execuţie a acestuia.
Execuţia unei secvenţe SQL folosind un obiect PreparedStatement se realizează printr-una din metodele executeQuery, executeUpdate sau execute, semnificaţiile lor fiind aceleaşi ca şi ı̂n cazul obiectelor de tip
Statement, cu singura deosebire că in cazul de faţă ele nu au nici un argument.
String sql = “UPDATE persoane SET nume=? WHERE cod=?”;
Statement pstmt = con.prepareStatement(sql);
pstmt.setString(1, “Ionescu”);
pstmt.setInt(2, 100);
pstmt.executeUpdate();
pstmt.setString(1, “Popescu”);
pstmt.setInt(2, 200);
pstmt.executeUpdate();
sql = “SELECT * from persoane WHERE cod >= ?”;
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, 100);
ResultSet rs = pstmt.executeQuery();
Fiecărui tip Java ii corespunde un tip generic SQL. Este responsabilitatea programatorului să se asigure că foloseşte metoda adecvată de tip setXXX la stabilirea valorii unui parametru de intrare. Lista tuturor tipurilor generice disponibile, numite şi tipuri JDBC, este definită de clasa Types, prin constantelor declarate de aceasta. Metoda setObject permite specificarea unor valori pentru parametrii de intrare, atunci când dorim să folosim maparea implicită intre tipurile Java şi cele JDBC sau atunci când dorim să precizăm explicit un tip JDBC.
pstmt.setObject(1, “Ionescu”, Types.CHAR);
pstmt.setObject(2, 100, Types.INTEGER); // sau doar
pstmt.setObject(2, 100);
Folosind metoda setNull putem să atribuim unui parametru de intrare valoare SQL NULL, trebuind ı̂nsă să specificăm şi tipul de date al coloanei in care vom scrie această valoare. Acelaşi lucru poate fi realizat cu metode de tipul setXXX dacă argumentul folosit are valoarea null.
pstmt.setNull(1, Types.CHAR);
pstmt.setInt(2, null);
Cu ajutorul metodelor setBytes sau setString avem posibilitatea de a specifica date de orice dimensiuni ca valori pentru anumite articole din baza de date. Există insă situaţii când este de preferat ca datele de mari dimensi uni să fie transferate pe ”bucăţi” de o anumită dimensiune. Pentru a realiza acest lucru API-ul JDBC pune la dispoziţie metodele setBinaryStream, setAsciiStream şi setUnicodeStream care ataşează un flux de intrare pe octeţi, caractere ASCII, respectiv UNICODE, unui parametru de intrare. Pe măsură ce sunt citite date de pe flux, ele vor fi atribuite parametrului. Exemplul de mai jos ilustrează acest lucru, atribuind coloanei continut conţinutul unui anumit fişier:
File file = new File(“date.txt”);
int fileLength = file.length();
InputStream fin = new FileInputStream(file);
java.sql.PreparedStatement pstmt = con.prepareStatement(
“UPDATE fisiere SET continut = ? WHERE nume = ’date.txt’”);
pstmt.setUnicodeStream (1, fin, fileLength);
pstmt.executeUpdate();
La execuţia secvenţei, fluxul de intrare va fi apelat repetat pentru a furniza datele ce vor fi scrise in coloana continut a articolului specificat. Observaţi că este necesar ı̂nă să ştim dinainte dimensiunea datelor ce vor fi scrise, acest lucru fiind solicitat de unele tipuri de baze de date.
Interfaţa CallableStatement
Interfaţa CallableStatement este derivată din PreparedStatement, instanţele de acest tip oferind o modalitate de a apela o procedură stocată intr-o bază de date, intr-o manieră standar pentru toate SGBD-urile.
Crearea unui obiect CallableStatement se realizează prin metoda prepareCall a clasei Connection:
Connection con = DriverManager.getConnection(url);
CallableStatement cstmt = con.prepareCall(
“{call proceduraStocata(?, ?)}”);
Trimiterea parametrilor de intrare se realizează intocmai ca la PreparedStatement, cu metode de tip setXXX. Dacă procedura are şi parametri de ieşire (valori returnate), aceştia vor trebui inregistraţi cu metoda registerOutParameter inainte de execuţia procedurii. Obţinerea valorilor rezultate in parametrii de ieşie se va face cu metode de tip getXXX.
CallableStatement cstmt = con.prepareCall(
“{call calculMedie(?)}”);
cstmt.registerOutParameter(1, java.sql.Types.FLOAT);
cstmt.executeQuery();
float medie = cstmt.getDouble(1);
Este posibil ca un parametru de intrare să fie şi parametru de ieşire. In acest caz el trebuie să primească o valoare cu setXXX şi, de asemenea, va fi inregistrat cu registerOutParameter, tipurile de date specificate trebuind să coincidă.
Obţinerea şi prelucrarea rezultatelor
Interfaţa ResultSet
In urma execuţie unei interogări SQL rezultatul va fi reprezentat printr-un obiect de tip ResultSet, ce va conţine toate liniile ce satisfac condiţiile impuse de comanda SQL. Forma generală a unui ResultSet este tabelară, având un număr de coloane şi de linii, funcţie de secvenţa executată. De asemenea, obiectul va conţine şi meta-datele interogării cum ar fi denumirele coloanelor selectate si numărul lor.
Statement stmt = con.createStatement();
String sql = “SELECT cod, nume FROM persoane”;
ResultSet rs = stmt.executeQuery(sql);
Rezultatul interogării de mai sus va fi obiectul rs cu următoarea structură:
Pentru a extrage informaţiile din această structură va trebui să parcurgem tabelul linie cu linie şi din fiecare să extragem valorile de pe coloane. Pentru acest lucru vom folosi metode de tip getXXX, unde XXX este tipul de dată al unei coloane iar argumentul primit indică fie numărul de ordine din cadrul tabelului, fie numele acestuia. Coloanele sunt numerotate de la stânga la dreapta, ı̂ncepând cu 1. In general, folosirea indexului coloanei in loc de numele său va fi mai eficientă. De asemenea, pentru maximă portabilitate se recomandă citirea coloanelor in ordine de la stânga la dreapta şi fiecare citire să se facă o singură dată.
Un obiect ResultSet foloseşte un cursor pentru a parcurge articolele rezultate in urma unei interogări. Iniţial acest cursor este poziţionat inaintea primei linii, fiecare apel al metodei next determinând trecerea la următoarea linie. Deoarece next returnează false când nu mai sunt linii de adus, uzual va fi folosită o buclă while-loop petru a itera prin articolele tabelului:
String sql = “SELECT cod, nume FROM persoane”;
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
int cod = r.getInt(“cod”);
String nume = r.getString(“nume”);
/* echivalent:
int cod = r.getInt(1);
String nume = r.getString(2);
*/
System.out.println(cod + “, ” + nume);
}
Implicit, un tabel de tip ResultSet nu poate fi modificat iar cursorul asociat nu se deplasează decât inainte, linie cu linie. Aşadar, putem itera prin rezultatul unei interogări o singură dată şi numai de la prima la ultima linie. Este insă posibil să creăm ResultSet-uri care să permită modificarea sau deplasarea in ambele sensuri. Exemplul următor va folosi un cursor care este modificabil şi nu va reflecta schimbările produse de alţi utilizatori după crearea sa:
Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
String sql = “SELECT cod, nume FROM persoane”;
ResultSet rs = stmt.executeQuery(sql);
Dacă un ResultSet foloseşte un cursor modificabil şi care poate naviga in ambele sensuri, atunci are la dispoziţie o serie de metode ce se bazează pe acest suport:
- absolute – Deplasează cursorul la o anumită linie specificată absolut;
- updateXXX – Actualizează valoarea unei coloane din linia curentă, unde XXX este un tip de date.
- updateRow – Transferă actualizările făcute liniei ı̂n baza de date.
- moveToInsertRow – deplasează cursorul la o linie specială, numită linie nouă, utilizateă pentru a introduce noi articole in baza de date. Linia curentă anterioară a cursorului va fi memorată pentru a se putea reveni la ea.
- insertRow – inserează articolul din zona linie nouă in baza de date; cursorul trebuie să fie poziţionat le linia nouă la execuţia acestei operaţiuni.
- moveToCurrentRow – revine la linia curentă din tabel.
- deleteRow – şterge linia curentă din tabel şi din baza de date; nu poate fi apelată când cursorul este in modul linie nouă.
Nu toate sistemele de gestiune a bazelor de date oferă suport pentru folosirea cursoarelor care pot fi modificate. Pentru a determina dacă baza de date permite acest lucru pot fi utilizate metodele supportsPositionedUpdate şi supportsPositionedDelete ale clasei DatabaseMetaData. In cazul in care acest lucru este permis, este responsabilitatea driver-ului bazei de date să asigure rezolvarea problemelor legate de actualizarea concurentă a unui cursor, astfel incât să nu apară anomalii.
Un exemplu simplu
In continuare vom da un exemplul simplu de utilizare a claselor de bază menţionate anterior. Programul va folosi o bază de date MySql, ce conţine un tabel numit persoane, având coloanele: cod, nume şi salariu. Scriptul SQL de creare a bazei este:
create table persoane(cod integer, nume char(50), salariu double);
Aplicaţia va goli tabelul cu persoane, după care va adăuga aleator un număr de articole, va efectua afişarea lor şi calculul mediei salariilor.
Exemplu simplu de utilzare JDBC
import java . sql .*;
public class TestJdbc {
public static void main ( String [] args ) {
String url = ” jdbc : mysql :// localhost / test ” ;
try {
Class . forName ( ” com . mysql . jdbc . Driver ” ) ;
} catch ( ClassNotFoundException e ) {
System . out . println ( ” Eroare incarcare driver !\ n ” + e ) ;
return ;
}
try {
Connection con = DriverManager . getConnection ( url ) ;
// Golim tabelul persoane
String sql = ” DELETE FROM persoane ” ;
Statement stmt = con . createStatement () ;
stmt . executeUpdate ( sql ) ;
// Adaugam un numar de persoane generate aleator
// Tabelul persoane are coloanele ( cod , nume , salariu )
int n = 10;
sql = ” INSERT INTO persoane VALUES (? , ? , ?) ” ;
PreparedStatement pstmt = con . prepareStatement ( sql ) ;
for ( int i =0; i < n ; i ++) {
int cod = i ;
String nume = ” Persoana ” + i ;
double salariu = 100 + Math . round ( Math . random () *
900) ;
// salariul va fi intre 100 si 1000
pstmt . setInt (1 , cod ) ;
pstmt . setString (2 , nume ) ;
pstmt . setDouble (3 , salariu ) ;
pstmt . executeUpdate () ;
}
// Afisam persoanele ordonate dupa salariu
sql = ” SELECT * FROM persoane ORDER BY salariu ” ;
ResultSet rs = stmt . executeQuery ( sql ) ;
while ( rs . next () )
System . out . println ( rs . getInt ( ” cod ” ) + ” , ” +
rs . getString ( ” nume ” ) + ” , ” +
rs . getDouble ( ” salariu ” ) ) ;
// Calculam salariul mediu
sql = ” SELECT avg ( salariu ) FROM persoane ” ;
rs = stmt . executeQuery ( sql ) ;
rs . next () ;
System . out . println ( ” Media : ” + rs . getDouble (1) ) ;
// Inchidem conexiunea
con . close () ;
} catch ( SQLException e ) {
e . printStackTrace () ;
}
}
}
Lucrul cu meta-date
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ă, aşa numitele meta-date (”date despre date”); Ca rezult 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 dat inregistrată in ODBC.
Folosirea interfeţei DatabaseMetaData
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 ;
}
}
}
Interfaţa ResultSetMetaD
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 ResultSetMetaData
Meta-datele unui ResultSet reprezintă informaţiile despre rezultatul conţinut in 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.
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);
Multumesc pentru informatiile utile. Înțelegerea elementelor fundamentale determină succesul lucrului cu bazele de date, ceea ce este foarte important pentru construirea soluțiilor potrivite în Java. Multumesc din nou!
Sper sa iti fie de folos acest articol .
Numai bine !
Web 版Skype 是享受您在傳統型應用程式中熟悉的Skype 功能最簡單的方法,而不需要下載。 您可以登入Web 版Skype 然後立即開始聊天。https://www.skypeie.com