Lucrul cu baze de date - JDBC

O bază de date reprezintă o modalitate de stocare persistentă a informaţiilor pe un suport fizic cu posibilitate de regăsire a acestora ulterior. Cel mai cunoscut model de baze de date este cel relaţional în care datele sunt memorate sub formă de tabele. Bazele de date relaţionale mai conţin pe lângă tabele funţii şi proceduri, mecanisme de gestionare a utilizatorilor, tipuri de date, etc.

JDBC (java Database Connectivity) este o tehnologie parte a Java SE (Standaed Edition) ce ofera programatorului o interfata unitara prin care acesta poate accesa o baza de date. JDBC ofera un API (Application Programming Interfaces) care defineste un set de operatii pe care programatorul le poate utiliza pentru a se conecta si a manipula continutului unei baze de date.

Arhitectura generala a JDBC este prezentata in figura urmatoare.

Una dintre problemele care apare atunci când se lucrează cu baze de date este reprezentată de incompatibilităţile dintre diverşi producători. Deşi există limbaj standard (SQL), totuşi utilizatorul trebuie să ştie cu ce tip de bază de date lucrează. JDBC a fost proiectat astfel încât să fie independent de tipul de bază de date cu care se lucrează.

Accesarea unei baze de date folosind JDBC este simplă şi implică următorii paşi:

  • Obţinerea unui obiect de tip Connection ce încapsulează conexiunea la baza de date (în acest pas se realizează deci conexiunea la baza de date).
  • Obţinerea unui obiect Statement dintr-un obiect de tip Connection. Acest obiect este folosit pentru a transmite spre execuţie comenzi SQL către baza de date. Prin intermediul acestui obiect sunt efectuate operaţii de interogare şi modificare a bazei de date.
  • Obţinerea unui obiect ResultSet dintr-un obiect Statement. Obiectul ResultSet încapsuleaz rezultatele operaţiilor de interogare.
  • Procesarea rezultatelor încapsulate în obiectul ResultSet.

Utilizarea JDBC in aplicatii multi-nivel

Modelul pe două nivele implică comunicarea directă între aplicaţia java şi baza de date. Aplicaţia utilizator trimite direct către baza de date secvenţe de instrucţiuni SQL ce sunt executate, iar rezultatele sunt întoarse către client. Baza de date poate fi localizată pe acelaşi calculator cu aplicaţia client sau pe un calculator aflat la distanţă, comunicarea realizându-se prin reţea (în acest caz aplicaţia poate fi considerată o aplicaţie de tip client server).

În cadrul modelului pe trei nivele comenzile sunt trimise către un nivel de mijloc (intermediar), care va trimite comenzile către baza de date. Baza de date procesează comenzile şi transmite rezultatele către nivelul intermediar care mai apoi le va trimite către client. Avantajele acestui model sunt: controlul mai bun al accesului, se simplifică instalarea aplicaţie (la nivelul clientului nu este nevoie să se instaleze o aplicaţie complexă ce consumă resurse, de cele mai multe ori accesul realizându-se prin intermediul unei aplicaţii de tip WEB), performanţe mai bune, mentenanţă mai uşoară, felexibilitate.

Prezentare API JDBC

Conectarea la o baza de date

In listingul de mai jos este exemplificat modul in care se realizeaza cu JDBC o conexiune la baza de date:

public class TestConectare {
 
    public static void main(String[] args) {
            try {
                //incarcare driver petru baza de date
                Class.forName("com.mysql.jdbc.Driver");
 
                //conectare la baza de date
              Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/persoane?user=student&password=pas123");
                System.out.println("Conexiune la baza de date realizata.");
 
                //inchide cnexiune la baza de date
                conn.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
   }
}          

Incarcarea driverului

Primul pas necesar pentru a putea lucra cu o bază de date este încărcarea driverului bazei de date la care urmează să se realizeze conexiunea. În funcţie de tipul bazei de date va trebui selectat driverul JDBC corespunzător acesteia. În mod uzual pe site-ul oficial al producătorilor de sisteme de baze de date se regăseşte si driverul JDBC pentru conectarea din java la acea baza de date.

Incarcarea driverului este foarte simplă şi implică o singură instrucţiune. Presupunând că se utilizează driverul JDBC MySQL pentru conectarea la o bază de date MySQL atunci codul care realizează încărcarea driverului este următorul:

Class.forName("com.mysql.jdbc.Driver");

Driverul JDBC încărcat are rolul de a converti comenzile SQL într-un anumit protocol specific unui anumit SGBD.

Şirul de caractere dat ca argument la metoda forName reprezintă clasa de tip driver. Acest şir îl regăsiţi de obicei în documentaţia driverului. După încărcarea driverului se poate realiza conexiunea la baza de date.

Realizarea conexiunii

Pentru realizarea conexiunii cel mai important parametru este URL-ul bazei de date. URL-ul identifică baza de date la care urmează să se realizeze conexiunea.

Url-ul catre baza de date este similar cu cel al unei resurse web:

jdbc:mysql://127.0.0.1/persoane?user=student&password=pas123

In cadrul url-ului avem urmatoarele componente:

