|
Problema maparii O/R a generat in comunitatea Java o confruntare intre mai multe solutii mai mult sau mai putin comerciale. Dintre acestea merita amintite cel putin Hibernate, TopLink sau Kodo (SolarMetric). Popularitatea acestor motoare O/R a prevalat in cele din urma fata de existentul standard de persistenta J2EE materializat sub forma entitatilor EJB 2.0. Ecosistemul JEE a oferit astfel cadrul perfect pentru standardizarea specificatiilor de persistenta care sa fie implementate tehnologic de existentele motoare O/R care sa garanteze prin maturitatea lor fezabilitatea cadrelor de lucru oferite formalizate in JPA (Java Persistence API) si JDO (Java Data Objects). Stabilizarea in acest fel a cadrelor de lucru pentru persistenta a avut mai multe efecte majore benefice: prezervarea portabilitatii SQL fata de diferitele motoare de baze de date obtinuta la nivelul standardului de baza JDBC, portabilitatea la nivelul solutiilor de implementare a modelelor de persistenta proiectate pe specificul diferitelor aplicatii, portabilitatea la nivelul stilului arhitectural (aplicatii pe doua straturi cu nivelul de persistenta rezident pe partea desktop vs. aplicatii pe trei straturi cu nivelul de persistenta plasat pe partea componentelor business distribuite EJB), standardizarea suportului pentru persistenta sub forma unor module funtionale dedicate la nivelul celor mai populare medii integrate de dezvoltare, NetBeans, Eclipse, JDeveloper etc.
In continuare vom descrie caracteristicile cele mai populare ale motoarelor bazate pe arhitectura JPA sprijinindu-ne pe suportul oferite de NetBeans in acest sens. Arhitectura stratificata pe care o luam in considerare are in vedere aplicatii container-desktop cu baze de date, insa, la nivelul specificatiilor O/R lucrurile nu difera dramatic fata de arhitecturile distribuite bazate pe containere EJB.
Primul lucru care trebuie facut se refera la verificarea existentei instalarii modulului NetBeans insarcinat cu gestionarea aspectelor legate de cadrul de lucru JPA. Astfel, din fereastra Module Manager deschisa apelata din meniul Tools se cauta in categoria Java EE intrarea Java Persistence API Support API. In cazul in care se constata lipsa acestui modul, instalare acestuia poate fi solicitata de la centrul NetBeans Update Center prin wizardul ce va fi invocat din butonul Update.
Al doilea lucru important consta in verificarea existentei posibilitatii de conectare la baza de date care (va) contine schema relationala de conectare. De exemplu, pentru o baza de date PostgreSQL se pot parcurge urmatorii pasi:
Indicarea driverului de lucru cu serverul PostgreSQL
Configurarea unei conexiuni de lucru cu serverul de baze de date pe baza driverului indicat mai sus prin apelarea optiunii Connect using din meniul contextual asociat acestuia:
Deschiderea conexiunii astfel create si eventual initierea unui dialog SQL din fereastra deschisa prin apelarea optiunii Execute Command din meniul contextula asociat
In fine, ultima actiune peremergatoare consta in:
1. Crearea sau deschiderea unui proiect de tipul aferent aplicatiilor Java SE (pentru aplicatii Java desktop) si apelarea optiunii New Entity Class din meniul contextual aferent pachetului destinat claselor din modelul afacerii aferente.
2. Intentia este declararea initiala entitatii Client ca simbioza intre o clasa numita Client si table CLIENTI in schema relationala avuta la dispozitie. Una din caracteristicile esentiale ale claselor desemnate ca entitati persistente este identitatea, motiv pentru care desemnam ca tip al proprietatii care va implementa acest aspect tipul Integer.
3. Inainte de a incheia procesul initial de declarare a entitatii Client, va trebui obligatoriu, pentru o mai facila modalitate de gestionare a informatiilor de configurare a stratului de persistenta, crearea unei unitati de persistenta. In NetBeans, obtinerea acesteia se realizeaza actionand butonul Create Persistence Unit din dialogul New Entity Class si implica trei specificatii importante: un nume (JAppPU), o biblioteca ce contine elementele de implementare a arhitecturii JPA pe o solutie concreta (TopLink) si o conexiune JDBC la baza de date (jdbc:postgresql ).
Ca urmare a acestui proces va rezulta
O clasa denumita Client "imbogatita" cu o serie de adnotari (@) care vor constitui baza maparii elementelor specifice modelului OO cu schema relationala
Fisierul persistence.xml. plasat in subdirectorul META-INF care va descrie in XML configuratiile unitatii de persistenta create. Editarea acestui fisiere se face cel mai comun in modul grafic sau text.
Elementele de configurare esentiale se refera la:
Providerul JPA <provider>;
Informatiile <properties> de conectare prin JDBC la baza de date;
Numele claselor entitati <class> care vor fi gestionate de stratul de persistenta.
Implicarea providerului TopLink ca suport de persistenta JPA in aplicatia noastra se manifesta prin adaugarea automata in resursele biblioteci ale acesteia a arhivelor JAR top-link-essential.jar si top-link-essential-agent.jar. Pentru ca la configurarea initiala a unitatii de persistenta am inidicat o conexiune JDBC pentru baza de date PostgreSQL va trebui sa adaugam in lista de biblioteci si driverul JDBC aferent .
Clasele de mapare vor fi construite dupa metodologia JavaBeans si vor contine adnotari (un fel de tag-uri - o facilitate noua oferita de versiunea 1.5 a limbajului Java) prin care vom preciza: tabela mapata, campurile cheie primara si, eventual, cum (si daca) trebuie generate valorile cheii primare. Adnotarile se realizeaza prin semnul urmat imediat (fara spatii) de cuvinte cheie specifice framework-ului cu care lucram (JPA - TopLink) adaugate inaintea declaratiei: unei clase, unui atribut sau unei metode.
Spre exemplu, clasa din listingul mai jos, rezultata din modificarea corepunzatoare a clasei Client initiale, specifica prin adnotari ca:
(1) este vorba de o entitate persistenta ( @Entity) care mapeaza tabela CLIENTI (numele tabelei poate fi prefixat de numele schemei in care rezida), (@Table)
(2) a carei cheie primara este data de atributul codcl[1](observam ca @Id este scris imediat inaintea declaratiei atributului propriu-zis) - valoarea acestui atribut va fi utilizata pentru a sincroniza starea obiectului Java din memoria interna a clientului cu inregistrarea corespunzatoare stocata pe serverul BD si pentru completarea criteriului implicit de cautare find(NumeClasaEntitate, valoareCheiePrimara).
import javax.persistence.Entity;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name='clienti') // specifica tabela mapata (de forma schema.tabela)
public class Client implements java.io.Serializable
public Client(Integer codcl)
public Integer getCodcl()
public String getDencl()
public String getAdresa()
public String getCodfiscal()
public String getTelefon()
public void setCodcl(Integer codcl)
public void setDencl(String dencl)
public void setAdresa(String adresa)
public void setCodfiscal(String codfiscal)
public void setTelefon(String telefon)
/* Metoda suprascrisa ce va fi utilizata pentru a obtine
* un string representativ (spre exemplu in cazul afisarii obiectelor
* ca elemente ale unui ComboBox)
*/
public String toString()
public boolean equals(Object object)
Client other = (Client)object;
if (this.codcl != other.codcl &&
(this.codcl == null ||
!this.codcl.equals(other.codcl)))
return false;
return true;
}
IMPORTANT !
De la bun inceput trebuie stiut ca: valoare unei chei (Id) poate fi data (sau generata) o singura data, la prima salvare a unui nou obiect in baza de date (atunci cand se creaza o noua inregistrare). Ulterior, stratul de persistenta JPA nu va permite modificarea valorii cheii (spre exemplu valoarea atributului codcl) pentru obiectele care au o inregistrare corespondenta in BD.
(asa cum am mai precizat chiar la inceput) este indicata utilizarea claselor de acoperire (Double, Integer sau BigDecimal) si nu a primitivelor (int, double, float, etc) pentru atributele numerice.
Vezi mai jos modalitatea de declarare a unei chei primare compuse
Iata si celelalte clase de mapare:
Clasa entitate Factura:
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name=' facturi')
public class Factura implements Serializable
public Factura(Integer nrfact)
public Integer getNrfact()
public Date getDatafact()
public Integer getCodcl()
public String getObs()
public void setNrfact(Integer valNoua)
public void setDatafact(Date valNoua)
public void setCodcl(Integer valNoua)
public void setObs(String valNoua)
public String toString()
public boolean equals(Object object)
Factura other = (Factura)object;
if (this.nrfact != other.nrfact &&
(this.nrfact == null ||
!this.nrfact.equals(other.nrfact)))
return false;
return true;
}
Clasa entitate Produs :
import javax.persistence.Entity;
import javax.persistence.GeneratorType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name='teste.produse')
public class Produs
public Produs(Integer codpr)
public Integer getCodpr()
public void setCodpr(Integer codpr)
public String getDenpr()
public void setDenpr(String denpr)
public String getUm()
public void setUm(String um)
public String getGrupa()
public void setGrupa(String grupa)
public Double getProctva()
public void setProctva(Double proctva)
public boolean equals(Object object)
Produs other = (Produs)object;
if (this.codpr != other.codpr &&
(this.codpr == null ||
!this.codpr.equals(other.codpr)))
return false;
return true;
}
In cazul in care avem de reprezentat o cheie primara compusa trebuie sa construim o clasa aditionala (dupa aceeasi metodologie JavaBeans), care sa contina atributele cheii primare. Aceasta noua clasa trebuie sa implementeze operatorul de egalitate (sa suprascrie metoda equals()) din urmatoarea ratiune: stocarea interna a obiectelor persistente in colectii de tip Map implica identificarea acestora in operatiunile de cautare prin intermediul campului/campurilor marcate @Id. Doua obiecte sunt egale daca au atributele marcate @Id egale. Odata construita clasa pentru cheia compusa,vom utiliza o adnotare la nivel de clasa: @IdClass si adnotarea @Id la nivelul atributelor ce constituie cheia primara din clasa de mapare. Iata implementarea pentru cazul tabelei LINIIFACT.
Clasa de indentitate pentru entitatea LinieFactura :
public class LinieFacturaPK
public LinieFacturaPK(Integer nrfact, Integer linie)
public Integer getNrfact()
public void setNrfact(Integer nrfact)
public Integer getLinie()
public void setLinie(Integer linie)
public boolean equals(Object object)
LinieFacturaPK other = (LinieFacturaPK)object;
if (this.linie != other.linie) return false;
if (this.nrfact != other.nrfact) return false;
return true;
}
Clasa entitate LinieFactura :
import java.io.Serializable;
import java.math.BigDecimal;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@Entity
@Table(name = 'liniifact',
uniqueConstraints = @UniqueConstraint(columnNames = ))
@IdClass(LinieFacturaPK.class)
public class LinieFactura implements Serializable
public LinieFactura(Integer nrfact, Integer linie)
public Integer getNrfact()
public void setNrfact(Integer nrfact)
public Integer getLinie()
public void setLinie(Integer linie)
public BigDecimal getCantitate()
public void setCantitate(BigDecimal cantitate)
public BigDecimal getPretunit()
public void setPretunit(BigDecimal pretunit)
public Integer getCodpr()
public void setCodpr(Integer codpr)
public boolean equals(Object object)
LinieFactura other = (LinieFactura)object;
if (this.linie != other.linie) return false;
if (this.nrfact != other.nrfact) return false;
return true;
}
Am discutat deja mai sus modul in care mediul NetBeans ofera sustinere pentru configurarea unitatii de persistenta. Am mentionat de asemenea principalii parametri:
provider - numele furnizorului (din sistemul de bibloteci suport) implementarii gestionarului stratului de persistenta JPA;
class - care introduce fiecare clasa entitate;
grupul de proprietati (property) specifice gestionarului stratului de persistenta dintre care se pot mentiona: proprietatile de configurare a conexiunii cu baza de date, dar si posibilitatea generarii sau sincronizarii modelului de entitati specificat prin grupul de parametri classes cu schema relationala de mapare ( de ex. toplink.ddl-generation).
Prin urmare, dupa ce am creat clasele entitatilor de persistenta, numele acestora trebuie specificate in fisierul persistence.xml care contine parametrii de configurare grupati sub forma unei unitati de persistenta (persistence unit) necesara pentru initializarea unei instante EntityManagerFactory. O astfel de instanta factory reprezinta sursa de creare a managerilor de persistenta sub forma obiectelor EntityManager care are ca atributiuni interogarea bazei de date si reconstituirea obiectelor persistente, precum si salvarea acestora intr-un context tranzactional.
Adaugarea claselor entitati in unitatea de persistenta si configurarea strategiei de generare a schemei relationale este facilitata de mediul NetBeans prin posibilitatea editarii grafice a fisiserului persistence.xml sub forma urmatorului formular:
de unde putem actiona butonul Add class si selectarea claselor entitati detectate automat de mediul NetBeans si listate in fereastra Add Entity. Se observa, de asemenea selectarea strategiei Create pentru sincronizarea cu schema bazei de date ceea ce inseamna ca, in cazul in care lipseste tabela aferenta unei entitati (specificata prin @Table(name = .) ) atunci va fi creata automat.
Desi, dupa cum am vazut mai sus, am ales strategia 'create-tables' pentru 'toplink.ddl-generation', scenariul pe care il vom urma in continuare presupune existenta tabelelor mentionate prin adnotarile @Table, ce urmeaza mentiunilor @Entity din codul sursa al claselor persistente, in schema bazei de date. Asa incat, deocamdata trebuie asigurata compatibilitatea tipurilor de date Java si SQL (de regula NUMERIC fara zecimale - Integer, NUMERIC cu zecimale - Double sau, mai bine, java.math.BigDecimal, DATE - cu java.util.Date plus mentiunea @Temporal(TemporalType.DATE), sau mai bine java.sql.Date ori java.sql.Timestamp, CHAR sau VARCHAR cu String etc.) precum si corespondeta nume atribute entitati - nume atribute relationale. In cazul in care aceasta ultima corespondeta nu se poate realiza direct, atunci inaintea fiecarui atribut al unei entitati se poate mentiona directiva @Column(name = "nume cimp tabela relationala").
Pentru test folosim clasa Main generata drept clasa principala, de initiere a executiei, proiectului aplicatiei noastre. Din fereastra de editare a codului sursa activam meniul contextual de unde invocam Persistence Use Entity Manager
Ca urmare vor fi adaugate in codul sursa mai multe linii de cod necesare pentru a invoca managerul de persistenta astfel:
import japp.model.Client;
import java.util.Collection;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class Main
public void persist(Object object) catch (Exception e) finally
}
Observam mai intai obtinerea unei instate EntityManagerFactory pe baza unitatii de persistenta si apoi invocarea contextului tranzactional pentru o instanta EntiyManager.
Inspirandu-ne din modelul tranzactional propus vom adauga metoda exemplu tranzactClienti in care:
dupa obtienerea managerului de persistenta (linia 2) clientul cu codul 1001 (valoare a cheii primare) este reconstituit din baza de date (4) prin intermediul metodei find(TipEntitate, valoare-cheie-primara) si modificat la fel ca un POJO[2] obisnuit si salvat in baza de date (9) in contextul tranzactiei deschise (7) inaintea operatiei proprizise si inchise cu success (10) sau prin anulare (13) daca survine vreo exceptie;
pentru adaugarea unui client nou acesta este instantiat folosind de preferinta un constructor care sa initializeze cel putin cheia primara (16) fiind apoi salvat intr-un context tranzactional la fel ca mai sus;
afisarea tuturor clientilor din baza de date, inclusiv cei doi mentionati mai sus, se poate face folosind metoda createQuery() oferita de managerul de persistenta (28). Aceasta metoda primeste ca argument o fraza in limbajul EJBQL si returneaza o instanta javax.persistence.Query ce ofera la randul ei pentru executie si gestionarea rezultatului executiei sub forma unei colectii de obiecte metoda getResultList();
stergerea unui obiect se face tot in context tranzactional dar invocand in locul metodei persist(Object) metoda remove(Object).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
public static void tranzactClienti() catch (Exception e) // adaugare client nou client = new Client(1201); client.setDencl('Client 201'); em.getTransaction().begin(); try catch (Exception e) // afisare lista clienti Collection<Client> clienti = em.createQuery('SELECT c FROM Client c').getResultList(); Iterator<Client> i = clienti.iterator(); while(i.hasNext()) System.out.println(i.next().getDencl()); // stergere client em.getTransaction().begin(); try catch (Exception e) finally } |
Modelul orientat obiect al entitatilor persistente rezultat pina acum are totusi cel putin un anumit neajuns: nematerializarea directa a relatiilor de asociere. Astfel ca, la acest moment, daca dorim sa obtinem facturile asociate unui client ar trebui procedat astfel:
Client client = em.find(Client.class, <clientPK>
Collection<Factura> facturi =
em.createQuery('SELECT f FROM Factura f WHERE f.codcl = :cod').
setParameter('cod', client.getCodcl()).getResultList();
sau invers, pentru cautarea clientului aferent unei facturi trebuie procedat astfel:
Factura factura = em.find(Factura.class, <facturaPK>
Client c = (Client) em.createQuery('SELECT c FROM Client WHERE c.codcl = :cod').
setParameter('cod', factura.getCodcl()).getSingleResult();
Dupa cum se observa si mai sus asocierea programatica a entitatilor Client si Factura se face urmand de fapt corepondeta cheie-straina cheie-primara care materializeaza asocierile dintre entitati la nivel relational. Din fericire, cadrul de lucru JPA ofera solutii elegante in acest sens pentru gestionarea automata a relatiiilor 1-M, M-1, 1-1 sau chiar M-M.
De exemplu:
Legatura Client - Factura este o legatura M 1 din perspectiva facturii sau 1 M din perspectiva clinetului. In orice caz, pentru simplificare cosideram drept sursa a asocierii acea entitate care corespunde tabelei ce contine cheia straina, in cazul nostru Factura. Atributul entitatii care reflecta cheia straina va fi inlocuit cu un atribut de tipul clasei destinatie a asociatiei (in cazul nostru Client) si va fi insotit de directiva @ManyToOne, dar si de directiva @JoinColumn care sa indice numele coloanei din tabela relationala desemnata drept cheie straina. La capatul celalalt al asociatiei, adica in clasa Client pentru exemplul nostru, va fi introdus un nou membru-colectie ce va fi precedat de directiva @OneToMany care va indica prin argumentul mappedBy numele atributului corepondent din entitatea asociata (Factura).
Clasa Factura va fi modificata dupa cum urmeaza:
// Eliminam membrul codcl si il inlocuim cu client
// private Integer codcl;
// public Integer getCodcl()
// public void setCodcl(Integer valNoua)
@ManyToOne
@JoinColumn(name = 'codcl')
private Client client;
public Client getClient()
public void setClient(Client client)
Iar clasa Client va fi modificata astfel:
// Adaugam membrul colectie facturi
@OneToMany(mappedBy = 'client')
private Collection<Factura> facturi;
public Collection<Factura> getFacturi()
public void setFacturi(Collection<Factura> facturi)
sau
@OneToMany(targetEntity = Factura.class, mappedBy = 'client')
private Collection facturi;
public Collection getFacturi()
public void setFacturi(Collection facturi)
Atributul cascade al adnotarii OneToMany are drept utilitate salvarea (si stergerea) in cascada si a elementelor de tipul clasei asociate.
La fel putem proceda in cazul asocierilor Factura-LinieFactura si Produs-LinieFactura. LinieFactura va fi sursa ambelor asociatii din moment ce tabela pe care este mapata contine ambele chei straine
FACTURI
LINIIFACT
PRODUS
nrfact
PK
nrfact
PK1/FK1
codpr
PK
datafact
linie
PK1/FK2
denpr
codcl
FK
codpr
FK
um
cantitate
grupa
pretunit
proctva
Ca urmare in entitatea LinieFactura vom defini un nou membru de tip Factura si vom inlocui membrul codpr cu un altul de tip Produs:
// private Integer codpr;
// public Integer getCodpr()
// public void setCodpr(Integer codpr)
@ManyToOne @JoinColumn(name = 'codpr')
private Produs produs;
public Produs getProdus()
public void setProdus(Produs produs)
@ManyToOne
@JoinColumn(name = 'nrfact', insertable = false, updatable = false)
private Factura factura;
public Factura getFactura()
public void setFactura(Factura factura)
In clasa Factura vom adauga colectia liniifactura:
@OneToMany(mappedBy = 'factura', cascade = CascadeType.ALL)
private Collection<LinieFactura> liniifactura = new ArrayList();
public Collection<LinieFactura> getLiniifactura()
public void setLiniifactura(Collection<LinieFactura> liniifactura)
Atributul cascade al adnotarii OneToMany are drept utilitate salvarea (si stergerea) in cascada si a elementelor de tip LinieFactura asociate cu cu obiectul-entitate factura in structura caruia este definita colectia liniifactura.
In clasa Produs vom adauga membrul liniifacturi
@OneToMany(mappedBy = 'produs')
private Collection<LinieFactura> liniifacturi;
public Collection<LinieFactura> getLiniifacturi()
public void setLiniifacturi(Collection<LinieFactura> liniifacturi)
Legaturile entitatii LinieFactura pot fi insa mult mai elegant reprezentate daca vom lua in considerare asocierea Factura-Produs de tip M M, clasa LinieFactura intermediind de fapt acest tip de asociatie, fiind mai apropiata de semantica clasei-asociatie din UML. In acest caz vom adaga colectia produse in entitatea Factura si colectia facturi in entitatea Produs pentru a le lega direct.
Astfel in clasa Factura vom adauga urmatoarea secventa de cod:
@ManyToMany
@JoinTable(
name = 'liniifact',
joinColumns = @JoinColumn(name = 'nrfact'),
inverseJoinColumns = @JoinColumn(name = 'codpr')
)
private Collection<Produs> produse;
public Collection<Produs> getProduse()
public void setProduse(Collection<Produs> produse)
iar in clasa Produs vom adauga urmatoarea secventa de cod:
@ManyToMany(mappedBy = 'produse')
private Collection<Factura> facturi;
public Collection<Factura> getFacturi()
public void setFacturi(Collection<Factura> facturi)
Clasa LinieFactura ar putea ramane neschimbata, sau, pentru un spor de eleganta, ar putea fi transformata astfel: cele doua atribute marcate cu @Id drept compunand cheia primara vor fi inlocuite cu un singur atribut pe care il vom numi simplu id de tipul clasei cheie primara LinieFacturaPK si care va fi marcat cu @EmbeddedId. De asemenea vom elimina directiva @IdClass din entitate si vom adauga directiva @Emeddable in clasa cheie primara pe care o vom obliga sa implementeze si interfata java.io.Serializable. In final aceste clase vor arata astfel:
Clasa entitate LinieFactura:
import java.io.Serializable;
import java.math.BigDecimal;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@Entity
@Table(name = 'liniifact',
uniqueConstraints = @UniqueConstraint(columnNames = ))
//@IdClass(LinieFacturaPK.class)
public class LinieFactura implements Serializable{
// @Id
// private Integer nrfact;
// @Id
// private Integer linie;
// public Integer getNrfact()
// public void setNrfact(Integer nrfact)
// public Integer getLinie()
// public void setLinie(Integer linie)
@EmbeddedId
private LinieFacturaPK id;
public LinieFacturaPK getId()
public void setId(LinieFacturaPK id)
private BigDecimal cantitate;
private BigDecimal pretunit;
public LinieFactura()
public LinieFactura(LinieFacturaPK id)
// public LinieFactura(Integer nrfact, Integer linie)
public BigDecimal getCantitate()
public void setCantitate(BigDecimal cantitate)
public BigDecimal getPretunit()
public void setPretunit(BigDecimal pretunit)
// private Integer codpr;
// public Integer getCodpr()
// public void setCodpr(Integer codpr)
@ManyToOne
@JoinColumn(name = 'codpr')
private Produs produs;
public Produs getProdus()
public void setProdus(Produs produs)
@ManyToOne
@JoinColumn(name = 'nrfact')
private Factura factura;
public Factura getFactura()
public void setFactura(Factura factura)
Clasa cheie primara LinieFacturaPK:
import javax.persistence.Embeddable;
@Embeddable
public class LinieFacturaPK implements java.io.Serializable
Luind in considerare ultima versiune a claselor entitati putem reconstitui intregul complex de obiecte persistente pornind de la o singura interogare, de exemplu asupra clientilor:
public static void geEntities1()
}
em.close();
}
care ar trebuie sa produca un rezultat cum ar fi:
Client 2 SA
+-- Factura 1113 pe care s-au vindut:
| +-- Produs 2
+-- Factura 2113 pe care s-au vindut:
| +-- Produs 2
+-- Factura 3113 pe care s-au vindut:
+-- Produs 2
Client 3 SRL
+-- Factura 1119 pe care s-au vindut:
| +-- Produs 2
| +-- Produs 3
| +-- Produs 4
| +-- Produs 5
+-- Factura 2119 pe care s-au vindut:
| +-- Produs 2
| +-- Produs 3
| +-- Produs 4
| +-- Produs 5
+-- Factura 3119 pe care s-au vindut:
+-- Produs 2
+-- Produs 3
+-- Produs 4
+-- Produs 5
s.a.m.d
sau secventa de cod
public static void geEntities1()
}
em.close();
}
care ar trebuie sa produca un rezultat cum ar fi:
Client 2 SA
+-- Factura 1113 pe care s-au vindut:
| +-- Produs 2 in cantitatea 100 la pretul 975.00
+-- Factura 2113 pe care s-au vindut:
| +-- Produs 2 in cantitatea 120 la pretul 975.00
+-- Factura 3113 pe care s-au vindut:
+-- Produs 2 in cantitatea 120 la pretul 975.00
Client 3 SRL
+-- Factura 1119 pe care s-au vindut:
| +-- Produs 2 in cantitatea 35 la pretul 1090.00
| +-- Produs 3 in cantitatea 40 la pretul 700.00
| +-- Produs 4 in cantitatea 50 la pretul 1410.00
| +-- Produs 5 in cantitatea 750 la pretul 6300.00
+-- Factura 2119 pe care s-au vindut:
| +-- Produs 2 in cantitatea 35 la pretul 1090.00
| +-- Produs 3 in cantitatea 40 la pretul 700.00
| +-- Produs 4 in cantitatea 55 la pretul 1410.00
| +-- Produs 5 in cantitatea 755 la pretul 6300.00
+-- Factura 3119 pe care s-au vindut:
+-- Produs 2 in cantitatea 35 la pretul 1090.00
+-- Produs 3 in cantitatea 40 la pretul 700.00
+-- Produs 4 in cantitatea 55 la pretul 1410.00
+-- Produs 5 in cantitatea 755 la pretul 6300.00
s.a.m.d
Daca pana acum am folosit strategia maparii claselor entitati pe structuri relationale preexistente in schema bazei de date de lucru, in continuare vom exploata suportul NetBeans pentru generarea entitatilor pe baza tabelelor de mapare.
Tinta noastra este (1) generarea initiala a entitatii DocumentIncasare pe baza tabelei INCASARI si entitatii IncasareFactura pe baza tabelei INCASFACT care reprezentare a starilor asociatiei Factura - DocumentIncasare de tip M M; (2) dupa care vom crearea sub-clasele entitati OrdinPlata, Chitanta si CEC derivate din clasa DocumentIncasare si (3) definirea corepunzatoare a tabelelor in schema relationala pentru noile entitati.
Generarea entitatilor pe baza tabelelor existente in schema relationala
Avand la dispozitie deja schema relationala normalizata corespunzatoare modelului entitatilor persistente, putem profita astfel incat sa facem economie in ceea ce priveste efortul de codificare bazandu-ne in acest sens tot pe suportul NetBeans.
In acest sens trebuie sa parcurgem urmatorii pasi:
invocarea optiunii New Entity Classes from Databases din meniul contextual;
specificarea conexiunii JDBC si selectarea tabelelor pe baza carora vor fi construite entitatile persistente (caseta de bifare Include Related Tables ajuta la depistarea tabelor relationate pe baza cheilor straine ce vor genera asociatiile din modelul entitatilor). In acesta etapa aducem tabelele incasari si incasfact in lista Selected Tables fara a bifa si Include Related Tables (FACTURI este deja mapata pe entitatea Factura);
stabilirea numelor exacte si a pachetelor pentru clasele entitasi ce vor fi generate, in cazul nostru DocumentIncasare in corepondenta cu tabela incasari si IncasareFactura in corespondeta cu tabela incasfact;
in final vom analiza noile clase si celelalte elemente adaugate in proiect.
Astfel observam generarea fisierelor cod-sursa DocumenteIncasare.java, IncasareFactura.java si IncasareFacturaPK.java. De asemenea cele doua entitati deja sunt adaugate in lista claselor unitatii de persistenta (este actualizat fisierul persistence.xml). In fine, in special pentru a putea lucra cu metadatele schemei bazei de date chiar daca mediul NetBeans nu are o conexiune deschisa permanenta, este generat fisierul javadeveloper_Japp.dbschema stocat in acelasi director META-INF al aplicatiei ca si persistence.xml.
In continuare sa analizam mai in detaliu codul generat, care, excluzand comentariile ar putea fi urmatorul:
clasa entitate DocumentIncasare, are o cheie primara (id) simpla pe baza atributului codinc si, de asemenea, are o asociere 1-M cu clasa IncasareFactura prin atributul incasareFacturaCollection. In plus, pentru fiecare atribut al entitatii a fost creata cate o interogare a carui nume, bazat pe sablonul findBy<NumeAtribut> poate fi invocat direct de catre o instanta EntityManager prin metoda createNamedQuery().getResultList() sau getSingleResult() - cum este cazul interogarii dupa atributul cheie primara;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Date;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name = 'incasari')
@NamedQueries( )
public class DocumentIncasare implements Serializable {
@Id @Column(name = 'codinc', nullable = false)
private BigDecimal codinc;
@Column(name = 'datainc')
@Temporal(TemporalType.DATE)
private Date datainc;
@Column(name = 'coddoc')
private String coddoc;
@Column(name = 'nrdoc')
private String nrdoc;
@Column(name = 'datadoc')
@Temporal(TemporalType.DATE)
private Date datadoc;
@OneToMany(cascade = CascadeType.ALL, mappedBy = 'documentIncasare')
private Collection<IncasareFactura> incasareFacturaCollection;
public DocumentIncasare()
public DocumentIncasare(BigDecimal codinc)
public BigDecimal getCodinc()
public void setCodinc(BigDecimal codinc)
public Date getDatainc()
public void setDatainc(Date datainc)
public String getCoddoc()
public void setCoddoc(String coddoc)
public String getNrdoc()
public void setNrdoc(String nrdoc)
public Date getDatadoc()
public void setDatadoc(Date datadoc)
public Collection<IncasareFactura> getIncasareFacturaCollection()
public void setIncasareFacturaCollection
(Collection<IncasareFactura> incasareFacturaCollection)
@Override
public int hashCode()
@Override
public boolean equals(Object object)
DocumentIncasare other = (DocumentIncasare)object;
if (this.codinc != other.codinc &&
(this.codinc == null ||
!this.codinc.equals(other.codinc))) return false;
return true;
}
@Override
public String toString()
in cazul entitatii IncasareFactura se observa de asemenea declararea grupului de interogari aferente fiecarui atribut si legatura inversa cu entitatea DocumentIncasare pe baza atributului documentIncasare (atentie la directiva @JoinColum prezenta ca urmare a faptului ca tabela de mapare incasfact detine cheia straina sursa a asociatiei . Cheia primara este declarata pe baza mecanismului @EmbeddedId, la fel cum am procedat in cazul entitatii LinieFactura, clasa desemnata a implementa identitatea fiind clasa @Embeddable IncasareFacturaPK
import java.io.Serializable;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
@Entity @Table(name = 'incasfact')
@NamedQueries( )
public class IncasareFactura implements Serializable
public IncasareFactura(IncasareFacturaPK incasareFacturaPK)
public IncasareFactura(IncasareFacturaPK incasareFacturaPK, BigDecimal transa)
public IncasareFactura(int nrfact, int codinc)
public IncasareFacturaPK getIncasareFacturaPK()
public void setIncasareFacturaPK(IncasareFacturaPK incasareFacturaPK)
public BigDecimal getTransa()
public void setTransa(BigDecimal transa)
public DocumentIncasare getDocumentIncasare()
@Override
public int hashCode()
@Override
public boolean equals(Object object)
IncasareFactura other = (IncasareFactura)object;
if (this.incasareFacturaPK != other.incasareFacturaPK &&
(this.incasareFacturaPK == null ||
!this.incasareFacturaPK.equals(other.incasareFacturaPK)))
return false;
return true;
}
@Override
public String toString()
Clasa de implementare a identitatii entitatii IncasareFactura
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@Embeddable
public class IncasareFacturaPK implements Serializable {
@Column(name = 'codinc', nullable = false)
private int codinc;
@Column(name = 'nrfact', nullable = false)
private int nrfact;
public IncasareFacturaPK()
public IncasareFacturaPK(int nrfact, int codinc)
public int getCodinc()
public void setCodinc(int codinc)
public int getNrfact()
public void setNrfact(int nrfact)
@Override
public int hashCode()
@Override
public boolean equals(Object object)
IncasareFacturaPK other = (IncasareFacturaPK)object;
if (this.nrfact != other.nrfact) return false;
if (this.codinc != other.codinc) return false;
return true;
}
@Override
public String toString()
Daca dorim si implementarea asociatiei M-M dintre Factura si DocumentIncasare, atunci va trebui sa adaugam in clasa Factura membru documente:
@ManyToMany
@JoinTable(
name = 'incasfact',
joinColumns = @JoinColumn(name = 'nrfact'),
inverseJoinColumns = @JoinColumn(name = 'codinc')
)
private Collection<DocumentIncasare> documente;
public Collection<DocumentIncasare> getDocumente()
public void setDocumente(Collection<DocumentIncasare> documente)
in DocumentIncasare atributul facturi:
@ManyToMany(mappedBy = 'documente')
private Collection<Factura> facturi;
public Collection<Factura> getFacturi()
iar in IncasareFactura:
@JoinColumn(name = 'nrfact', referencedColumnName = 'nrfact',
insertable = false, updatable = false)
@ManyToOne
private Factura factura;
public Factura getFactura()
public void setFactura(Factura factura)
Un test pentru a afisa de exemplu ordinele de plata ar putea arata astfel:
public static void geEntities3()
}
em.close;
}
Avind ca rezultat :
Nr. OP 111 din data Wed Aug 10 00:00:00 EEST 2005
+-- Factura incasata 1111 cu suma 53996.00
+-- Factura incasata 1118 cu suma 101975.00
Nr. OP 333 din data Tue Aug 09 00:00:00 EEST 2005
+-- Factura incasata 1117 cu suma 97500
+-- Factura incasata 1118 cu suma 100000.00
+-- Factura incasata 1120 cu suma 7315.00
Nr. OP 555 din data Wed Aug 10 00:00:00 EEST 2005
+-- Factura incasata 1113 cu suma 106275.00
Nr. OP 666 din data Thu Aug 11 00:00:00 EEST 2005
+-- Factura incasata 1117 cu suma 3696.00
(3) Crearea entitatilor din ierarhia de mostenire si generarea structurilor relationale de mapare a ierarhiei de mostenire
Exista mai multe strategii de mapare in tabele relationale a unei ierarhii de mostenire. Cea mai avantajoasa, dupa parerea noastra, implica :
pe langa tabela corepunzatoare clasei parinte, trebuie sa fie creata cite o tabela pentru fiecare sub-clasa asa incat cheia primara din tabela clasei parinte sa fie reluata in fiecare tabela corepunzatoare claselor copil si, in celasi timp, cheia primara din tabelele claselor copil sa fie declarata cheie straina catre cheia primara a tabelei "mama" a super-clasei:
CREATE TABLE ordineplata (
codinc NUMERIC(8) PRIMARY KEY,
bancaEmitenta VARCHAR(20),
nrcontPlatitor VARCHAR(20),
bancaIncasatoare VARCHAR(20),
nrcontIncasator VARCHAR(20)
CREATE TABLE cecuri (
codinc NUMERIC(8) PRIMARY KEY,
bancaEmitenta VARCHAR(20),
nrcont VARCHAR(20)
CREATE TABLE chitante (
codinc NUMERIC(8) PRIMARY KEY,
casier VARCHAR(20)
ALTER TABLE chitante
ADD CONSTRAINT fk_chit FOREIGN KEY (codinc) REFERENCES incasari(codinc);
ALTER TABLE cecuri
ADD CONSTRAINT fk_cec FOREIGN KEY (codinc) REFERENCES incasari(codinc);
ALTER TABLE ordineplata
ADD CONSTRAINT fk_op FOREIGN KEY (codinc) REFERENCES incasari(codinc);
super-clasa entitate va fi marcata prin directivele @Inheritence ce va indica strategia de mostenire (JOINED), si @DiscriminatorColumn care va indica acea coloana din tabela coreponzatoare (daca nu exista va fi creata) ce va diferentia prin valorile ei inregistrarile corepunzatoare fiecarei subclase, de exemplu coloana coddoc din tabela INCASARI cu valorile CEC, OP, CHIT corespunzatoare celor trei subclase CEC, OrdinPlata, Chitanta:
@Entity
@Table(name = 'incasari')
@Inheritance (strategy = InheritanceType.JOINED)
@DiscriminatorColumn(
name = 'coddoc', discriminatorType = DiscriminatorType.STRING
@NamedQueries( )
public class DocumentIncasare implements Serializable
fiecare subclasa va fi marcata ca entitate cu @Entity, va indica tabela de mapare cu @Table si va preciza valoarea corespunzatoare coloanei cu rol de discriminator prin @DiscriminatorValue, fara a defini insa explicit o cheie primara proprie @Id si fara a relua definitia cheii primare mostenite din super-clasa:
Subclas CEC:
import java.math.BigDecimal;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table (name = 'cecuri')
@DiscriminatorValue('CEC')
public class CEC extends DocumentIncasare
public CEC(BigDecimal codinc)
public String getBancaEmitenta()
public void setBancaEmitenta(String bancaEmitenta)
public String getNrcont()
public void setNrcont(String nrcont)
Subclasa Chitanta:
import java.math.BigDecimal;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table (name = 'chitante')
@DiscriminatorValue('CHIT')
public class Chitanta extends DocumentIncasare{
private String casier;
public Chitanta()
public Chitanta(BigDecimal codinc)
public String getCasier()
public void setCasier(String casier)
Subclasa OrdinPlata:
import java.math.BigDecimal;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table (name = 'ordineplata')
@DiscriminatorValue('OP')
public class OrdinPlata extends DocumentIncasare
public OrdinPlata(BigDecimal codinc)
public String getBancaEmitenta()
public void setBancaEmitenta(String bancaEmitenta)
public String getNrContPlatitor()
public void setNrContPlatitor(String nrcontPlatitor)
public String getBancaIncasatoare()
public void setBancaIncasatoare(String bancaIncasatoare)
public String getNrContIncasator()
public void setNrContIncasator(String nrContIncasator)
Nu trebuie uitata adaugarea noilor entitati in unitatea de persistenta, adica actualizarea fisierului persistence.xml.
In fine, un simplu test pentru a instatia o subclasa ar putea arata astfel:
public static void newDocumentIncasare() catch (Exception e) finally
}