1.1.1 Baze de date relaţionale
Mulţi dintre noi am lucrat deja cu baze de date relaţionale. De fapt, cei mai mulţi folosim baze de date relaţionale în fiecare zi. Această tehnologie este foarte cunoscută. Ea însăşi este un motiv suficient pentru multe organizaţii ca s-o aleagă. Totuşi aceasta înseamnă mai mult: Bazele de date relaţionale nu sunt folosite la întâmplare, ci pentru că sunt incredibil de flexibile şi robuste faţă de controlul informaţiei.
Nu există un sistem de control al bazelor de date specific pentru un limbaj, şi nici un sistem de baze de date relaţionale nu este specific vreunei aplicaţii. Tehnologia relaţională oferă un mod de a trimite informaţie între diferite aplicaţii sau între diferite tehnologii care sunt parte din aceeasi aplicaţie (de exemplu, motorul de tranzacţii şi motorul de rapoarte). Tehnologia relaţională este folosită pe multe sisteme şi platforme diferite. De fapt, modelul de date relaţional este adesea reprezentarea la nivel de companie a entităţilor.
Sistemele de control ale bazelor de date au interfeţele de program bazate pe SQL, şi, de cele mai multe ori, vorbim despre sisteme de baze de date SQL, sau, mai simplu, baze de date SQL.
1.1.2 Despre SQL
Pentru a folosi sisteme de control ale bazelor de date, este necesară înţelegerea în prealabil a modelului relaţional. Această înţelegere este necesară pentru îmbunătăţirea calitativă a performanţelor aplicaţiilor. Sistemele de control automatizează părţi (care se repetă) ale programelor, dar pentru a controla complet aceste sisteme, sunt necesare cunoştinţele despre bazele de date SQL. Până la urmă, scopul final este de a controla robust şi eficient datele persistente.
Să începem cu nişte termeni SQL pe care-i vom folosi mai târziu. Vom folosi SQL ca limbaj de definiţie a datelor (data definition language – DDL) pentru a crea structurile de baze de date cu instrucţiunile CREATE şi ALTER. După crearea tabelelor (şi a indecşilor, query-urilor, etc), vom folosi SQL ca limbaj de manipulare a datelor (data manipulation language – DML). Cu DML, executăm comenzi SQL care modifică sau recuperează informaţie. Manipularea datelor include inserare, modificare, ştergere. Recuperarea datelor se va face executând cereri cu restricţii, proiecţii, şi operaţii de legătură (incluzând produsul cartezian). Pentru rapoarte eficiente, vom folosi SQL pentru grupare, sortare, agregarea datelor în mai multe feluri. Instrucţiunile SQL se pot imbrica una în cealaltă (această tehnică se cheamă sub-selectare). Chiar dacă folosim des instrucţiunile SQL şi suntem familiari cu operaţiile de bază din acest limbaj, uneori este greu să ne aducem aminte de ele, şi uneori acestea variază ca mod de utilizare. Cunoştinţele despre SQL sunt absolut necesare pentru dezvoltarea aplicaţiilor folosind persistenţa relaţională.
1.1.3 Persistenţa în aplicaţiile orientate obiect
În aplicaţiile orientate obiect, persistenţa ajută un obiect să depăşească durata de viaţă a procesului care l-a creat. Starea unui obiect poate fi stocată pe disc, şi un obiect cu aceeaşi stare poate fi re-creat mai târziu.
Această aplicaţie nu este limitată la obiecte singulare – modele întregi de obiecte interconectate pot fi făcute persistente, şi apoi re-create într-un nou proces. Cele mai multe obiecte nu sunt persistente. Un obiect transient are o durată de viaţă limitată, mărginită de viaţa procesului care l-a instanţiat. Aproape toate aplicaţiile conţin un amestec de obiecte persistente şi transiente. Noi avem nevoie de un subsistem care să controleze datele persistente.
Bazele de date relaţionale moderne oferă o reprezentare structurată a informaţiei persistente, lăsând posibilitate de sortare, căutare şi agregare de date. Sistemele de control ale bazelor de date sunt responsabile pentru verificarea concurenţei şi integritatea datelor. Sunt responsabile pentru partajarea datelor între mai mulţi utilizatori şi mai multe aplicaţii. Un sistem de control al bazelor de date este deasemenea prevăzut cu securitate la nivel de date. Persistenţa se referă la:
• Stocare, organizare, recuperare de date structurate
• Concurenţă şi integritate a informaţiei
• Partajarea informaţiei
Specific, aceste probleme puse într-un context de aplicaţie orientată obiect folosesc aşa-numitul model-domeniu. O aplicaţie care foloseşte acest model nu va lucra direct pe reprezentarea tabulară a entităţilor. Aplicaţia va avea ea însăşi modelele orientate obiect ale entităţilor. Dacă baza de date are tabelele OBIECT şi LICITATIE, atunci aplicaţia care le foloseşte va defini clasele Obiect şi Licitatie.
Apoi, în loc de lucrul direct pe rândurile şi coloanele dintr-un rezultat SQL, logica programului interacţionează cu acest model orientat-obiect şi cu reţeaua lui (creată în timpul rulării) de obiecte interconectate. Logica programului nu se execută niciodată în baza de date (ca şi o procedură stocată SQL), ci este implementată în limbajul de programare orientat obiect. Aceasta permite folosirea conceptelor sofisticate orientate-obiect, cum ar fi moştenirea sau polimorfismul. Nu toate aplicaţiile sunt definite în acest mod, şi nici nu ar trebui să fie. Aplicaţiile simple ar fi mult mai bune fără acest model, lucrând cu reprezentarea tabulară a datelor persistente.
În cazurile aplicaţiilor cu logică netrivială, acest model ajută semnificativ la reutilizarea, îmbunătăţirea şi uşurinţa de întreţinere a codului.
1.2 Motive şi scopuri
Controlul informaţiei care este stocată într-un container persistent este o nevoie a multor aplicaţii. Totuşi, efortul de a implementa un strat de persistenţă, de cele mai multe ori, cere o măsură mare de timp, şi este obositor.
1.2.1 Reducerea efortului la dezvoltare
Principalul scop al generatorilor de straturi de persistenţă este de a reduce drastic efortul de dezvoltare necesar implementării unui strat de persistenţă, fără a pierde flexibilitatea şi adaptabilitatea necesară pentru nevoile aplicaţiei. Descrierea unei astfel de componente trebuie să fie destul de simplă încât scrierea ei să nu ia mult timp
1.2.2 Producerea de cod optimizat
Un alt scop al generatorilor este acela de a produce cod optimizat, în conformitate cu specificaţiile din descrierea componentei. Generatorul trebuie să analizeze descrierea componentei, ca să evite producerea de cod inutil aplicaţiei. Codul trebuie să fie întotdeauna generat astfel încât execuţia lui să ia cât mai puţin timp, şi dacă se poate, cât mai puţine resurse.
1.2.3 Persistenţă independentă de container
Ca şi orice abstractizare, descrierea componentei şi interfaţa generată trebuie să fie independente de tipul de container în care este ţinută informaţia. Din acest motiv, descrierea componentei nu trebuie să conţină detalii care sunt specifice unui container de persistenţă. Aceasta oferă flexibilitate în a schimba containerul, oricând dezvoltatorul găseşte acest lucru ca fiind convenabil.
1.3 Probleme des întâlnite
Problemele pot fi împărţite în mai multe părţi, pe care le vom examina separat. Vom începe prin a studia un exemplu simplu, fără probleme. Apoi, construind pe el, vom observa problemele ce pot apărea.
Să presupunem că avem de implementat o aplicatie de magazin virtual. În această aplicaţie, avem nevoie de o clasă care să reprezinte informaţii despre un utilizator al acestui sistem, altă clasă pentru reprezentarea detaliilor pentru facturare ale utilizatorului (Figura 1).
Privind această diagramă, observăm că un Utilizator are mai multe DetaliiFacturare. Navigarea în relaţie se poate face în ambele direcţii. Pentru început, clasele reprezentând aceste entităţi pot fi extrem de simple:
Class Utilizator {
var $numeUtilizator;
var $nume;
var $adresa;
var $detaliiFacturare;
//... metode pentru acces (get/set), alte metode...
}
Class DetaliiFacturare {
var $numarCont;
var $numeCont;
var $tipCont;
var $utilizator;
//... metode pentru acces (get/set), alte metode...
}
Pentru că ne interesează doar aspectul entităţilor legat de persistenţă, am omis implementarea metodelor pentru acces, şi a metodelor de business. Este destul de uşor de găsit o structură SQL bună pentru acest caz:
create table UTILIZATOR (
NUMEUTILIZATOR VARCHAR(15) NOT NULL PRIMARY KEY,
NUME VARCHAR(50) NOT NULL,
ADRESA VARCHAR(100)
)
create table DETALII_FACTURARE (
NUMAR_CONT VARCHAR(10) NOT NULL PRIMARY KEY,
NUME_CONT VARCHAR(50) NOT NULL,
TIP_CONT VARCHAR(2) NOT NULL,
NUMEUTILIZATOR VARCHAR(15) FOREIGN KEY REFERENCES UTILIZATOR
)
Relaţia între cele două entităţi este reprezentată ca o cheie străină, NUMEUTILIZATOR în DETALII_FACTURARE. Pentru acest model simplu, neconcordanţa obiectual-relaţională este greu evidenţiată. Codul ce trebuie generat este clar.
Acum, să vedem ce se întâmplă când ne gândim la ceva mai realistic. Neconcordanţele de paradigmă vor fi vizibile când adăugăm mai multe entităţi şi relaţii între ele în aplicaţie.
Cea mai evidentă problemă în această implementare este că am modelat adresa ca un simplu câmp String. În cele mai multe sisteme, este necesar să stocăm separat informaţia despre stradă, oraş, ţară. Am putea adăuga aceste proprietăţi direct în clasa Utilizator, dar este foarte posibil ca alte clase din sistem să aibă nevoie de această informaţie, aşa că este mai logic să avem o clasă separată de Adresa. Modelul modificat se află în Figura 0
Să folosim un tabel Adresa? Nu neapărat. Se obişnuieşte să ţinem aceste informaţii în tabelul Utilizator, în coloane separate. Într-un fel este mai bine, pentru că nu trebuie să facem legături între tabele ca să aducem utilizatorul şi adresa printr-o singură cerere. Cea mai bună soluţie ar putea fi crearea unui tip de date în SQL care să reprezinte adresa, şi să folosească o singură coloană de acel nou tip în loc de mai multe coloane.
Avem de ales între a adăuga mai multe coloane, sau o singură coloană (de un tip de date nou în SQL). Aceasta este o problemă de granularitate.
1.3.1 Probleme de granularitate
Granulariatatea se referă la dimensiunea relativă a obiectelor cu care lucrăm. Când ne referim la obiecte şi la tabele din baze de date, problema granularităţii constă în salvarea obiectelor cu diferite grade de granularitate în tabele şi coloane care sunt (în mod natural) limitate ca şi granularitate.
Să revenim la exemplul nostru. Cea mai bună metodă ar fi să adăugăm un nou tip de date care să poată memora Adresa într-o singură coloană în baza de date. Acest nou tip (clasă) Adresa şi tipul de date ADRESA din SQL ar trebui să garanteze interoperabilitatea. Totuşi, există multe probleme în definirea tipurilor de date (user defined types – UDT) în sistemele de control ale bazelor de date SQL.
UDT este una dintre acele aşa-numite extensii obiect-relaţionale pentru tradiţionalul SQL. Din nefericire, suportul pentru UDT este o facilitate destul de obscură în cele mai multe sisteme de control ale bazelor de date, şi în mod sigur nu este portabil între sisteme. Standardul SQL permite definirea tipurilor de date de către utilizator, dar foarte sărăcăcios. Din acest motiv (şi alte motive), folosirea UDT nu este comună în acest timp, şi puţin probabil să găsim o structură de tabele care să folosească intensiv UDT-uri. Deci nu vom putea stoca obiecte de tipul Adresa într-o singură coloană în SQL. Soluţia pentru această problemă are mai multe coloane, de tipuri standard SQL (booleane, numerice, string). Luând în considerare din nou granularitatea obiectelor, tabelul UTILIZATOR va fi definit (de obicei) astfel:
create table UTILIZATOR (
NUMEUTILIZATOR VARCHAR(15) NOT NULL PRIMARY KEY,
NUME VARCHAR(50) NOT NULL,
ADRESA_STRADA VARCHAR(50),
ADRESA_ORAS VARCHAR(15),
ADRESA_JUDET VARCHAR(15),
ADRESA_CODPOSTAL VARCHAR(6),
ADRESA_TARA VARCHAR(15),
)
Apare următoarea observaţie: clasele din modelele de obiect au diferite nivele de granularitate – de la clase cu granularitate mare (Utilizator) la clase cu granularitate fină (Adresa), până la proprietăţi simple, string (codPostal).
Se pare că problema granularităţii nu este aşa de dificil de rezolvat. De fapt, poate nici n-ar fi trebuit amintită, dacă n-ar fi fost vizibilă în atâtea sisteme deja existente. O problemă mult mai dificilă şi mai interesantă apare atunci când considerăm obiecte ce folosesc moştenirea, o caracteristică a dezvoltării orientată-obiect.
1.3.2 Probleme de subtipuri
În general, moştenirea este implementată folosind super-clase şi sub-clase. Pentru a ilustra de ce aceasta ar putea fi o problemă de nepotrivire, vom continua construcţia exemplului nostru. Să facem ca aplicaţia de magazin virtual să accepte nu doar plata prin cont bancar, dar şi prin carduri de debit şi de credit. Avem deci la dispoziţie câteva metode pentru a factura un utilizator. Cel mai natural mod de a reflecta aceste schimbări este să folosim moştenirea clasei DetaliiFacturare. Am putea să avem o super-clasă abstractă DetaliiFacturare, şi clasele concrete CreditCard, DebitCard, Cec, şi aşa mai departe. Fiecare dintre aceste sub-clase îşi va defini informaţia necesară (puţin diferită), şi funcţionalităţi complet diferite asupra acestei informaţii.
CUPRINS
1 INTRODUCERE ÎN PERSISTENŢA RELAŢIONALĂ 3
1.1 CE ESTE PERSISTENŢA? 3
1.1.1 Baze de date relaţionale 3
1.1.2 Despre SQL 4
1.1.3 Persistenţa în aplicaţiile orientate obiect 4
1.2 MOTIVE ŞI SCOPURI 5
1.2.1 Reducerea efortului la dezvoltare 5
1.2.2 Producerea de cod optimizat 6
1.2.3 Persistenţă independentă de container 6
1.3 PROBLEME DES ÎNTÂLNITE 6
1.3.1 Probleme de granularitate 8
1.3.2 Probleme de subtipuri 9
1.3.3 Probleme de identitate 11
1.3.4 Probleme legate de asociere 12
1.3.5 Probleme legate de navigabilitatea în graf 14
1.3.6 Costul acestor probleme 15
1.4 MAPAREA OBIECTUAL RELAŢIONALĂ 16
1.4.1 Ce este ORM? 16
1.4.2 Nivele de calitate a ORM urilor 17
1.4.2.1 Pur relaţionale 17
1.4.2.2 Mapare obiectuală uşoară 17
1.4.2.3 Mapare obiectuală medie 17
1.4.2.4 Mapare obiectuală completă 18
1.4.3 Probleme generice 18
1.4.4 De ce ORM? 19
1.4.4.1 Productivitate 20
1.4.4.2 Mentenanţă 20
1.4.4.3 Performanţă 20
1.4.4.4 Independenţa de straturile inferioare 21
1.5 LUCRUL CU OBIECTE PERSISTENTE 21
1.5.1 Ciclul de viaţă al persistenţei 22
1.5.1.1 Obiecte transiente 23
1.5.1.2 Obiecte persistente 23
1.5.1.3 Obiecte detaşate 24
2 PERSISTENŢA RELAŢIONALĂ APLICATĂ 26
2.1 MANAGERUL DE PERSISTENŢĂ HIBERNATE 26
2.1.1 Un obiect făcut persistent 26
2.1.2 Un obiect adus din persistenţă 27
2.1.3 Un obiect persistent modificat 27
2.1.4 Un obiect persistent făcut transient 28
2.1.5 Persistenţa tranzitivă în Hibernate 28
2.1.6 Aducerea obiectelor cu Hibernate 30
2.2 ACTIVERECORD – MANAGERUL DE PERSISTENŢĂ DIN RUBY ON RAILS 31
2.2.1 Mapare automată între clase şi tabele, între atribute şi coloane 32
2.2.2 Asocierea dintre obiecte prin meta instrucţiuni simple 32
2.2.3 Agregare între obiectele valoare 33
2.2.4 Regulile de validare pot să difere pentru obiecte noi sau existente 33
2.2.5 Înregistrările pot să funcţioneze ca liste sau arbori 34
2.2.6 Metode callback pentru întreaga durată de viaţă 35
2.2.7 Ierarhii de moştenire 35
2.2.8 Tranzacţii 36
2.2.9 Manipulare directă (în loc de invocări de servicii) 36
2.3 HIBERNATE SAU ACTIVERECORD? 36
2.3.1 Diferenţe arhitecturale de bază 37
2.3.2 Valoarea clarităţii 37
2.3.3 Asocieri 39
2.3.3.1 Persistenţa tranzitivă 40
2.3.4 Limbaje de cerere 41
2.3.4.1 Căutările instante în Rails 42
2.3.4.2 Cererea de obiecte cu HQL 42
2.3.5 Îmbunătăţirea performanţelor 43
2.3.5.1 Aducerea târzie 43
2.3.5.2 Legături externe şi aducere explicită 44
2.3.5.3 Caching 45
2.3.6 Ce alegem? 45
3 APLICAŢIA PRACTICĂ 46
3.1 SPECIFICAREA APLICAŢIEI 46
3.2 DOCUMENTAŢIA DE PROIECTARE ŞI IMPLEMENTARE 46
3.2.1 Tehnologii folosite 50
3.2.2 Aplicarea persistenţei relaţionale 50
3.3 DOCUMENTAŢIA DE UTILIZARE 51
4 ANEXE 53
4.1 ACRONIME 53
4.2 BIBLIOGRAFIE 54