Laborator 02

Asigurarea Persistenței Datelor folosind JPA (Java Persistence API) peste Hibernate

Obiective

  • descrierea avantajelor și dezavantajelor pe care le implică folosirea unui sistem care mapează informațiile dintr-un sistem de reprezentare relațional în obiecte, comparativ cu alte interfețe de programare care oferă acces la sursele de date;
  • enumerarea principalelor clase care constituie arhitectura JPA;
  • maparea unui model de date relațional la un model de date orientat pe obiecte folosind sintaxa Java Persistence API;
  • exploatarea informațiilor dintr-o sursă de date, în cadrul unor obiecte de la nivelul de logică a aplicației, fără a cunoaște detaliile de implementare de la nivelul bazei de date.

Cuvinte Cheie

ORM (object-relational mapping), POJO (Plain Oriented Java Object), entity, embedded class, persistence, relationship, inheritance, persistence unit, DAO (Data Access Object), JPQL (Java Persistence Query Language), Criteria API, abstract schema, Backus-Naur Form (BNF)

Materiale Ajutătoare

JPA (Java Persistence API) - aspecte generale

JPA (Java Persistence API) reprezintă o colecție de clase (în limbajul de programare Java), oferind funcționalități legate de interogarea eficientă a informațiilor stocate în baze de date relaționale, mai ales în situația în care volumul acestora este considerabil. Această interfață de programare implementează un nivel de abstractizare, toate informațiile fiind stocate la nivel de obiecte, astfel încât programatorul nu este obligat să cunoască detaliile specifice unui sistem de gestiune al bazelor de date. Prin urmare, JPA realizează o punte între informațiile reprezentate relațional - așa cum sunt reținute într-o bază de date - și sub formă de instanțe ale unor clase, permițând un transfer facil între aceste moduri de organizare, implementând în același timp funcționalități de acces la date prin diferite mecanisme de interogare.

În Java, JPA reprezintă deja un standard în privința nivelului de acces la date. Astfel funcționalitatea de interacțiune cu baza de date este oferită de regulă de un furnizor JPA, adică de un sistem care implementează această interfață de programare open-source. Cele mai frecvent utilizate produse de acest tip sunt:

Istoric, JPA a fost implementat împreună cu specificația Enterprise JavaBeans 3.0 în Java Enterprise Edition 5. Versiunea JPA 1.0 era descrisă astfel de JSR 220. Ulterior, în versiunea Java Enterprise Edition 6 a fost implementat JPA 2.0 prin JSR 317. JPA 2.1 reprezintă cea mai recentă versiune a acestei interfețe de programare, fiind inclus în Java Enterprise Edition 7, funcționalitatea sa fiind conținută în JSR 338.

Sisteme de Tip ORM (Object-Relational Mapping)

Termenul de ORM (Object-Relational Mapping) este folosit pentru a descrie o tehnică de programare prin care sunt transformate informațiile între formatul în care sunt stocate în bazele de date relaționale și formatul în care sunt reprezentate în limbajele de programare orientate-obiect.

O soluție ORM este formată din mai multe componente:

  1. o interfață de programare care să ofere funcționalități de tip CRUD (Create, Read, Update, Delete) pentru instanțele unei clase persistente;
  2. un limbaj (sau o interfață de programare) prin care să poată fi descrise interogări referitoare la clasele persistente și la diferitele proprietăți ale acestora;
  3. un mecanism flexibil de specificare a modului în care datele dintr-o formă de reprezentare pot fi convertite în cealaltă formă de reprezentare;
  4. o tehnică prin care să se poată utiliza obiectele tranzacționale astfel încât să se realizeze diferite funcții de optimizare cum ar fi evitarea scrierilor murdare, a citirilor nerepetabile sau a citirilor fantomă, precum și a preluării asocierilor evaluate leneș (eng. lazy association fetching).

Necesitatea de a folosi un sistem ORM este dată de incompatibilitățile între modelul de date relațional (în care informațiile sunt reprezentate într-o formă tabulară) și modelul de date obiectual (în care informațiile sunt stocate sub forma unui graf de obiecte interconectate). Problemele pe care le adresează un sistem de mapare de tip ORM sunt legate de:

  • granularitate - modelul de date obiectual conține de regulă mai multe entități decât modelul de date relațional;
  • moștenire - mecanismul de moștenire, descris de limbajele de programare orientate obiect, nu este suportat de cele mai multe dintre sistemele de gestiune pentru baze de date relaționale;
  • identitate - noțiunea de similaritate în sisteme de gestiune pentru baze de date relaționale este descrisă prin intermediul cheii primare în timp ce modelul obiectual face distincția între identitate (verificarea fiind realizată la nivelul referințelor către obiecte) și egalitate (verificarea referindu-se la conținutul propriu-zis al obiectelor, adică al valorilor pe care le iau proprietățile claselor);
  • asociere - în sistemele de gestiune pentru baze de date, referințele sunt reprezentate prin intermediul cheii străine în timp ce modelul obiectual utilizează referințele între instanțe ale claselor;
  • navigare - modul de acces la informații este diferit în cele două mecanisme de reprezentare al datelor.

Sistemele de tip ORM folosesc un proces bazat pe mai multe etape în conversia dintre un obiect și o înregistrare la nivelul bazei de date, interacțiunea dintre acestea purtând numele de mapare obiectual-relațională:

  1. etapa obiectului care încapsulează date (eng. object data) conține:
    1. obiecte POJO (Plain Oriented Java Objects), care descriu entitățile pentru care se asigură persistența;
    2. clase și interfețe de tip serviciu, care oferă diferite funcționalități (de tip CRUD) referitoare la entitățile persistente, reprezentând componentele care implementează logica aplicației;
  2. etapa de asociere sau realizare a persistenței este formată din mai multe componente:
    1. furnizorul de servicii JPA reprezintă un produs specific care implementează interfața de programare JPA (Hibernate, EclipseLink, Spring Data JPA, TopLink, OpenJPA, DataNucleus);
    2. fișierul ce descrie mapările (ORM.xml) conține legăturile dintre proprietățile unui obiect POJO și atributele tabelei asociate din cadrul bazei de date;
    3. obiectul de încărcare JPA descrie o zonă de memorie în care pot fi stocate informațiile din sursa de date relaționale, putând fi integrată cu clasele și interfețele de tip serviciu pentru a putea accesa proprietățile unei entități persistente;
    4. tabela de obiecte este o locație temporară care poate stoca informații din baza de date și pe care sunt realizate toate interogările, înainte de a fi transmise mai departe către sursa de date;
  3. etapa informațiilor relaționale conține sursa de date conectată la obiectul de logică a aplicației

Hibernate

Hibernate reprezintă o soluție de tip ORM pentru asigurarea persistenței și a interogării eficiente a informațiilor stocate în baze de date relaționale, prin intermediul limbajului de programare Java. Este un produs open-source ce realizează maparea dintre tabelele bazei de date și clase Java, asigurând astfel interacțiunea dintre sistemeul de gestiune pentru baze de date și serverul de aplicații ce implementează logica aplicației.

Mecanismul prin care sunt reprezentate legăturile dintre baza de date și clasele Java este reprezentat de fișiere XML, astfel încât nu este necesară dezvoltarea de cod sursă pentru obținerea unei astfel de funcționalități. Astfel, în situația în care apare o schimbare la nivelul bazei de date sau la nivelul claselor Java, este suficient ca aceasta să fie realizată și la nivelul fișierului XML corespunzător pentru ca legătura dintre entități să fie menținută. API-ul pus la dispoziție atât pentru asigurarea persistenței cât și pentru interogarea diferitelor informații din baza de date este flexibil și scalabil, fiind accesibil tuturor programatorilor, indiferent de nivelul de pregătire. Se oferă o abstractizare a operațiilor de la nivelul sistemului de gestiune pentru baze de date, astfel încât detaliile de implementare de la nivelul acestuia nu trebuie să fie cunoscute, interacțiunea făcându-se la nivelul obiectelor Java ce corespund unor concepte specifice afacerii sau culturii organizației respective. Pot fi modelate astfel legături oricât de complexe dintre tabelele entitate de la nivelul bazei de date. Mai mult, accesul la date se realizează optimizat, folosindu-se diferite strategii inteligente pentru interogarea informațiilor, menținând în același timp ușurința în utilizare. De asemenea, poate fi utilizat independent de un server de aplicații.

Hibernate suportă mai multe sisteme de gestiune pentru baze de date, cum ar fi Oracle, MySQL, PostgreSQL, Microsoft SQL Server, Sybase, Informix Dynamic Server, DB2, HSQL, precum și mai multe tehnologii cum ar fi Java Enterprise Edition, XDoclet Spring, plugin-uri Eclipse sau Maven.

Arhitectura Hibernate este structurată pe mai multe niveluri, astfel încât să fie încapsulate cât mai multe dintre interfețele de programare pe care le utilizează, oferind aplicației servicii legate de obiectele persistente.

1. Un obiect persistent reprezintă o entitate, reprezentată de regulă sub forma uneia (sau mai multor) tabele la nivelul bazei de date, pentru care se asigură mecanisme de stocare și de încărcare la nivelul aplicației Java relativ la sursa de date respectivă. Comunicația dintre aplicația Java și Hibernate este realizată prin intermediul serviciilor care furnizează obiecte persistente pe baza parametrilor furnizați (aceștia putând fi, la rândul lor, tot obiecte persistente).

2. Structura Hibernate este reprezentată de mai multe clase:

  • Configuration conține proprietățile din fișierele de configurare (hibernate.properties sau hibernate.cfg.xml) necesare pentru funcționarea Hibernate; o astfel de clasă are o instanță unică la nivelul aplicației Java care exploatează informații dintr-o sursă de date, obiectul având rolul de a specifica parametrii de conexiune la baza de date dar și de a realiza procesul de asociere dintre modelul relațional și modelul obiectual;
  • SessionFactory reprezintă o clasă instanțiată pentru o anumită configurație Hibernate, furnizând (pentru toate firele de execuție de la nivelul aplicației) sesiuni prin care se oferă acces propriu-zis la baza de date; întrucât este un obiect care necesită multe resurse, este utilizată o instanță unică (pentru un anumit fișier de configurare), inițializată la un moment dat și refolosită pe parcusul ciclului de viață al aplicației Java;
  • Session este utilizat pentru a obține o conexiune propriu-zisă la baza de date; întrucât este un obiect care necesită puține resurse, poate fi instanțiat de fiecare dată când este necesară interacțiunea cu sursa de date; funcționalitățile pe care le oferă includ stocarea și încărcarea unui obiect persistent; un astfel de obiect nu trebuie să fie utilizat mai mult decât este necesar, întrucât nu sunt sigure într-un mediu de execuție concurent;
  • Transaction constă dintr-o serie de operații care trebuie să fie executate în mod atomic; o astfel de funcționalitate este oferită de alte interfețe de programare cu care Hibernate interacționează, nefiind obligatorie utilizarea acestei interfețe, utilizatorii putând să aleagă să implementeze propriul mecanism prin care să asigure un comportament tranzacțional;
  • Query descrie interogări în limbajul SQL sau HQL (Hibernate Query Language) pentru a gestiona informațiile reprezentate la nivelul bazei de date; sunt implementate funcționalități pentru legarea parametrilor disponibili în momentul execuției aplicației sau pentru limitarea numărului de rezultate;
  • Criteria specifică interogări într-un limbaj orientat pe obiecte, manipulând informațiile de la nivelul bazei de date.

3. Interacțiunea cu baza de date este realizată prin intermediul mai multor interfețe de programare:

  • JDBC oferă acces la sursa de date prin intermediul unui driver de conectare, permițând ca interogarea datelor să se facă într-un mod similar ca în cazul exploatării sistemului de gestiune pentru baze de date, în limbajul nativ;
  • Java Transaction API (JTA) și Java Naming and Directory Interface (JNDI) asigură integrarea cu servere de aplicații folosite de tehnologia Java Enterprise Edition.

Raportul cu JDBC (Java Database Connectivity)

Java Database Connectivity reprezintă o interfață de programare care oferă acces la informațiile stocate într-o bază de date relațională (care respectă standardul SQL) prin intermediul unei aplicații implementată în limbajul Java, permițând execuția diferitelor interogări. Cu toate că această soluție este caracterizată prin flexibilitate, scalabilitate, eficiență și portabilititate, există o serie de avantaje și dezavantaje, comparativ cu utilizarea sistemelor de tip ORM.