  • componenta care specifica tipul de driver utilizat (jdbc:odbc)
  • componenta care specifica adresa de ip a calculatorului pe care este localizata baza de date (127.0.0.1)
  • numele bazei de date la care se realizeaza conectarea (student)
  • utilizatorul si parola bazei de date (user=student&password=pas123)

Transmiterea de comenzi catre baza de date

Pentru a putea efectua operaţii asupra unei baze de date la care s-a realizat conexiunea se lucrează cu un obiect Statement. Acest obiect se obţine din cadrul obiectului Connection astfel:

Statement stat = con.createStatement();

Din acest moment obiectul ‘stat’ va fi utilizat pentru a efectua operaţii asupra bazei de date.

Operatiile asupra bazei de date pot fi de tip uptdate sau de tip interogare.

Operatiile de tip update presupune alterarea continutului sau structurii baei de date (construire tabel, adugare /modificare/stergere inregistrare, etc) - pentru aceste operatii se va utiliza metoda executeUpdate(…). Operatiile de tip interogare presupune intoarcerea unor rezultate catre aplicatie cu rezultatele obtinute - pentru aceste operatii se va folosi metoda executeQuery(…).

Creare tabel

Se utilizează metoda executeUpade() care va primi ca parametru un String care reprezintă o comandă SQL validă pentru crearea unui tabel.

stat.executeUpdate("CREATE TABLE PERS (NUME VARCHAR(32), VARSTA INTEGER)");

Inserare date in tabel

Pentru adăugarea de înregistrări în cadrul unui tabel se utilizează aceiaşi metodă executeUpdate().

stat.executeUpdate(“INSERT INTO PERS VALUES(‘ADI’, 15));  

Integrogare baza de date

Pentru citirea conţinutului unui tabel se utilizeză metoda executeQuery() a clasei Statement.

ResultSet result = stat.executeQuery("SELECT PROD,PRET FROM STOC");       

Rezultatul interogării bazei de date va fi returnat într-un obiec de tip ResultSet. Pentru a parcurge , linie cu linie, a rezultatelor interogării se realizează utilizând metoda next() din cadrul obiectului ResultSet.

Exemplu complet

In cadrul programului următor este prezentat exemplu care realizează operaţiile de creare de tabel, inserare de înregistrări şi interogare a unui tabel.

import java.sql.*;
 
public class JdbcTest {
 
  public static void main(String[] args) {
    try{
 
      Class.forName("com.mysql.jdbc.Driver");
      Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1/persoane?user=student&password=pas123");
      Statement stat = con.createStatement();
 
      stat.executeUpdate("CREATE TABLE STOC (PROD VARCHAR(32), PRET INTEGER)");
 
      stat.executeUpdate("INSERT INTO STOC VALUES ('PROD1' , 2500)");
      stat.executeUpdate("INSERT INTO STOC VALUES ('PROD2' , 7900)");
 
      ResultSet result = stat.executeQuery("SELECT PROD,PRET FROM STOC");
 
      while(result.next()){
       String prod = result.getString(1);
       int pret = result.getInt(2);
 
       System.out.println("Produs = "+prod+"   "+"Pret = "+pret);
      }
    }
    catch(Exception e){e.printStackTrace();}
 
  }
}   

Comenzi precompilate

Clasa PreparedStatement extinde clasa Statement. Spre deosebire de Statement, un obiect PreparedStatemtn primeşte o comandă SQL în momentul în care este creat. Un obiect PreparedStatement reprezintă o comandă SQL precompilată. Această clasă se utilizează in momentul în care se doreşte executarea unei comenzi de mai multe ori, intrucât cresc performanţele (viteza de execuţie) , deoarece comanda SQL este precompilată în momentul crearii obiectului PreparedStatement.

PreparedStatement ps = con.prepareStatement("INSERT INTO STOC VALUES(?,?)");
ps.setString(1,"PROD 3");ps.setInt(2,6000);
ps.executeUpdate();
ps.setString(1,"PROD 4");ps.setInt(2,9000);
ps.executeUpdate();

Functionalitati JDBC 2.0

Dacă în cadrul JDBC 1.0 deplasarea cursorului în cadrul unui obiect ResultSet se putea face doar înainte, utilizând metoda next(), JDBC 2.0 oferă noi facilităţi de deplasare în cadrul ResultSet.

Liniile următoare arată modul de crearea a unui ResultSet în cadrul căruia cursorul se poate deplasa atât înainte cât şi înapoi.

Statement st = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
                                                     ResultSet.CONCUR_READ_ONLY);