AVANTAJE DEZAVANTAJE
• control asupra interogării realizate, procesarea interogării se realizează fără probleme • necesitatea de a scrie cod sursă redundant
• performanțe deosebite pentru volume de date considerabile
• adecvată unor aplicații de complexitate redusă • inadecvată unor aplicații de complexitate sporită
• sintaxă facilă, ușor de asimilat de către dezvoltatorii familiarizați cu limbajul SQL • interogările sunt specifice unui anumit tip de sistem de gestiune pentru baze de date
• programatorul trebuie să cunoască detaliile de implementare de la nivelul sistemului de gestiune pentru baze de date, nu există posibilitatea de implementare a paradigmei de proiectare MVC (Model - View - Controller)

Un sistem de mapare între modelul de date relațional și modelul de date obiectual utilizează de regulă soluții de acces la date de tipul JDBC, încapsulându-le funcționalitatea, astfel încât să nu fie necesar să se utilizeze cunoștințe legate de reprezentarea propriu-zisă a datelor. În acest fel, dezvoltatorul de poate concentra asupra implementării nivelului de logică a aplicației mai degrabă decât de a gestiona informațiile stocate în bazele de date. El nu va fi nevoit să proiecteze nici interogările într-un limbaj specific bazelor de date și nici să construiască aplicația în funcție de structura conceptuală a bazei de date, lucrând cu entități care au corespondent la nivelul fluxurilor de date și a proceselor organizaționale de la nivelul companiei. De asemenea, gestiunea tranzacțiilor (set de instrucțiuni executate atomic) și generarea cheilor sunt realizate în mod automat, astfel încât procesul de dezvoltare al unei aplicații integrate pentru întreprinderi va fi mult mai eficient.

Arhitectura Java Persistence API

JPA reprezintă un mecanism prin care entitățile care își găsesc un corespondent în logica aplicației sunt convertite în date relaționale și viceversa. Acesta descrie modul în care pot fi descrise obiectele de tip POJO (Plain Oriented Java Object) precum și modul în care pot fi gestionate relațiile dintre acestea.

Un obiect de tip Entity reprezintă o entitate persistentă, fiind utilizat pentru stocarea și încărcarea informațiilor din baze de date relaționale. Acesta deține un singur atribut (opțional) - name, denumirea entității respective. De regulă, se folosește ca adnotare pentru a indica faptul că trebuie asigurată persistența clasei în cauză, la nivelul unei tabele în baza de date.

Pachetul javax.persistence definește mai multe clase care controlează modul în care este gestionată o entitate persistentă:

  • EntityManagerFactory - produce unul sau mai multe obiecte de tipul EntityManager pe care le gestionează simultan;
  • EntityManager - reprezintă o interfață care gestioneză operațiile la nivelul obiectelor persistente Entity (furnizând metode de tipul persist(), find(), remove()), furnizând și obiecte de tip interogare (Query) - comportament de tip fabrică față de acestea;
  • EntityTransaction - gestionează operațiile de la nivelul unui obiect de tip EntityManager (relație de tip 1-la-1!!!), astfel încât acestea să fie executate atomic; metodele pe care le furnizează sunt begin(), respectiv commit() și rollback();
  • Persistence - utilizată pentru a obține instanțe de tipul EntityManagerFactory
  • Query - implementată de fiecare furnizpr JPA pentru a obține obiecte relaționale care întrunesc anumite condiții; sunt furnizate metode pentru a preciza și pentru a obține valorile unor parametri, pentru a controla numărul de rezultate întoarse, pentru a executa propriu-zis anumite interogări sau pentru a controla alte aspecte ce țin de manipularea informațiilor stocate în bazele de date relaționale și preluarea lor în aplicațiile Java.

Clasa Entity

O entitate este un obiect persistent, a cărui întreținere nu necesită numeroase resurse la nivelul aplicației Java. De regulă, clasa care o descrie corespunde unei tabele dintr-o bază de date relațională, existând echivalența dintre o instanță și o înregistrare din tabelă. Deși clasa de tip Entity este responsabilă cu operațiile pe care le pune la dispoziție obiectul persistent, pot fi implementate și alte mecanisme ajutătoare.

Persistența este asigurată prin intermediul unor atribute sau metode adnotate pentru a indica legătura cu atributele din cadrul sursei de date.

Condițiile pe care trebuie să le întrunească o clasă de tip entitate, pentru a putea descrie un obiect persistent sunt:

  1. să fie adnotată cu însemnarea javax.persistence.Entity;
  2. să definească un constructor implicit (fără argumente), cu drepturi de acces public sau protected (pe lângă acest tip de constructor mai pot exista și alți constructori);
  3. să nu folosească modificatorul final (această constrângere este valabilă inclusiv pentru atributele sau metodele persistente);
  4. în situația în care o instanță a sa este transmisă prin valoare (ca obiect detașat), trebuie să implementeze interfața Serializable;
  5. pot fi derivate din orice tip de clase (similar, clasele non-entitate pot fi derivate din clase de tip entitate);
  6. obiectele de tip persistent trebuie să aibă drepturi de acces private, implicit (package-private) sau protected, putând fi accesate numai prin intermediul unor metode.

O clasă de tip Entity mai este cunoscută și sub denumirea de POJO (Plain Oriented Java Object) și trebuie să respecte formatul oricărui obiect JavaBeans.

Starea unei entități poate fi obținută prin intermediul câmpurilor și a proprietăților sale, accestea fiind accesibile numai prin intermediul unor metode. O clasă poate folosi atât câmpuri, cât și proprietăți.

Un câmp poate fi accesat la nivelul clasei în timp ce o proprietate poate fi accesată prin intermediul unor metode de tip getter. Adnotările care descriu legătura cu atributele de la nivelul tabelei din baza de date relațională sunt plasate în cadrul obiectelor / metodelor asupra cărora există drepturi de acces suficiente.

Persistența unui câmp este asigurată atâta vreme cât nu are modificatorul transient sau nu este adnotat cu însemnarea javax.persistence.Transient.

Pentru o proprietate property trebuie să se asigure:

  • o metodă setter: public void setProperty(Type property);
  • o metodă getter:
    • pentru tipuri de date boolean: public boolean isProperty();
    • pentru tipuri de date non-boolean public Type getProperty().

Se pot folosi tipuri de date de tip primitiv, java.lang.String, alte tipuri serializabile (wrappere pentru tipuri de date primitive, java.math.BigInteger, java.math.Big.Decimal, java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Time, java.sql.TimeStamp, tipuri de date definite de utilizator, byte[], Byte[], char[], Character[]), tipuri enumerate, alte entități (sau colecții de entități) precum și clase imbricate.

Identificatori Unici ai Entităților

Orice entitate trebuie să fie identificată prin intermediul unui obiect unic, cunoscut și sub denumirea de cheie primară, prin care se poate distinge fiecare instanță în parte.

O cheie primară poată fi simplă sau compusă:

  • cheile primare simple folosesc adnotarea javax.persistence.Id pentru a identifica fie câmpul, fie proprietatea care îndeplinește acest rol; ele pot avea doar următoarele tipuri:
    1. tipuri de date primitive sau wrappere ale acestora;
    2. java.lang.String;
    3. java.util.Date (pentru câmpurile sau proprietățile de tip @Temporal) / java.sql.Date;
    4. java.math.BigInteger / java.math.BigDecimal.
Nu trebuie folosit niciodată un număr zecimal pe post de cheie primară!!!
  • cheile primare compuse trebuie definite într-o clasă distinctă, care alcătuiește identificatorul unic pentru entitatea persistentă respectivă, în acest caz folosindu-se adnotările javax.persistence.EmbeddedId, respectiv javax.persistence.IdClass; clasa de tip cheie primară trebuie să îndeplinească următoarele condiții:
    1. să aibă precizat dreptul de acces public;
    2. să aibă drepturile de acces pentru câmpuri public iar pentru proprietăți protected;
    3. să implementeze un constructor implicit (fără parametri);
    4. să implementeze metodele hasCode() și equals(Object other);
    5. să fie serializabilă;
    6. să aibă câmpurile sau proprietățile asociate unei clase entitate (caz în care denumirea li tipurile de date trebuie să corespundă întocmai) sau să facă parte dintr-o clasă imbricată.

Modelarea Relațiilor dintre Entități

Referințe

Relațiile dintre entități pot avea mai multe multiplicități:

  • 1 la 1 (o instanță a entității este legată la o singură instanță a celeilalte entități) - folosesc adnotarea javax.persistence.OneToOne pentru câmpul sau proprietatea corespunzătoare;
  • 1 la mai mulți (o instanță a entității este legată la mai multe instanțe a celeilalte entități) - folosesc adnotarea javax.persistence.OneToMany pentru câmpul sau proprietatea corespunzătoare;
  • mai mulți la 1 (mai multe instanțe ale entității sunt legate la o singură instanță a celeilalte entități) - folosesc adnotarea javax.persistence.ManyToOne pentru câmpul sau proprietatea corespunzătoare;
  • mai mulți la mai mulți (mai multe instanțe ale entității sunt legate la mai multe instanțe ale celeilalte entități) - folosesc adnotarea javax.persistence.ManyToMany pentru câmpul sau proprietatea corespunzătoare.

Frecvent, în astfel de situații sunt utilizate colecții, iar acestea trebuie să implementeze tipurile de date standard din limbajul de programare Java pentru acest tip (indiferent de implementarea propriu-zisă, metodele trebuie să utilizeze parametri și valori întoarse de acest tip!!!):

Pot fi utilizate și variantele acestor clase folosind parametrizare cu tipuri de date generice.

Dacă un câmp sau o proprietate reprezintă o colecție de tipuri primitive sau de clase imbricate, se poate folosi însemnarea javax.persistence.ElementCollection, având atributele:

  • targetClass - indică denumirea tipului primitiv sau al clasei imbricate (opțional dacă se folosește parametrizare cu tipuri de date generice);
  • fetch - precizează modul în care va fi preluată colecția (leneș sau harnic) - opțional:
    • javax.persistence.FetchType.LAZY;
    • javax.persistence.FetchType.EAGER.

Dacă un câmp sau o proprietate reprezintă o colecție de entități persistente sau relații, se poate folosi tipul java.util.Map, acesta fiind asocierea dintre o cheie și o valoare, ce respectă următoarele condiții:

  • cheia și valoarea pot fi un tip primitiv, o clasă imbricată sau o entitate;
  • în privința unei chei
    • dacă are un tip de date primitiv, se folosește însemnarea javax.persistence.MapKeyColumn pentru a se indica atributul de tip cheie (implicit, denumirea acestuia are forma <RELATIONSHIP-FIELD/PROPERTY-NAME>_KEY;
    • dacă este o entitate, se folosește una dintre însemnările javax.persistence.MapKeyJoinColumn / javax.persistence.MapKeyJoinColumns, în funcție de numărul de câmpuri după care este realizată asocierea (în situația în care o astfel de însemnare nu este precizată, se va folosi denumirea implicită care are forma <RELATIONSHIP-FIELD/PROPERTY-NAME>_KEY);
Dacă este cheia primară sau un câmp sau proprietate a unei entități persistente, se poate folosi însemnarea javax.persistence.MapKey (dar nu în asociere cu @MapKeyClass !!!).
  • în privința unei valori:
    • dacă are un tip de date primitiv sau o clasă imbricată, se folosește însemnarea @ElementCollection;
    • dacă este o entitate, se folosesc însemnările @OneToMany (pentru relațiile unidirecționale @JoinColumn) sau @ManyToMany, realizându-se în mod automat asocierea cu tabela referită.
În cazul relațiilor bidirecționale dintre entități, tipul de date java.util.Map va fi folosit într-o singură entitate.

În situația în care nu se folosește parametrizarea cu tipuri de date generice, pentru cheie, respectiv pentru valoare se vor folosi adnotarea javax.persistence.MapKeyClass, respectiv se va preciza atributul targetClass al adnotării @ElementCollection.

Direcția unei relații poate fi:

  • unidirecțională, în sensul că o singură entitate are un câmp sau o proprietate care referă un altul / o alta în entitatea asociată;
  • bidirecțională, în sensul că fiecare entitate dispune de un câmp sau o proprietate care referă un altul / o alta în entitatea asociată (și viceversa), prin intermediul căruia poate fi accesat “corespondentul” său; regulile care trebuie respectate în acest sens sunt:
    1. partea care referă dintr-o relație trebuie să își refere perechea folosind una din adnotările @OneToOne, @OneToMany, @ManyToMany împreună cu atributul mappedBy care precizează câmpul / proprietatea din partea referită;
    2. partea referită dintr-o relație (având multiplicitate multiplă)NU trebuie să folosească elementul mappedBy;
    3. pentru relații de tipul 1 la 1, entitatea care deține relația este cea care conține cheia străină;
    4. pentru relații de tipul mai mulți la mai mulți, oricare dintre părți poate fi entitatea care deține relația.

Diferitele tipuri de direcții dictează interogările care pot fi realizate cu entitățile respective.

Entitățile între care există relații sunt de fapt dependente unele de altele, în acest sens fiind necesar să se definească un comportament în privința entității copil atunci când se produce o schimbare la nivelul entității părinte. Acestea sunt controlate prin intermediul adnotării javax.persistence.CascadeType.

OPERAȚIE DESCRIERE
ALL alias pentru cascade={DETACH, MERGE, PERSIST, REFRESH, REMOVE}; toate operațiile vor fi aplicate pe entitatea copil referită de entitatea părinte la nivelul căreia s-a produs o schimbare
DETACH dacă entitatea părinte este detașată de la contextul de persistență, și entitatea copil va fi detașată
MERGE dacă entitatea părinte este atașată la contextul de persistență, și entitatea copil va fi atașată
PERSIST dacă entitatea părinte este stocată în contextul de persistență, și entitatea copil va fi stocată
REFRESH dacă entitatea părinte este încărcată în contextul de persistență, și entitatea copil va fi încărcată
REMOVE dacă entitatea părinte este ștearsă din contextul de persistență, și entitatea copil va fi ștearsă; pentru a se evita ca unele entități să rămână fără corespondent, în cazul relațiilor de tip @OneToOne și @OneToMany, se recomandă să se folosească atributul orphanRemoval

Moștenire

Între entități se poate stabili și relația de moștenire, o entitate putând fi derivată dintr-o clasă non-entitate la fel cum și o clasă non-entitate poate moșteni o clasă entitate.

În situația în care clasa moștenită nu este o entitate, câmpurile sau proprietățile sale persistente pot fi totuși accesate dacă folosesc adnotarea javax.persistence.MappedSuperclass. O astfel de abordare este folosită în situația în care mai multe entități au unele câmpuri sau proprietăți comune. Totuși, clasele cu adnotarea @MappedSuperclass nu pot fi interogate (nu pot fi folosite ca parametri ai metodelor din clasele EntityManager sau Query) și nici nu pot fi referite ca relații ale unor entități (relațiile fiind definite în cadrul entităților care le moștenesc). Acest lucru se datorează faptului că aceste clase nu au tabele corespondente în sursa de date.

În cazul în care clasa moștenită nu este o entitate și nici nu definește adnotarea @MappedSuperclass, toate câmpurile sau atributele sale nu vor fi persistente, chiar dacă pot fi accesate la nivel obiectual.

O clasă entitate pot fi și abstractă. Același lucru este valabil și pentru clasele non-entitate moștenite de clasele entitate. În momentul în care aceasta este implicată în cadrul unei interogări, vor fi utilizate toate instanțele efective ale clasei abstracte.

Relațiile de tip moștenire dintre entități pot fi mapate la nivelul sursei de date prin intermediul unei strategii, indicate prin adnotarea javax.persistence.Inheritance plasată la nivelul clasei rădăcină din cadrul ierarhiei. Astfel, atributul strategy trebuie să aibă una din valorile tipului enumerat javax.persistence.InheritanceType:

  1. SINGLE_TABLE (implicit): un singur tabel pentru întreaga ierarhie de clase;
  2. TABLE_PER_CLASS: un singur tabel pentru o clasă (concretă = care poate fi instanțiată);
  3. JOINED: câmpurile și proprietățile specifice unei clase moștenite corespund unei tabele diferite decât câmpurile și proprietățile unei clase de bază (comune).

1. În cazul strategiei în care un singur tabel este mapat pentru întreaga ierarhie de clase (corespunzătoare valorii InteritanceType.SINGLE_CLASS) trebuie indicată o coloană discriminator conținând o valoare ce identifică subclasa din care face parte instanța pentru care se asigură persistența la nivelul înregistrării din baza de date.

O coloană discriminator poate fi specificată folosind adnotarea javax.persistence.DiscriminatorColumn la nivelul clasei care se găsește la baza ierarhiei respective. Aceasta definește mai multe atribute:

  • name (de tip String), opțional, indică denumirea coloanei care va fi utilizată pe post de discriminator; valoarea implicită este DTYPE;
  • discriminatorType (de tip DiscriminatorType), opțional, descrie tipul coloanei care va fi utilizatp pe post de discriminator; valorile posibile sunt indicate de tipul enumerat DiscriminatorType: STRING (implicit), CHAR, INTEGER
  • columnDefinition (de tip String), opțional, reprezintă codul SQL care va fi utilizat în momentul în care se creează o coloană care va fi utilizată pe post de discriminator; valoarea implicită depinde de implementarea din furnizorul JPA;
  • length (de tip String), opțional, definește lungimea coloanei pentru tipul de date șir de caractere (fiind ignorat pentru alte tipuri de atribute); valoarea implicită este 31.

Totodată, adnotarea javax.persistence.DiscriminatorValue poate fi utilizată pentru a specifica valoarea din cadrul coloanei discriminator pentru fiecare entitate din ierarhia de clase. Ea poate fi folosită numai pentru clase concrete (care pot fi instanțiate). Dacă o astfel de adnotare nu este specificată pe o entitate din ierarhia de clase care utilizează o coloană discriminator, furnizorul JPA va indica o valoare implicită (de regulă, este denumirea entității).

O restricție care trebuie avută în vedere este faptul că acele coloane care conțin starea subclaselor trebuie să poată avea valoarea NULL.

Această strategie oferă suport pentru relații de tip polimorfic între entități și interogări care acoperă întreaga ierarhie de clase.

2. În cazul strategiei în care fiecare clasă este asociat unei tabele distincte în cadrul bazei de date (corespunzătoare valorii InteritanceType.TABLE_PER_CLASS), toate câmpurile sau proprietățile clasei (inclusiv cele moștenite) au un corespondent la nivelul tabelei din cadrul sursei de date.

Această strategie nu oferă suport pentru relații de tip polimorfic între entități (trebuie folosite interogări distincte pentru fiecare subclasă, acestea putând fi ulterior concatenate folosind clauza UNION). Din acest motiv, o astfel de strategie este opțională, nefiind necesar ca furnizorul JPA să o implementeze.

3. În cazul strategiei în care câmpurile și proprietățile specifice unei clase moștenite corespund unei tabele diferite decât câmpurile și proprietățile unei clase de bază (corespunzătoare valorii InteritanceType.JOINED), se definește un tabel pentru clasa aflată la rădăcina ierarhiei și alte tabele pentru subclase, acestea conținând însă numai câmpurile și proprietățile specifice ale acestora. Astfel, clasele derivate conțin câmpuri sau atribute reprezentând cheile primare care sunt în același timp chei străine cptre clasa de bază.

Unii furnizori JPA necesitp să se precizeze o coloană de tip discriminator care corespunde entității aflate la baza ierarhiei atunci când se utilizează această strategie, aceasta fiind o cerință ce trebuie implementată fie prin structura bazei de date fie prin adnotarea @DiscriminatorColumn.

Această strategie oferă suport pentru relații de tip polimorfic, fiind necesar însă să se realizeze una sau mai multe operații de asociere atunci când sunt instanțiate clasele derivate sau atunci când trebuie realizate interogări care acoperă întreaga ierarhie, ceea ce poate afecta performațele în cazul unor ierarhii mai extinse.

Clase Imbricate

O clasă imbricată este utilizată pentru a reprezenta starea unei entități, fără a avea o identitate autonomă în privința persistenței (pe care o preiau de la clasa în care sunt incluse și în cadrul cărora există). Ele se supun acelorași reguli ca și entitățile persistente, fiind adnotate cu însemnarea javax.persistence.Embeddable. Astfel, ele pot conține, la rândul lor, câmpuri sau proprietăți de tip primitiv, entitate (situație în care direcția relației este unidirecțională, dintspre entitatea referită către entitatea ce conține clasa imbricată), clasă impricată sau colecții ale acestora.

O entitate poate avea un câmp / proprietate de tip clasă imbricată, respectiv colecție de clase imbricate. Acestea pot fi adnotate cu însemnarea javax.persistence.Embedded (fără ca acest lucru să fie obligatoriu).

Validarea Entităților

O entitate persistentă poate fi validată prin intermediul Java API for JavaBeans Validation (Bean Validation), integrat în cadrul containerelor Java Enterprise Edition. Constrângerile pot fi aplicate și asupra claselor imbricate sau a claselor părinte referite. Implicit, procesul de validare va fi realizat asupra câmpurilor sau proprietăților adnotate după evenimentele PrePersist, PreUpdate, respectiv PreRemove ale ciclului de viață. Pot fi utilizate constrângeri implicite - definite în pachetul javax.validation.constraints sau constrângeri definite de utilizator (care pot folosi combinații ale constrângerilor implicite sau pot defini alte constrângeri). O constrângere are asociată o clasă care validează valoarea câmpului sau a proprietății. Frecvent, sunt impuse constrângeri asupra formatului pe care îl pot lua câmpurile sau proprietățile, fiind folosite în acest sens expresii regulate. Și în cazul validărilor se folosește aceeași strategie, acestea fiind definite la nivelul obiectelor pentru câmpuri, respectiv la nivelul metodelor de tip getter pentru proprietăți.

Clasele EntityManagerFactory / EntityManager

Entitățile sunt gestionate prin intermediul unor obiecte speciale, instanțe ale clasei javax.persistence.EntityManager. Acestea sunt asociate unui context de persistență, reprezentat de o colecție de entități gestionate care există într-o anumită sursă de date. Un context de persistență definește domeniul de vizibilitate în cadrul căruia entitățile gestionate sunt adăugate, modificate, șterse sau identificate pe baza cheii primare, existând de asemenea posibilitatea de a se executa interogări care le utilizează. În acest sens, interfața EntityManager definește metode care interacționează cu contextul de persistență.

Tipuri de Obiecte EntityManager

Obiectele EntityManager pot fi gestionate fie de un container (al unui server de aplicații), fie de o aplicație propriu-zisă.

1. În cazul unui obiect EntityManager gestionat de un container (al unui server de aplicații), contextul de persistență este propagat în mod automat către toate componentele aplicației care îl folosesc în cadrul unei unice tranzacții JTA (Java Transaction API). De asemenea, containerul serverului de aplicații este responsabil și de ciclul de viață al obiectelor EntityManager.

O tranzacție JTA implică de regulă apeluri între componentele aplicației care trebuie să acceseze unicul context de persistență, fapt ce poate fi realizat prin injectarea acestuia prin intermediul adnotării javax.persistence.PersistenceContext.

@PersistenceContext
EntityManager entityManager;

Un context de persistență este propagat împreună cu tranzacția JTA curentă, astfel încât apelurile realizate prin intermediul obiectului EntityManager vor fi realizate în cadrul acesteia. Astfel, nu mai este necesar ca diferitele componente ale unei aplicații să transmită referințe ale acestui obiect atunci când doresc ca toate operațiile să facă parte din cadrul aceleiași tranzacții.

2. În cazul unui obiect EntityManager gestionat de aplicația propriu-zisă, contextul de persistență nu este propagat la nivelul componentelor. Totodată, ciclul de viață al acestuia trebuie să fie tratat explicit.

O astfel de abordare este utilizată atunci când:

  • aplicațiile au nevoie să acceseze un context de persistență care nu este propagat împreună cu tranzacția JTA între diferite obiecte EntityManager în cadrul unei unități de persistență;
  • injectarea nu poate fi realizată datorită constrângerilor legate de concurență (un obiect EntityManager nu este sigur atunci când se partajat de mai multe fire de execuție).

Fiecare obiect EntityManager funcționează în cadrul unui context de persistență autonom, izolat. Într-o astfel de situație, obiectele de tip EntityManager (precum și contextul de persistență asociat) trebuie să fie create și distruse explicit de către aplicație. Interfața EntityManagerFactory furnizează metode pentru gestiunea obiectelor de tip EntityManager, fiind în același timp sigură la utilizarea în cadrul unui mediu concurent, partajat de mai multe fire de execuție. O astfel de instanță poate fi obținută prin folosirea metodei createEntityManagerFactory() din clasa javax.persistence.Persistence.

EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("02-BookStore-JPA");
Obținerea unui obiect de tip EntityManagerFactory poate fi realizată și prin injectarea unei unități de persistență (prin folosirea adnotării javax.persistence.PersistenceUnit).
@PersistenceUnit
EntityManagerFactory entityManagerFactory;

Prin intermediul acestui clase de tip “fabrică” pot fi construite obiecte de tip EntityManager (în acest sens fiind utilizată metoda createEntityManager()).

EntityManager entityManager = entityManagerFactory.createEntityManager();

În cazul obiectelor EntityManager gestionate de aplicația propriu-zisă, tranzacția JTA nu este propagată în mod automat, astfel încât aplicațiile trebuie să obțină manual acces către aceasta. Interfața javax.persistence.EntityTransaction definește metode pentru precizarea informațiilor de demarcare atunci când sunt realizate operații asupra unor entități persistente.

EntityTransaction entityTransaction = entityManager.getTransaction();
O funcționalitate similară este definită de clasa javax.transaction.Transaction, aceasta putând fi injectată la nivelul codului sursă prin intermediul adnotării javax.annotation.Resource:
@Resource
UserTransaction userTransaction;

De regulă, înainte de a realiza orice fel de operație asupra unei entități persistente, trebuie marcat acest lucru la nivelul unei tranzacții. De asemenea, după ce se realizează una sau mai multe operații asupra unei entități persistente, este necesar ca toate modificările să fie transmise la nivelul sursei de date prin metoda commit() sau - în cazul în care s-a produs o eroare -, acestea trebuie să fie ignorate prin metoda rollback().

O tranzacție nu se poate termina decât printr-o operație de tip commit sau rollback.
try {
  entityTransaction.begin();
  // do something
  entityTransaction.commit();
} catch (Exception exception) {
  entityTransaction.rollback();
}

Funcționalități Implementate de Interfața EntityManager

În cazul unei entități persistente, ciclul de viață este definit prin intermediul stării în care aceasta se găsește:

  • nou denotă o entitate care a fost creată dar nu are o identitate persistentă (întrucât nu a fost stocată la nivelul sursei de date), nefiind asociată unui context persistent;
  • gestionat reprezintă o entitate care deține o identitate persistentă și care este asociată unui context persistent;
  • detașat se referă la o entitate care conține o identitate persistentă, dar nu este asociată unui context persistent;
  • eliminat indică o entitate care este caracterizată printr-o identitate persistentă, este asociată unui context persistent, fiind marcată pentru ștergere la nivelul sursei de date.

O entitate persistentă este gestionată prin intermediul metodelor puse la dispoziție de interfața EntityManager. Funcționalitatea oferită este de tip CRUD (Create, Read, Update, Delete).

Adăugare

Trecerea din starea nou în starea gestionat se face printr-un apel al metodei persist() invocată fie pe o instanță a clasei entitate respective, fie pe un obiect entitate cu care se află într-o relație de legătură (și în care atributul cascade al adnotării respective are valoarea ALL sau PERSIST). Stocarea propriu-zisă la nivelul sursei de date va fi realizată însă numai în momentul în care tranzacția din care face parte este terminată prin operația commit, în situația în care nu se produc erori.

EntityTransaction entityTransaction = entityManager.getTransaction();
Book book = new Book();
// set book properties
try {
  entityTransaction.begin();
  entityManager.persist(book);
  entityTransaction.commit();
} catch (Exception exception) {
  entityTransaction.rollback();
}
În cazul în care entitatea are o altă stare decât nou, sunt definite următoarele comportamente:
  • starea gestionat: metoda este ignorată, dar vor fi invocate metodele persist() ale obiectelor cu care se află în relație, dacă este cazul (în funcție de valoarea parametrului cascade);
  • starea detașat: se generează o excepție de tipul IllegalArgumentException în momentul în care tranzacția din care face parte este terminată prin operația commit (sau aceasta eșuează fără eroare);
  • starea eliminat: entitatea este trecută în starea gestionat.
Căutare & Modificare

O entitate persistentă poate fi căutată la nivelul sursei de date folosind una dintre variantele metodei find() din clasa EntityManager:

Se observă că metoda este supraîncărcată, pentru căutare fiind necesar să se indice, în mod obligatoriu, tipul de date al clasei respective precum și valoarea cheii primare. Opțional, se poate preciza o listă de proprietăți sub formă de perechi (cheie, valoare) precum și nivelul la care este necesar să se blocheze tabela (scriere, citire) pentru a se evita obținerea de anomalii.

EntityTransaction entityTransaction = entityManager.getTransaction();
try {
  entityTransaction.begin();
  Book book = entityManager.find(Book.class, bookID);
  // set book attributes
  entityManager.persist(book);
  entityTransaction.commit();
} catch (Exception exception) {
  entityTransaction.rollback();
}
Ștergere

Trecerea din starea gestionat în starea eliminat se face printr-un apel al metodei remove() invocată fie pe o instanță a clasei entitate respective, fie pe un obiect entitate cu care se află într-o relație de legătură (și în care atributul cascade al adnotării respective are valoarea ALL sau REMOVE). Ștergerea propriu-zisă la nivelul sursei de date va fi realizată însă numai în momentul în care tranzacția din care face parte este terminată prin operația commit, în situația în care nu se produc erori sau în cazul în care este invocată metoda flush().

EntityTransaction entityTransaction = entityManager.getTransaction();
try {
  entityTransaction.begin();
  Book book = entityManager.find(Book.class, bookID);
  entityManager.remove(book);
  entityTransaction.commit();
} catch (Exception exception) {
  entityTransaction.rollback();
}
În cazul în care entitatea are o altă stare decât nou, sunt definite următoarele comportamente:
  • starea nou: metoda este ignorată, dar vor fi invocate metodele remove() ale obiectelor cu care se află în relație, dacă este cazul (în funcție de valoarea parametrului cascade);
  • starea detașat: se generează o excepție de tipul IllegalArgumentException în momentul în care tranzacția din care face parte este terminată prin operația commit (sau aceasta eșuează fără eroare);
  • starea eliminat: metoda este ignorată.
Sincronizare

Sincronizarea unei entități la nivelul sursei de date se produce:

  • în momentul în care tranzacția din care face parte este terminată prin operația commit, în situația în care nu se produc erori;
  • atunci când se apelează metoda flush() a clasei EntityManager.
Sincronizarea va fi realizată și la nivelul entităților cu care obiectul curent se află într-o relație, în situația în care atributul cascade al adnotării care descrie tipul de asociere are valoarea ALL sau PERSIST.

Clasa EntityTransaction

Orice operație realizată folosind entități persistente trebuie să fie invocată în cadrul unei tranzacții, reprezentată de interfața EntityTransaction. O tranzacție oferă un nivel de izolare, asigurând faptul că toate operațiile realizate în cadrul său vor fi transferate cu succes la nivelul sursei de date (commit) sau, în cazul în care se produce o eroare, se revine la starea bazei de date de dinainte de tentativa sa de execuție (rollback).

O tranzacție pune la dispoziție metodele pentru începerea (begin()), respectiv terminarea acesteia (commit(), respectiv rollback()).

Suplimentar, interfața EntityTransaction pune la dispoziția utilizatorilor metodele:

  • isActive() - verifică dacă tranzacția se află în progres;
  • setRollbackOnly() / getRollbackOnly() - gestionează proprietatea rollbackOnly a unei tranzacții prin care se indică faptul că singurul rezultat posibil în urma execuției acesteia este de revenire la starea inițială a sursei de date.

Clasa Query

În Java Persistence API, interogările entităților persistente pot fi realizate prin două modalități:

  • limbajul JPQL (Java Persistence Query Language), cu o sintaxă asemănătoare standardului SQL; astfel de interogări sunt mai lizibile și au dimensiuni mai mici; de asemenea, pot fi înțelese și asimilate facil de persoanele familiarizate cu aceste limbaje; există posibilitatea de a defini interogări la nivelul entităților persistente (fie printr-o adnotare, fie într-un descriptor al aplicației) acestea putând fi referite ulterior prin intermediul unei denumiri; astfel de interogări nu implică verificarea tipurilor de date, astfel încât trebuie realizate întotdeauna operațiile de conversie explicită, ceea ce presupune că pot fi generate posibile excepții în momentul în care aplicația este rulată;
  • Criteria API, folosindu-se limbajul de programare Java; aceste interogări au dimensiuni mai mari (trebuie instanțiate mai multe obiecte pe care se realizează anumite operații înainte de a le transmite către obiectul de gestiune al entităților); întrucât este vorba de un API Java, poate fi înțeles și asimilat cu ușurință de persoanele familiarizate cu acest limbaj de programare; interogările sunt definite la nivelul de logică a aplicației, având o performanță superioară interogărilor dinamice care ar trebui parsate de fiecare dată când sunt invocate; ele implică verificarea tipurilor de date astfel încât nu sunt necesare operații de conversie explicită, evitându-se posibilitatea de generare a excepțiilor în momentul în care aplicația este rulată.

JPL (Java Persistence Query Language)

Limbajul JPL (Java Persistence Query Language) permite definirea de interogări pentru entități persistente, independente de sursa de date cu care interacționează.

În acest scop, utilizează o schemă abstractă a entității persistente (modelul de date propriu-zis precum și relațiile cu alte entități persistente) precum și operatori sau expresii aplicate acesteia. O schemă abstractă reprezintă o abstractizare a schemei de relație (fiind formată din entitățile persistente împreună cu starea lor precum și legăturile dintre acestea). Limbajul JPL convertește interogările descrise la nivelul schemei de relație abstractizată în interogări executate în cadrul sursei de date la care sunt mapate entitățile respective.

Vizibilitatea unei interogări include toate schemele abstracte ale entităților care se află în legătură în cadrul unei unități de persistență.

Noțiunea de tip al schemei abstracte se referă la tipul de date al unui câmp sau al unei proprietăți a entității persistente în cadrul schemei abstracte. Astfel, fiecare câmp sau proprietate persistentă dintr-o entitate au un corespondent de același tip în schema abstractă. Tipul entității persistente din schema abstractă se obține pe baza clasei entitate și a informațiilor despre metadate oferite de adnotările din limbajul de programare Java.

Operația de navigare implică traversarea relațiilor dintre entități în cadrul unei expresii din limbajul de interogare.

O expresie de cale oferă accesul către starea unei entități sau către un câmp / o proprietate de tip relație.

Un câmp de tip stare reprezintă un atribut persistent al unei entități.

Un câmp de tip relație reprezintă un atribut persistent al unei entități al cărui tip este același cu al schemei abstracte a entității referite.

Tipuri de Interogări

În limbajul JPL pot fi utilizate două categorii de interogări:

  • interogări anonime, obținute prin intermediul metodei createQuery() din interfața EntityManager și folosite pentru a defini interogări dinamice în cadrul nivelului de logică a aplicației;
    public List<Book> getBooksWithTitleOrSubtitle(String pattern) {
      return entityManager.createQuery("SELECT b.title, b.subtitle, b.edition, b.printing_year FROM Book b WHERE b.title LIKE :pattern OR b.subtitle LIKE :pattern")
        .setParameter("pattern", pattern)
        .getResultList();
  • interogări cu nume, obținute prin intermediul metodei createNamedQuery() din interfața EntityManager pentru obiectele adnotate cu însemnarea javax.persistence.NamedQuery (care definește atributele name ce indică denumirea interogării cu nume și query reprezentând interogarea propriu-zisă); astfel de interogări sunt statice și sunt definite la nivelul metadatelor.
    @NamedQuery(
      name="getBooksWithTitleOrSubtitle",
      query="SELECT b.title, b.subtitle, b.edition, b.printing_year FROM Book b WHERE b.title LIKE ?1 OR b.subtitle LIKE ?2"
    )
    public List<Book> getBooksWithTitleOrSubtitle(String pattern) {
      return entityManager.createNamedQuery("getBooksWithTitleOrSubtitle")
        .setParameter(1, pattern)
        .setParameter(2, pattern)
        .getResultList();

Așa cum se poate observa, în cadrul interogărilor (anonime sau cu nume) pot fi utilizați parametri care sunt specificați:

  • prin denumirea lor (aceasta fiind prefixată în cadrul interogărilor prin caracterul :);
  • prin poziția lor (aceasta fiind formată din caracterul ? urmat de indexul propriu-zis, numerotarea făcându-se de la 1).

Pentru a se putea obține lista de rezultate, este necesar să se specifice valorile parametrilor, folosind metodele puse la dispozițede clasa javax.persistence.Query:

Sintaxa JPA

În sintaxa JPA sunt definite interogări pentru:

  • obținerea de informații (de tip SELECT);
  • actualizarea de informații (de tip UPDATE sau DELETE).

Interogări de tip SELECT

O interogare de tip SELECT este formată din 6 clauze: 1. SELECT: indică tipurile de obiecte sau valorile furnizate; tipurile de obiecte ale clauzei SELECT sunt determinate de tipurile expresiilor pe care le include (în situația în care există mai multe expresii, rezultatul are forma Object[], ordinea elementelor în tablou fiind aceeași cu cea în care acestea apar în clauza SELECT).

O clauză SELECT nu poate specifica o valoare ce conține o colecție (spre exemplu, o relație către o tabelă în care multiplicitatea este 1-la-mai mulți). Într-o astfel de situație, este necesar să se realizeze o operație de asociere folosind cuvintele-cheie IN sau JOIN.

În cazul în care se folosesc funcții agregate, rezultatele au un tip de date specific acestora:

FUNCȚIE AGREGATĂ TIP REZULTAT FUNCȚIONALITATE
COUNT Long număr total de rezultate
AVG Double valoarea medie a atributelor
MIN tipul de date al atributului valoarea minimă a atributelor
MAX tipul de date al atributului valoarea maximă a atributelor
SUM Long (pentru atribute de tip intreg) suma atributelor
Double (pentru atribute de tip zecimal)
BigInteger (pentru atribute BigInteger)
BigDecimal (pentru atribute BigDecimal)

Dacă setul de date nu conține valori pentru care să se aplice funcția agregată, comportamentul este următorul:

  1. metoda COUNT întoarce valoarea 0;
  2. metodele AVG, MIN, MAX, SUM întorc valoarea null.

Poate fi utilizat cuvântul-cheie DISTINCT pentru a fi omise valorile duplicate, o astfel de abordare fiind utilă atunci când se interoghează un tip de date java.util.Collection care permite valori dupolicate.

În JPQL este permis ca în cadrul unei clauze SELECT să fie incluse expresii de tip constructor, astfel încât să se furnizeze instanțe noi ale unui obiect pentru înregistrările respective. În acest sens, se va folosi operatorul NEW urmat de denumirea (completă) a clasei și de parametrii acesteia:

SELECT NEW ro.pub.cs.aipi.lab02.entities.Book(b.edition, b.printing_year)
FROM book b
WHERE b.title = 'Thinking in Java'

2. FROM: precizează domeniul de viziblitate al interogării, indicat prin variabile de identificare (ce pot ulterior referite în clauzele SELECT și WHERE), acestea putând fi:

  • denumirea unei scheme abstracte a unei entități;
  • un element al unei relații care conține o singură valoare;
  • un element al unei relații care conține mai multe valori;
  • un membru al unei colecții din cadrul unei relații 1-la-mai mulți;

Un identificator este o secvență de unul sau mai multe caractere valide (literă, cifră, $, _) care nu este prefixat o cifră. Caracterul ? este rezervat în limbajul de interogare și nu poate fi utilizat. Se face distincția între minuscule și majuscule, cu excepția cuvintelor-cheie (ABS, ALL, AND, ANY, AS, ASC, AVG, BETWEEN, BIT_LENGTH, BOTH, BY, CASE, CHAR_LENGTH, CHARACTER_LENGTH, CLASS, COALESCE, CONCAT, COUNT, CURRENT_DATE, CURRENT_TIMESTAMP, DELETE, DESC, DISTINCT, ELSE, EMPTY, END, ENTRY, ESCAPE, EXISTS, FALSE, FETCH, FROM, GROUP, HAVING, IN, INDEX, INNER, IS, JOIN, KEY, LEADING, LEFT, LENGTH, LIKE, LOCATE, LOWER, MAX, MEMBER, MIN, MOD, NEW, NOT, NULL, NULLIF, OBJECT, OF, OR, ORDER, OUTER, POSITION, SELECT, SET, SIZE, SOME, SQRT, SUBSTRING, SUM, THEN, TRAILING, TRIM, TRUE, TYPE, UNKNOWN, UPDATE, UPPER, VALUE, WHEN, WHERE) și a variabilelor de identificare. De asemenea, nu se recomandă să se utilizeze un cuvânt cheie din standardul SQL pe post de identificator.

O variabilă de identificare este un identificator din cadrul clauzei FROM, singurul loc unde este permisă definirea lor. În cazul denumiri acestora, NU se face distincția între minuscule și majuscule. O altă constrângere este distincția față de denumirile altor entități din cadrul unității de persistente.

Delimitatorul folosit în cazul în care sunt folosite mai multe variabile de identificare este caracterul ,.

O variabilă de identificare poate referi o altă variabilă de identificare, dacă aceasta a fost definitită anterior (evaluarea făcându-se de la stânga la dreapta). De asemenea, aceasta poate influența rezultatele furnizate, chiar și atunci când nu este inclusă în clauza WHERE (frecvent, acest lucru se întâmplă atunci când sunt folosite operații de asociere pe baza unui atribut de tip relație).

Spre exemplu, interogarea:

SELECT b
FROM Book AS b, IN (b.categories) AS bc

va furniza acele volume pentru care au fost definite categorii, adică:

SELECT b
FROM Book b
WHERE b.categories IS NOT EMPTY

O variabilă de identificare este o referință către o singură valoare al cărei tip este dat de expresia utilizată în declarație. Există două tipuri de declarații:

  • variabile de interval (eng. range variable) - acestea având tipul unei scheme abstracte pe care o poate parcurge; sunt definite ca alias-uri pentru o entitate abstractă, folosirea cuvântului cheie AS fiind opțională.
    SELECT b
    FROM Book b
    SELECT b
    FROM Book AS b
  • membri ai unor colecții (eng. collection member) sunt obținuți de regulă din referințele către alte entități în care multiplicitatea este 1-la-mai mulți; într-o astfel de situație, este obligatoriu să se folosească cuvântul-cheie IN.
    SELECT b
    FROM Book AS b, IN (b.categories) AS bc

O funcționalitate similară cu termenul IN o are cuvântul-cheie JOIN (care poate fi folosit împreună cu termenul INNER sau nu, rezultatul furnizat fiind același).

SELECT b
FROM Book AS b JOIN b.categories AS bc

JPQL oferă posibilitatea de a folosi și operații de tip LEFT JOIN (care poate fi folosit împreună cu termenul OUTER sau nu, rezultatul furnizat fiind același).În această situație, nu este necesar să se mai precizeze și condiția de asociere.

În cazul operației de JOIN, entitățile persistente între care se realizează legătura pot să conțină și valori care nu sunt colecții.

Operatorul FETCH furnizează rezultatele unui obiect ca efect al operației de asociere dintre entități, chiar și în situația în care informațiile respective nu au fost solicitate explicit în clauza WHERE.

SELECT b
FROM Book b LEFT JOIN FETCH b.categories

O expresie de cale (eng. path expression) exprimă modul în care sunt asociate entitățile persistente în cadrul unei scheme abstracte, acestea având un impact asupra domeniului de definiție și a rezultatelor furnizate. Ele se pot găsi în oricare subclauză a unei interogări și nu fac parte din sintaxa SQL standard. O expresie de cale poate stoca rezultate ce conțin o singură valoare, respectiv rezultate ce conțin o colecție de valori.

Tipul unei expresii de cale este tipul obiectului reprezentat de atributul care sufixează expresia acesta putând fi:

  • câmp sau proprietate persistente;
  • câmp sau proprietate de tip relație, putând avea o singură valoare;
  • câmp sau proprietate de tip relație, putând avea o colecție de valori.

O expresie de cale permite unei interogări JPQL să navigheze de-a lungul unor entități între care există o relație. Posibilitatea navigării într-un anumit punct este dată de multiplicitatea obiectului curent:

  • dacă acesta poate avea o singură valoare, navigarea este permisă: b.collection.name este permisă datorită faptului că elementul collection conține o singură valoare;
  • dacă acesta poate avea o colecție de valori, navigarea nu este permisă: b.categories nu permite navigarea, întrucât elementul categories conține o colecție de valori.

3. WHERE: restricționează valorile furnizate pe baza unor condiții, interogarea furnizând toate valorile corespunzătoare din sursa de date pentru care condiția este întrunită; deși este opțională utilizarea sa, interogările conțin o astfel de clauză, în caz contrar fiind furnizate toate înregistrările.

Clauza WHERE este formată din:

a) literali, pot avea tipul:

  • șir de caractere, cuprins între apostroafe (caracterul '); dacă șirul de caractere conține caracterul ', este necesar ca acesta să fie dublat; mecanismul de codificare folosit este cel din limbajul de programare Java, Unicode;
  • numeric, cu subtipurile:
    • exact, fără zecimale, putând lua valori în intervalul corespunzător tipului long din limbajul de programare Java;
    • aproximativ, cu zecumale (în notarea științifică), putând lua valori în intervalul corespunzător tipului double din limbajul de programare Java;
  • valoare de adevăr, poate avea valorile TRUE sau FALSE;
  • tip enumerat (folosind sintaxa tipurilor enumerate din limnbajul de programare Java, fiind necesar să se includă denumirea completă a clasei din care acesta face parte - inclusiv pachetul)

b) parametri pot fi identificați prin denumire (prefixată de caracterul :, făcându-se distincția între minuscule și majuscule) sau poziție (prefixată de caracterul ?, numerotarea făcându-se începând cu 1).

Într-o interogare JPQL, nu pot fi folosiți atât parametri identificați prin denumire cât și parametri indicați prin poziție.

Un parametru poate fi folosit numai în cadrul clauzelor WHERE sau HAVING.

c) expresie condițională evaluată de la stânga la dreapta în funcție de precedența operatorilor

TIP DE OPERATOR ORDINEA DE PRECEDENȚĂ
navigare .
aritmetic + - (unari)
* / (înmulțire, împărțire)
+ - (adunare, scădere)
comparație + - (unari)
=
>
>=
<
<=
<> (inegalitate)
[NOT] BETWEEN
[NOT] LIKE
[NOT] IN
IS [NOT] NULL
IS [NOT] EMPTY
[NOT] MEMBER OF
logici NOT
AND
OR

Modificarea precedenței unei expresii poate fi realizată prin folosirea de paranteze.

Operatorul BETWEEN determină dacă o expresie aritmetică se găsește într-o gamă de valori. Dacă expresia aritmetică are valoarea NULL, rezultatul aplicării operatorului BETWEEN este necunoscut.

Operatorul LIKE stabilește dacă o expresie de tip șir de caractere respectă un anumit șablon. Dacă expresia de tip șir de caractere are valoarea NULL, rezultatul aplicării operatorului LIKE este necunoscut.

Un șablon poate conține mai multe caractere speciale (eng. wildcards) care au o anumită semnificație:

CARACTER SPECIAL SEMNIFICAȚIE
_ orice caracter (unul singur)
% orice caracter (zero sau mai multe)
ESCAPE indică faptul că un caracter special poate să apară ca atare în expresia de tip șir de caractere, atunci când este prefixată de o anumită valoare

Operatorul IN verifică dacă o expresie (de tip șir de caractere sau numeric) se găsește într-un anumit set de valori. Dacă expresia are valoarea NULL, rezultatul aplicării operatorului IN este necunoscut.

Comparația unei expresii care nu conține o colecție de valori cu valoarea NULL se face prin intermediul operatorului IS [NOT]. Utilizarea operatorului de egalitate (=) într-o astfel de situație poate produce un rezultat necunoscut.

Verificarea faptului că o expresie care reține o colecție de valori conține sau nu rezultate se face prin intermediul operatorului IS [NOT] EMPTY. În situația în care expresia are valoarea NULL, rezultatul comparației va fi de asemenea NULL.

Operatorul [NOT] MEMBER OF testează dacă o valoare face parte dintr-o colecție, fiind necesar ca acestea să aibă același tip. Dacă nu se cunosc valorile asociate expresiei sau colecției, rezultatul furnizat este necunoscut. Pentru utlizarea acestei expresii, cuvântul-cheie OF este opțional.

Clauzele WHERE și HAVING pot conține subinterogări, fiind obligatoriu ca acestea să fie plasate între paranteze ( .. ) pentru ca expresia să fie validă. La rândul lor, acestea pot fi transmise ca parametrii operatorilor:

  • [NOT] EXISTS - verifică dacă subinterogarea a furnizat unul sau mai multe rezultate;
  • ALL / ANY = SOME - verifică dacă toate / unele valori furnizate de subinterogare respectă condiția indicată și pot fi utilizate împreună cu operatorii =, <, , >, >= sau <>:
    • în cazul ALL, dacă subinterogarea este vidă, rezultatul va fi adevărat;
    • în cazul ANY = SOME, dacă subinterogarea este vidă, rezultatul va fi fals.

Limbajul JPQL definește o serie de funcții care operează pe expresii de tipul șiruri de caractere, valori numerice sau de tip dată și timp, acestea putând fi utilizate în cadrul clauzelor SELECT, WHERE și HAVING.

Funcții pentru șiruri de caractere

În cadrul unui șir de caractere, indexarea se face începând cu valoarea 1.
SEMNĂTURĂ METODĂ TIP ÎNTORS
CONCAT(String, String) String
LENGTH(String) int
LOCATE(String, String, [, start]) int
SUBSTRING(String, start, length) String
TRIM[([LEADING|TRAILING|BOTH] char) FROM] (String) String
LOWER(String) String
UPPER(String) String

Funcția CONCAT unește două șiruri de caractere într-un singur șir de caractere.

Funcția LENGTH furnizează lungimea unui șir de caractere sub forma unui număr întreg.

Funcția LOCATE furnizează poziția la care un subșir se găsește în cadrul unui șir. Opțional, căutarea poate fi realizată începând cu o anumită poziție (implicit, căutarea se face începând cu poziția 1). Dacă subșirul nu se găsește în cadrul șirului, rezultatul întors va fi 0.

Funcția SUBSTRING calculează un subșir al unui șir, pornind de la o anumită poziție și având o anumită lungime.

Funcția TRIM elimină caracterul specificat (opțional) de la începutul și / sau de la sfârșitul unui șir de caractere. În cazul în care nu se precizează nici un parametru opțional, sunt eliminate spațiile din cadrul șirului de caractere.

  • LEADING - sunt eliminate numai caracterele de la începutul șirului;
  • TRAILING - sunt eliminate numai caracterele de la sfârșitul șirului;
  • BOTH - sunt eliminate atât caracterele de la încputul șirului cât și caracterele de la sfârșitul șirului.

Funcțiile LOWER și UPPER convertesc toate caracterele unui șir de caractere la minuscule, respectiv la majuscule.

Funcții pentru valori numerice

SEMNĂTURĂ METODĂ TIP ÎNTORS
ABS(number) int, float sau double
MOD(int, int) int
SQRT(double) double
SIZE(Collection) int

Funcția ABS primește ca parametru o expresie numeric și furnizează modulul valorii respective, având același tip de date ca și parametrul.

Funcția MOD returnează restul împărțirii numerelor primite ca parametri.

Funcția SQRT calculează rădăcina pătrată a valorii transmisă ca parametru.

Funcția SIZE furnizează numărul de elemente din colecția transmisă ca parametru.

Funcții pentru expresii de tip dată / timp

Funcțiile pentru expresiile de tip dată / timp furnizează momentul curent, în diferite formate, în funcție de necesități.

SEMNĂTURĂ METODĂ TIP ÎNTORS
CURRENT_DATE java.sql.Date
CURRENT_TIME java.sql.Time
CURRENT_TIMESTAMP java.sqlTimestamp

Expresiile de tip CASE furnizează un set de condiții alternative pentru o expresie, indiferent de tipul ei precum și o acțiune implicită care va fi realizată dacă nici una dintre condițiile anterioare nu a fost întrunită. Pot fi comparate expresii având orice tip de date.

O astfel de instrucțiune începe prin cuvântul-cheie CASE urmat de valoarea expresiei care se dorește a fi evaluată și se termină prin END. Valorile posibile sunt specificate prin WHEN iar acțiunile corespunzătoare sunt precizate prin THEN. Prin cuvântul-cheie ELSE se indică acțiunea care va fi realizată dacă nici una dintre condițiile anterioare nu a fost întrunită.

CASE expression
  WHEN value1 THEN action1
  [WHEN value2 THEN action2]
  ...
  [WHEN valuen THEN actionn]
  [ELSE action]
END
Tipul unei entități persistente poate fi obținut prin funcția TYPE.

În situația în care o referință către o entitate nu este identificată în sursa de date, valoarea acesteia este NULL.

Evaluarea expresiilor care conțin valori de tip NULL este aceeași ca în limbajul SQL standard.

  • o comparație sau o operație aritmetică care are o valoare necunoscută este evaluată la NULL;
  • două valori NULL nu sunt egale, compararea acestora rezultând într-o valoare necunoscută;
  • testul IS NULL convertește o entitate persistentă vidă la TRUE la fel cum testul IS NOT NULL convertește o entitate persistentă nevidă la FALSE;
  • operatorii logici folosesc 3 valori pentru aceste tipuri de date: adevărat, fals și necunoscut.

AND True False Unknown
True True False Unknown
False False False False
Unknown Unknown False Unknown
OR True False Unknown
True True True True
False True False Unknown
Unknown True Unknown Unknown
NOT REZULTAT
True False
False True
Unknown Unknown

În limbajul JPQL, numai valorile de același tip pot fi comparate, cu o singură excepție (valorile numerice exacte și aproximative, situație în care are loc conversia de tip).

Operațiile de comparare sunt realizate asupra valorilor la nivel de obiecte Java și nu asupra modului în care acestea sunt reprezentate în sursa de date. Din acest motiv, în clasele de tip entitate trebuie să se utilizeze tipuri de date derivate din Object care pot avea valoarea NULL și nu tipuri de date primitive care nu au această proprietate.

TEST CONDIȚIONAL True False Unknown
IS TRUE True False False
IS FALSE False True False
expresia este necunoscută False False False

4. GROUP BY: grupează valorile furnizate conform unor criterii bazate pe un set de proprietăți;

5. HAVING: restricționează valorile furnizate pe baza unor condiții (utilizată împreună cu clauza GROUP BY);

6. ORDER BY: sortează valorile furnizate pe baza unui criteriu de ordonare, acestea fiind evaluate de la stânga la dreapta, poziția indicând prioritatea pe care o are un câmp; se pot folosi cuvintele cheie ASC pentru sortare crescătoare, respectiv DESC pentru sortare descrescătoare.

O clauză ORDER BY este validă numai în situația în care aceasta include un set de atribute ordonabile, deci care apar în clauza SELECT.
select_statement ::= select_clause from_clause
                     [where_clause][groupby_clause][having_clause][orderby_clause]

Interogări de tip UPDATE și DELETE

O interogare de tip UPDATE sau DELETE este formată din 2 clauze:

  1. UPDATE / DELETE: tipurile de obiecte care vor fi modificate / șterse;
  2. WHERE: restricționează valorile furnizate pe baza unor condiții;
update_statement ::= update_clause [where_clause]
delete_statement ::= delete_clause [where_clause]

Criteria API

Criteria API este utilizat pentru a defini interogări pentru entități persistente și pentru relațiile dintre ele în cadrul unor obiecte, folosind limbajul de programare Java. Astfel, interogările vor fi portabile (raportat la sursa de date utilizată) și nu există posibilitatea de a apărea probleme legate de conversiile între tipurile de date.

Și Criteria API funcționează pe baza unor scheme abstracte ale entităților persistente, pe relațiile dintre acestea și pe obiecte imbricate, oferind funcționalități pentru interogarea, modificarea și ștergerea valorilor din sursa de date prin invocarea operațiilor corespunzătoare din cadrul JPA. Este definită și o altă interfață de programare, Metamodel API, prin intermediul căruia entitățile persistente sunt modelate pentru a putea fi utilizate adecvat.

Etapele necesare pentru a realiza o interogare folosind Criteria API sunt:

  1. construirea unui obiect javax.persistence.criteria.CriteriaBuilder prin invocarea metodei getCriteriaBuilder() a clasei EntityManager
    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
  2. definirea unui obiect interogare, de tip javax.persistence.criteria.CriteriaQuery, prin apelul metodei createQuery() din interfața CriteriaBuilder; atributele acestui obiect interogare, parametrizat la tipul de date al rezultatului furnizat, vor fi modificate prin detaliile interogării:
    CriteriaQuery<Book> criteriaQuery = criteriaBuilder.createQuery(Book.class);
  3. precizarea entității persistente rădăcină (punctul inițial al interogării) prin apelarea metodei from() a obiectului de tip CriteriaQuery; se obține astfel un obiect de tip javax.persistence.criteria.Root, parametrizat și acesta la tipul de entitate persistenă respectivă; se indică astfel clauza FROM a interogării:
    Root<Boot> book = criteriaQuery.from(Book.class);
  4. indicarea tipului de rezultat care va fi furnizat în urma execuției interogării prin apelul metodei select() pe obiectul CriteriaQuery; prin acest apel se stabilește clauza SELECT a interogării:
    criteriaQuery.select();
  5. pregătirea interogării pentru a fi executată, prin instanțierea unui obiect de tip TypedQuery, prin care se indică tipul de date al rezultatului furnizat; un astfel de obiect poate fi rulat la nivelul sursei de date; el se obține prin apelul metodei createQuery() a obiectului de tip EntityManager căruia i se furnizează ca parametru obiectul CriteriaQuery creat anterior; astfel, modificările aduse la detaliile interogării sunt stocate astfel încât interogarea să fie pregătită spre a putea fi executată:
    TypedQuery<Book> typedQuery = entityManager.createQuery(criteriaQuery);
  6. execuția interogării se face apelând metoda getResultList() din clasa TypedQuery, care furnizează lista de rezultate, garantând faptul că acestea au exact tipul de date așteptat:
    List<Book> allBooks = typedQuery.getResultList();
Utilizarea Metamodel API

Prin intermediul interfeței de programare Metamodel API, se definește un metamodel pentru entitățile gestionate în cadrul unei unități de persistență. Astfel, pentru fiecare entitate, este necesar să se definească și o clasă metamodel, având aceeași denumire, la care este sufixat caracterul _.

Book.java
package ro.pub.cs.aipi.lab02.entities;
 
import java.util.Set;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
 
@Entity
@Table(name = "bookstore.book")
public class Book extends PersistentEntity {
 
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "id")
  protected Long id;
  @Column(name = "title")
  protected String title;
  @Column(name = "subtitle")
  protected String subtitle;
  @ManyToMany(targetEntity = Writer.class)
  @JoinTable(name="bookstore.author", joinColumns=@JoinColumn(name="book_id"), inverseJoinColumns=@JoinColumn(name="writer_id"))
  protected Set<Writer> writers;
  @Column(name = "description")
  protected String description;
  @Column(name = "edition")
  protected Long edition;
  @Column(name = "printing_year")
  protected Long printingYear;
  @OneToOne
  @JoinColumn(name = "collection_id", unique = false, nullable = false, updatable = true)
  protected Collection collection;
  @ManyToMany(targetEntity = Category.class)
  @JoinTable(name="bookstore.category_content", joinColumns=@JoinColumn(name="book_id"), inverseJoinColumns=@JoinColumn(name="category_id")) 
  protected Set<Category> categories;
 
  // ...
}