Metoda createStatement primeşte doi parametri. Primul parametru poate lua valorile TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE şi TYPE_SCROLL_SENSITIVE. Cel de al doilea parametru poate lua valorile CONCUR_READ_ONLY şi CONCUR_UPDATABLE.

Odată obţinut obiectul ResultSet se pot folosi următoarele metode pentru deplasarea cursorulu : absolute(), afterLast(), beforeFirst(), first(), next(), previous(), relative(row).

Modificarea conţinutului unui obiect ResultSet

O nouă facilitate introdusă de JDBC 2.0 este abilitatea de a modifica liniile din cadrul unui obiect ResultSet. O dată realizate modificările , baza de date va putea fi reîmprospătată conform acestor modificări.

 rs.absolute(2);
 rs.updateString(1,"AltProdus");
 rs.updateInt(2,6700);
 rs.updateRow();  

Secvenţa anterioară prezintă modul în care se poate realiza reînprospătarea unei linii în cadrul unui ResulSet. Se observă că după ce linia a fost modificată, pentru ca aceste modifcări să se realizeza şi în cadru bazei de date, se apelează metoda updateRow().

Inserarea unei linii în cadrul bazei de date, utilizând JDBC 1.0 se realiza astfel:

stat.executeUpdate("INSERT INTO STOC VALUES('PRODX',9800)")

Aceiaşi operaţie se poate realiza utlizând JDBC 2.0 astfel:

rs.moveToInsertRow();
rs.updateString("PROD", "PRODX");
rs.updateInt(2, 9800);
rs.insertRow();

Pentru a modifica o înregistrare se pot folosi atât numele coloanelor cât şi numărul acestora. Numărul coloanei din cadrul ResultSet nu are nici o legătură cu numărul coloanei din cadrul bazei de date.

In momentul în care se execută moveToInsertRow(), obiectul ResultSet memorează linia pe care se afală cursorul, după care adaugă o nouă linie şi mută cursorul pe linia respectivă. In aceste condiţii după ce s-a adăugat o linie , execuţia metodei moveToCurrentRow(), determină mutarea cursorului pe linia la care se afla înainte de inserarea noii linii.

Stergerea unei linii se realizează astfel:

rs.absolute(4);
rs.deleteRow();

Exemplu complet JDBC 2.0

import java.sql.*;
 
public class DBWork{
 
  public static void main(String[] args) {
       try{
 
      Class.forName("com.mysql.jdbc.Driver");
      Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1/produse?user=student&password=pas123");
 
      Statement stat = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
 
      ResultSet rs = stat.executeQuery("SELECT * FROM STOC");
 
      //modificarea liniei 2 din cadrul rs
      rs.absolute(2);
      rs.updateString(1,"AltProdus");
      rs.updateInt(2,6700);
      rs.updateRow();
 
      //adauga linie
      rs.moveToInsertRow();
      rs.updateString(1, "PRODX");
      rs.updateInt(2, 9800);
      rs.insertRow();
 
      //sterge linie
      rs.absolute(4);
      rs.deleteRow();
 
      rs.afterLast();
      while(rs.previous()){
        System.out.println(rs.getString(1)+" "+rs.getInt(2));
      }
 
      rs.close();
      }
 
    catch(Exception e){e.printStackTrace();}
 
  }
}