Interogările construite folosind Criteria API folosesc clasa metamodel și atributele sale pentru a referi clasele entitate gestionate împreună cu câmpurile și atributele lor, precum și relațiile dintre acestea.

Clasele metamodel care corespund entităților persistente au tipul javax.persistence.metamodel.EntityType<T>.

De regulă, procesoarele de adnotări generează clasele metamodel fie în momentul compilării, fie în momentul rulării. În cazul utilizării Criteria API, există două abordări:

  1. generarea statică a claselor metamodel prin utilizarea procesorului de adnotări al furnizorului JPA, javax.persistence.metamodel.Metamodel
    Book_.java
    package ro.pub.cs.aipi.lab02.entities;
     
    import javax.persistence.metamodel.SetAttribute;
    import javax.persistence.metamodel.SingularAttribute;
    import javax.persistence.metamodel.StaticMetamodel;
     
    @StaticMetamodel(Book.class)
    public class Book_ {
      public static volatile SingularAttribute<Book, Long> id;
      public static volatile SingularAttribute<Book, String> title;
      public static volatile SingularAttribute<Book, String> subtitle;
      public static volatile SetAttribute<Book, Writer> writers;
      public static volatile SingularAttribute<Book, Long> edition;
      public static volatile SingularAttribute<Book, Long> printingYear;
      public static volatile SingularAttribute<Book, Collection> collection;
      public static volatile SetAttribute<Book, Category> categories;
    }
  2. obținerea clasei metamodel prin:
    1. apelarea metodei getModel() a obiectului rădăcină al interogării;
      CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
      CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Book.class);
      Root<Book> book = criteriaQuery.from(Book.class);
      EntityType<Book> Book_ = book.getMode();
    2. obținerea unei instanțe a clasei Metamodel și apelarea metodei entity() a acestui obiect
      Metamodel metamodel = entityManager.getMetamodel();
      EntityType<Book> Book_ = metamodel.entity(Book.class);
În practică, se obișnuiește să se definească o clasă metamodel statică, care va asigura faptul că nu se vor produce erori la conversiile de tip de date. Obținerea claselor metamodel dinamic, prin oricare din cele două mecanisme (apelul metodei getModel() a clasei Root, respectiv invocarea metodei entity() pe clasa Metamodel) nu asigură faptul că nu se vor produce erori la conversiile de tip și nici nu oferă aplicației acces la câmpurile și proprietățile persistente din clasa metamodel.
Utilizarea Interogărilor

O interogare folosind Criteria API implică definirea clauzelor obligatorii SELECT și FROM precum și a celor opționale (WHERE, GROUP BY, HAVING, ORDER BY) prin intermediul unor obiecte Java, astfel încât să se evite generarea de excepții datorate conversiilor de tipuri în momentul în care sunt obținute rezultatele.

Interfața javax.persistence.criteria.CriteriaBuilder este utilizată pentru a furniza interogări, selecții, expresii, predicate și criterii de sortare.

Un obiect CriteriaBuilder se obține prin invocarea metodei getCriteriaBuilder() pe un obiect de tipul EntityManager:

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();

Interogările sunt construite prin intermediul unui obiect de tipul CriteriaQuery, acesta având posibilitatea de a accesa una sau mai multe entități la un moment dat. De regulă, se utilizează una dintre variantele metodei supraîncărcate createQuery() a clasei CriteriaBuilder, însă pentru a se evita posibilitatea de generare a unor excepții legate de conversiile de tipuri, se recomandă să se utilizeze metoda care primește ca parametru tipul de date al entității care va fi furnizată ca rezultat createQuery().

CriteriaQuery<Book> criteriaQuery = criteriaBuilder.createQuery(Book.class);

Pentru un obiect de tip CriteriaQuery este necesar să se definească o entitate rădăcină, care reprezintă punctul inițial în traversarea ierarhiei de clase pentru obținerea rezultatului interogării. Din acest motiv, un astfel de obiect mai poartă și denumirea de rădăcină a interogării, desemnând funcționalitatea clauzei FROM.

O instanță de tipul Root<T> se obține apelând metoda from() a obiectului de tip CriteriaQuery, care poate primi ca parametru fie tipul de date al clasei entitate fie o instanță de tipul EntityType<T>

Root<Book> book = criteriaQuery.from(Book.class);
Root<Book> book = criteriaQuery.from(Book_);

Interogările pot avea una sau mai multe rădăcini, situație întâlnită în cazul operațiilor de asociere dintre entități.

În situația în care rezultatele aparțin mai multor entități, interogarea trebuie să definească o asociere către obiectul referit, apelând una dintre metodele join() descrise de clasa javax.persistence.criteria.From. Acestea folosesc o clasă metamodel de tipul EntityType<T> pentru a indica câmpul sau proprietatea persistente din entitatea cu care se face legătura și furnizează un obiect de tipul javax.persistence.criteria.Join(X,Y), unde X reprezintă entitatea copil iar Y reprezintă entitatea părinte.

Root<Book> book = criteriaQuery.from(Book.class);
Join<Book, Category> joinBookCategory = book.join(Book_.categories);

Metoda join() poate fi invocată în cascadă, până se ajunge la tipul de asociere dorit.

Obiectele de tip javax.persistence.criteria.Path, folosite în clauzele SELECT și WHERE ale unei interogări, pot fi de tipul rădăcină, asociere sau de alt tip. Metodele get() ale acestei clase oferă acces la câmpurile și proprietățile unei entități. Acesta primește ca parametru atributul corespunzător din clasa metamodel, care poate conține o singură valoare (specificat cu adnotarea @SingularAttribute) sau o colecție de valori (specificate prin adnotările @CollectionAttribute, @SetAttribute, @ListAttribute, @MapAttribute).

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Book> criteriaQuery = criteriaBuilder.createQuery(Book.class);
Root<Book> book = criteriaQuery.from(Book.class);
criteriaQuery.select(book.get(Book_.title));

Constrângerile unei interogări, incluse în clauzele WHERE sau HAVING, pot fi precizate prin intermediul metodei where() din clasa CriteriaQuery. Aceasta evaluează instanțele interfeței Expression pentru a filtra rezultatele interogării în funcție de condițiile pe care acestea le conțin. Condițiile vor fi precizate prin intermediul metodelor specifice din interfațele Expression respectiv CriteriaBuilder. Un obiect de tip Expression poate fi inclus și în clauza SELECT.

Interfața Expression definește metode prin care rezultatele unei interogări pot fi filtrate.

METODA DESCRIERE
isNull() verifică dacă o expresie are valoarea null
isNotNull() verifică dacă o expresie nu are valoarea null
in() verifică dacă valoarea unei expresii se găsește într-o anumită listă
criteriaQuery.where(book.get(Book_.subtitle).isNotNull());

Interfața CriteriaBuilder definește metode prin care rezultatele unei interogări pot fi filtrate.

METODA DESCRIERE
equal verifică dacă două expresii sunt egale
notEqual() verifică dacă două expresii nu sunt egale
gt() verifică dacă prima expresie numerică este strict mai mare decât a doua expresie numerică
ge() verifică dacă prima expresie numerică este mai mare sau egală cu a doua expresie numerică
lt() verifică dacă prima expresie numerică este strict mai mică decât a doua expresie numerică
le() verifică dacă prima expresie numerică este mai mică sau egală cu a doua expresie numerică
between() verifică o expresie se găsește între alte două expresii
like() verifică dacă o expresie verifică un anumit șablon
criteriaQuery.where(cb.equal(book.get(Book_.title), "Thinking in Java"));

În situația în care se folosesc condiții compuse, legate între ele prin operatori logici, pot fi folosite metodele and(), or() și not(), disponibile în interfața CriteriaBuilder.

criteriaQuery.where(cb.equal(book.get(Book_.title), "Thinking in Java").and(cb.gt(book.get(Book_.printingYear), 2000)));

Pentru interogările care furnizează mai multe rezultate, se poate dovedi util ca acestea să fie organizate. În acest sens, interfața CriteriaQuery definește metodele:

  • orderBy() ordonează rezultatele interogării în funcție de atributele unei entități;
  • groupBy() grupează împreună rezultatele unei interogări în funcție de atributele unei entități, restricții suplimentare asupra acestor grupuri putând fi precizate prin intermediul metodei having().

Metoda orderBy() primește ca parametru obiecte de tip javax.persistence.criteria.Order obținute prin apelul metodelor asc() respectiv desc() din interfața CriteriaBuilder, care indică criteriul de sortare (crescător, respectiv descrescător).

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Book> criteriaQuery = criteriaBuilder.createQuery(Book.class);
Root<Book> book = criteriaQuery.from(Book.class);
criteriaQuery.select(book.get(Book_.title));
criteriaQuery.orderBy(criteriaBuilder.desc(book.get(Book_.printingYear), book.get(Book_.edition)));
În cazul în care se dorește precizarea mai multor criterii de ordonare, acestea vor fi transmise sub forma unei liste ca parametru al metodei orderBy(), prioritatea fiind dată de ordinea de apariție în cadrul acesteia.

Metoda groupBy() partiționează rezultatele interogării în mai multe grupuri. Aceasta poate fi utilizată împreună cu metoda having() pentru a filtra informațiile de la nivelul grupurilor, aceasta primind ca parametru o expresie condițională.

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Book> criteriaQuery = criteriaBuilder.createQuery(Book.class);
Root<Book> book = criteriaQuery.from(Book.class);
criteriaQuery.select(book.get(Book_.title));
criteriaQuery.groupBy(book.get(Book_.printingYear));
criteriaQuery.having(book.get(Book_.title).like("%Thinking in Java%"));

O interogare este pregătită pentru execuție în momentul în care se apelează metoda createQuery() din clasa EntityManager, aceasta furnizând un obiect de tipul TypedQuery<T>.

Rularea propriu-zisă a interogării se face prin intermediul uneia dintre metodele definite în clasa TypedQuery<T>:

În cadrul Criteria API sunt definite și interogări bazate pe șiruri de caractere, care precizează valorile atributelor unei entități prin intermediul unor valori hardcodate, în loc de a folosi obiectele din metamodel. O astfel de abordare furnizează o tipare slabă, comparativ cu soluția în care tiparea era tare. Similar, interogările pot fi statice sau dinamice și oferă aceleași funcționalități.

Avantajul utilizării interogărilor bazate pe șiruri de caractere constă în posibilitatea de construire a obiectelor interogare la momentul compilării, fără a fi necesar să se genereze în momentul rulării pe baza metamodelului.

Dezavantajul utilizării interogărilor bazate pe șiruri de caractere se referă la lipsa de siguranță în privința tipurilor de date ale rezultatelor furnizate, ceea ce poate conduce la excepții care nu pot fi prevăzute, cauzate de operațiile de conversie.

criteriaQuery.where(cb.equal(book.get("title"), "Thinking in Java").and(cb.gt(book.get("printingYear"), 2000)));

Configurarea mediilor integrate de dezvoltare

Unități de Persistență

O unitate de persistență definește setul de clase de tip entitate care vor putea fi gestionate de obiectele EntityManager din cadrul aplicației.

Toate entitățile pentru care se dorește să se asigure persistența trebuie să facă parte din cadrul aceleiași surse de date!!!

Fișierul de configurare persistence.xml conține de regulă informațiile necesare pentru unitatea de persistență. Acesta trebuie plasat în calea resources/META-INF din cadrul directorului de surse al aplicației și trebuie inclus în classpath!!!

persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <persistence-unit name="02-BookStore-JPA" transaction-type="RESOURCE_LOCAL">
    <class>ro.pub.cs.aipi.lab02.entities.Book</class>
    <class>ro.pub.cs.aipi.lab02.entities.BookPresentation</class>    
    <class>ro.pub.cs.aipi.lab02.entities.Category</class>    
    <class>ro.pub.cs.aipi.lab02.entities.Collection</class>
    <class>ro.pub.cs.aipi.lab02.entities.Country</class>
    <class>ro.pub.cs.aipi.lab02.entities.Format</class>
    <class>ro.pub.cs.aipi.lab02.entities.InvoiceHeader</class>
    <class>ro.pub.cs.aipi.lab02.entities.InvoiceLine</class>
    <class>ro.pub.cs.aipi.lab02.entities.Language</class>
    <class>ro.pub.cs.aipi.lab02.entities.PublishingHouse</class>
    <class>ro.pub.cs.aipi.lab02.entities.SupplyOrderHeader</class>
    <class>ro.pub.cs.aipi.lab02.entities.SupplyOrderLine</class>
    <class>ro.pub.cs.aipi.lab02.entities.User</class>
    <class>ro.pub.cs.aipi.lab02.entities.Writer</class>
 
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/bookstore"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="javax.persistence.jdbc.password" value="StudentAipi2015"/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
      <property name="hibernate.format_sql" value="true" />
      <property name="hibernate.connection.pool_size" value="1"/>
      <property name="hibernate.temp.use_jdbc_metadata_defaults" value="false"/>
      <property name="hibernate.id.new_generator_mappings" value="false"/>
    </properties>
  </persistence-unit>
</persistence>

Eclipse Mars (4.5)

În mediul integrat de dezvoltare Eclipse, activarea JPA se face din meniul contextual (accesibil prin click dreapta pe denumirea sa, în Package Explorer) al proiectului, în care pot fi configurate proprietățile acestuia.

În secțiunea Project Facets se bifează opțiunea JPA. Tot aici poate fi vizualizată și versiunea curentă a acestei interfețe de programare a aplicațiilor.

În secțiunea JPA, pot fi controlate următoarele proprietăți:

  • furnizorul JPA (în cazul de față, Hibernate, acesta fiind configurat din fișierul persistence.xml, dacă nu există o astfel de valoare în lista de opțiuni, lăsându-se valoarea implicită Generic 2.1) precum și versiunea curentă a interfeței de programare a aplicațiilor;
  • biblioteca ce oferă funcționalitatea referitoare la accesul la date (se poate alege valoarea Disable Library Configuration în situația în care se folosește un sistem de tip Maven);
  • conexiunea la baza de date (acesta poate fi de asemenea configurat în fișierul persistence.xml);
  • mecanismul prin care sunt gestionate clasele persistente (descoperire automată pe baza adnotărilor, respectiv precizarea lor în fișierul persistence.xml);
  • locația la care pot fi accesate clasele metamodel.

NetBeans 8.0.2

În mediul integrat de dezvoltare NetBeans, nu este necesară activarea JPA, acest lucru realizându-se în momentul în care este detectat fișierul persistence.xml în directorul resources/META-INF din codul sursă.

Acesta poate fi editat atât în mod vizual (Design) cât și în mod text (Source).

Pot fi configurate:

  • denumirea unității de persistență;
  • biblioteca ce asigură funcționalitatea de acces la date;
  • URL-ul pentru asigurarea conexiunii la baza de date;
  • strategia de generare a tabelelor (Create, Drop and Create, None);
  • strategia de validare (Auto, Callback, None);
  • mecanismul de gestiune a zonei de memorie tampon partajate (All, None, Enable Selective, Disable Selective, Unspecified);
  • clasele entitate care vor fi gestionate (pentru acestea, se indică calea lor completă, inclusiv pachetul din care fac parte).

Activitate de Laborator

0. Să se cloneze în directorul de pe discul local conținutul depozitului la distanță de la https://www.github.com/aipi2015/Laborator02. În urma acestei operații, directorul Laborator02 va trebui să conțină subdirectorul labtasks, fișierele README.md și LICENSE.

student@aipi2015:~$ git clone https://www.github.com/aipi2015/Laborator02.git
Modificați în fișierul persistence.xml din directorul resources/META-INF informațiile necesare obținerii drepturilor de acces la baza de date (nume de utilizator, parola).

1. Să se implementeze metoda getPersistentEntitySize() din clasa PersistentEntityManager a pachetului ro.pub.cs.aipi.lab02.businesslogic care furnizează numărul de obiecte al unei entități persistente.

<spoiler|Indicații de Rezolvare> Metoda statică getEntityManagerFactory() din clasa Utilities furnizează o fabrică pentru obiecte de gestiune a entităților, unică la nivelul întregii unități de persistență. Resursele ocupate de obiectul de tip EntityManager trebuie eliberate (prin apelul metodei close()) dacă acesta nu mai este necesar. Se va folosi un obiect de tip Query care va întoarce un singur rezultat. Limbajul JPL implică folosirea denumirii clasei de tip entitate în locul denumirii tabelei. Se poate utiliza metoda getSimpleName() pentru obiectul persistentEntityType de tip Class. </spoiler>

2. Să se implementeze metoda getBookList() din clasa BookManager a pachetului ro.pub.cs.aipi.lab02.businesslogic care furnizează lista tuturor cărților din baza de date.

Pentru fiecare volum vor fi incluse următoarele informații:

  • identificatorul;
  • titlul;
  • lista autorilor (în formatul prenume nume):
    • în cazul în care nu există nici un autor, se va afișa textul * * *;
    • în cazul în care există mai mulți autori, aceștia vor fi delimitați prin șirul de caractere , (virgulă și spațiu);
  • denumirea colecției;
  • denumirea editurii;
  • ediția;
  • anul de apariție;
  • țara.

Rezultatul acestei metode este o listă de obiecte de tip BookInformation care are drept proprietăți toate informațiile solicitate sub forma unor tipuri de date primitive din limbajul Java (sau wrappere ale acestora).

Este obligatoriu să se folosească Criteria API.

<spoiler|Indicații de Rezolvare> Se încarcă tot conținutul entității persistente Book (folosind metoda getCollection() din clasa PersistentEntityManager), parcurgându-se ulterior lista de rezultate pentru a se extrage informațiile necesare. Pentru unele proprietăți (lista de scriitori), vor fi necesare prelucrări suplimentare, care vor fi realizate direct pe obiectele Java. Este necesar ca obiectul de tip EntityManager să fie menținut atâta vreme cât este necesar să se obțină obiecte de tip referință, acestea fiind încărcate leneș. Numai după ce toate informațiile au fost încărcate din baza de date, resursele ocupate de acesta pot fi eliberate. </spoiler>

3. Să se implementeze metoda create() din clasa PersistentEntityManager a pachetului ro.pub.cs.aipi.lab02.businesslogic, pentru a stoca un nou obiect într-o tabelă a bazei de date.

public Long create(T persistentEntity);

Metoda primește ca parametru un obiect de tip generic (derivat din PersistentEntity), conținând toate proprietățile, mai puțin cheia primară (care va fi autogenerată) și întoarce identificatorul din baza de date al entității persistente respective.

<spoiler|Indicații de Rezolvare> Nu uitați să folosiți o tranzacție, care asigură atomicitatea operației și un nivel de izolare corespunzător. Identificatorul poate fi obținut numai după ce obiectul a fost stocat în baza de date, în afara tranzacției (cu alte cuvinte, după ce a fost apelată metoda commit() - sau flush()). </spoiler>

4. Să se implementeze metoda updateBookPresentationPriceForBooksWithMultipleFormats() din clasa BookPresentationManager a pachetului ro.pub.cs.aipi.lab02.businesslogic astfel încât să mărească cu un anumit procent prețul cărților care sunt disponibile într-un număr de formate (de prezentare) mai mare decât un anumit prag.

public List<BookPresentationInformation> updateBookPresentationPriceForBooksWithMultipleFormats(int numberOfFormats, double amount)

Metoda primește ca parametri procentul cu care este crescut prețul cărților precum și numărul de formate pe care trebuie să îl aibă și furnizează informații referitoare la prezentările cărților pentru care au fost actualizate, stocate ca proprietăți ale clasei BookPresentationInformation:

  • identificatorul din tabela book_presentation;
  • identificatorul cărții corespunzătoare;
  • identificatorul formatului corespunzător;
  • prețul după modificare.

Este de preferat să se folosească Criteria API, acolo unde este posibil.

<spoiler|Indicații de Rezolvare> Operațiile de creare și de ștergere a unei tabele temporare vor fi realizate folosind interogări native, întrucât JPA nu oferă suport pentru astfel de operații. Întrucât actualizarea folosește informații din tabela temporară, acestea nefiind corespunzătoare unei entități, și această operație va fi realizată tot prin intermediul unor interogări native. Se va utiliza Criteria API pentru a se determina lista cărților pentru care trebuie actualizat prețul precum și pentru a se determina informațiilor solicitate după operația de actualizare. </spoiler>

5. Să se implementeze metoda deleteWritersWithoutBooks() din clasa WriterManager a pachetului ro.pub.cs.aipi.lab02.businesslogic. Metoda furnizează numărul de înregistrări care au fost modificate.

Este obligatoriu să se folosească Criteria API.

<spoiler|Indicații de Rezolvare> Pentru subinterogarea din clausa WHERE se va utiliza un obiect de tip SubQuery. Aceasta va furniza numărul de înregistrări din entitatea Author corespunzătoare scriitorului respectiv, fiind necesar ca acesta să fie 0. </spoiler>

6. Să se implementeze metoda getWritersBibliography() din clasa WritersManager a pachetului ro.pub.cs.aipi.lab02.businesslogic care furnizează lista scriitorilor care au un scris un anumit număr de cărți, dintre care la unele sunt unici autori iar pe altele le-au redactat în colaborare (oricare dintre aceste valori pot fi și nule).

public List<WriterInformation> getWritersBibliography(int numberOfBooksTotal, int numberOfBooksAlone, int numberOfBooksCollaboration) {

Rezultatul acestei metode este o listă de obiecte de tip WriterInformation care are drept proprietăți toate informațiile solicitate sub forma unor tipuri de date primitive din limbajul Java (sau wrappere ale acestora).

Se vor afișa prenumele și numele autorilor, lista cărților scrise (separate prin caracterul ;), numărul total de cărți, numărul de cărți la care este unic autor, numărul de cărți redactate în colaborare cu alții.

Informațiile vor fi ordonate (crescător) în funcție de prenumele și numele scriitorilor.

<spoiler|Indicații de Rezolvare> Se determină lista tuturor scriitorilor folosind metoda getCollection() din clasa PersistentEntityManager. Pentru fiecare scriitor în parte, se determină - prin intermediul unei interogări - lista tuturor cărților pe care aceștia le-au scris. În situația în care numărul lucărilor la care au participat corespunde pragului impus se calculează - prin intermediul altor interogări - numărul cărților la care sunt unici autori, respectiv numărul de cărți redactate în colaborare cu alții (pentru acestea este necesar să se utilizeze subinterogări care contorizează numărul de înregistrări din entitatea Author corespunzătoare unei cărți din lista celor pe care le-a redactat un scriitor). Dacă și aceste criterii respectă condițiile, informațiile sunt incluse în rezultat. </spoiler>

7. Să se implementeze metoda makeSupplyOrderBasedOnStockPile() din clasa BookPresentationManager a pachetului ro.pub.cs.aipi.lab02.businesslogic care creează comenzi de aprovizionare pentru cărțile al căror stoc este inferior unui anumit prag, furnizat ca parametru. O comandă de aprovizionare este realizată pentru o anumită editură și conține toate cărțile furnizate de aceasta, având stocul care respectă condiția respectivă.

public List<SupplyOrderInformation> makeSupplyOrderBasedOnStockpile(int stockpile);

Rezultatul acestei metode este o listă de obiecte de tip SupplyOrderInformation care are drept proprietăți toate informațiile solicitate sub forma unor tipuri de date primitive din limbajul Java (sau wrappere ale acestora): identificatorul editurii, identificatorul pentru prezentarea cărții, respectiv cantitatea pentru care a fost realizată comanda de aprovizionare.

Este obligatoriu să se folosească Criteria API.

<spoiler|Indicații de Rezolvare> Se indentifică acele formate de prezentare ale cărților pentru care stocul nu întrunește cantitatea minimă, acestea fiind ordonate în funcție de editură. Sortarea în funcție de mai multe criterii se realizează folosind o listă de obiecte de tip javax.persistence.criteria.Order. Accesul la identificatorul editurii se face prin intermediul unui obiect Join<Collection, PublishingHouse> obținut pe baza operației de asociere (prin intermediul metodei join()) ce pornește de la un obiect de tipul BookPresentation. Se parcurge această listă, creându-se o comandă de aprovizionare atunci când se întâlnește o editură nouă. O entitate corespunzătoare va fi stocată pentru fiecare volum în parte, cantitatea fiind determinată ca diferență între pragul minim și valoarea aflată în stoc. Pentru stocarea unei valori în baza de date se va utiliza metoda create() din clasele specifice, derivate din PersistentEntityManager, pentru tipurile de date corespunzătoare (SupplyOrderHeader, respectiv SupplyOrderLine). Pentru generarea unui număr de identificare se poate utiliza metoda statică generateIdentificationNumber() din clasa Utilities pentru care se specifică stuctura: numărul de caractere de tip literă, respectiv numărul de caractere de tip cifră. Lista înregistrărilor stocate se poate determina pe baza datei la care acestea au fost emise (data curentă).

Este necesar să se elibereze resursele alocate pentru obiectul de tip EntityManager între operațiile de scriere, respectiv de citire a informațiilor în baza de date, pentru a se asigura faptul că interogările furnizează înregistrări actualizate.

</spoiler>

8. Să se implementeze metoda getBookListsWithStockpile() din clasa BookManager a pachetului ro.pub.cs.aipi.lab02.businesslogic care furnizează lista cărților pentru care suma stocurilor (determinate din toate formatele de prezentare posibile) depășește un anumit prag, furnizat ca parametru.

public List<BookInformationDetailed> getBooksListWithStockpile(int stockpile)

Pentru fiecare volum vor putea fi consultate următoarele informații:

  • identificatorul;
  • titlul și subtitlul;
  • lista autorilor;
  • lista categoriilor;
  • lista formatelor;
  • lista limbilor.

Rezultatul acestei metode este o listă de obiecte de tip BookInformationDetailed care are drept proprietăți toate informațiile solicitate sub forma unor tipuri de date primitive din limbajul Java (sau wrappere ale acestora

În cazul în care o listă este vidă, se va afișa valoarea null. În cazul în care o listă conține mai multe elemente, acestea vor fi delimitate prin caracterul virgulă.

Este obligatoriu să se folosească Criteria API.

<spoiler|Indicații de Rezolvare> Se determină cărțile pentru care suma stocurilor corespunzătoare diferitelor tipuri de prezentări întrunește condiția specificată. Pentru fiecare carte în parte se determină informațiile solicitate. Datele precum formatul și limba vor fi obținute unei interogări adiționale prin care se realizează asocierea dintre entitățile Book și BookPresentation. </spoiler>

Resurse

JPA (Java Persistence API)

Hibernate

Soluții

laboratoare/laborator02.txt · Last modified: 2015/11/04 12:15 by Andrei Roșu-Cojocaru
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0