En aquest article presentarem diversos exemples de com anotar una classe pertanyent al model del domini d’un sistema d’informació (veure Domain Model: De l’anàlisi al disseny i Domain Model: Del disseny a la implementació persistent) amb anotacions de JPA (Java Persistence API) per tal de mantenir sincronitzades les dades en memòria amb les dades existents a la base de dades. No és la meva intenció descriure en detall com funciona JPA sino, simplement, mostrar com es pot afegir les anotacions de JPA al model del domini per fer-lo persistent. Com a exemple, prendrem el model conceptual de la base de dades Northwind que es distribueix amb MSAccess i SQLServer.
Entitats del domini
El patró Domain Model ens diu que tota entitat del model conceptual tindrà una classe software (en el nostre cas, una classe java) que la representarà. Prenem com a exemple, l’entitat Employee:1
2
3
public class Employee implements java.io.Serializable {
...
}
JPA ens diu que, a cada classe del model del domini que representi una entitat, haurem d’afegir l’anotació @Entity per tal de poder-la reconèixer com a tal i @Table per tal de saber a quina taula es guardaran les dades d’aquella entitat:
1
2
3
4
5
@Entity
@Table(name="Employees",schema="dbo",catalog="Northwind")
public class Employee implements java.io.Serializable {
...
}
JPA defineix una sèrie de valors per defecte per tal de minimitzar el volum de les anotacions. En el nostre cas, a l’anotació @Table hagués fet servir el nom de la classe com a nom de taula i l’esquema i catàleg per defecte de la base de dades.
Cada entitat tindrà una sèrie d’atributs, que es correspondran a les columnes de la base de dades i a les propietats de la classe java. Així doncs, Employee tindrà diversos atributs (employeeid, lastName, firstName, etc.). Per cada atribut hem de declarar un mètode getter per a obtenir el valor i un mètode setter per a modificar-lo. Típicament, la implementació d’aquests mètodes manipularà un atribut privat de l’objecte:
1
2
3
4
5
6
7
8
9
10
private String city;
...
@Column(name="City", length=15)
public String getCity() {
return this.city;
}
public void setCity(String city) {
this.city = city;
}
L’anotació @Column ens permet indicar amb més detall informació sobre la columna, concretament el nom. La regla per defecte (si no indiquem @Column) és mapejar la propietat a la columna amb el mateix nom. També es pot afegir informació addicional per a la generació de la definició de taules a partir de les classes i les metadades de mapeig. Opcionalment, també podriem mapejar les columnes a partir dels atributs de la classe java enlloc de les propietats. Per a fer això, hauriem de posar les anotacions als atributs en comptes dels setters. Finalment, cal tenir en compte que tots els atributs de l’entitat s’han de mapejar o bé a propietats o bé a atributs (JPA decidirà on buscar l’anotació en funció d’on trobi l’anotació de l’identificador).
Identificadors i "ValueTypes"
L’identificador és la propietat que identifica un objecte (en terminologia de bases de dades, la clau primària). És una propietat especial ja que és la que permet al subsistema de persistència determinar si dues instàncies d’una classe representen o no la mateixa instància d’una entitat del domini (és a dir, si han d’anar a la mateixa fila de la taula). S’ha d’anotar amb l’anotació Id:
1
2
3
4
5
6
7
8
9
...
@Id
@Column(name="EmployeeID", unique=true, nullable=false)
public int getEmployeeId() {
return this.employeeId;
}
public void setEmployeeId(int employeeId) {
this.employeeId = employeeId;
}
En el cas de Hibernate, existeix la possibilitat de fer servir un identificador artificial (que no es correspongui a cap atribut del model conceptual) i fer que Hibernate generi els identificadors per als diferents objectes de manera automàtica.
Els “Value Types” son tipus de dades que, tot i que es corresponen a una classe java, no tenen entitat pròpia al domini (típicament no tenen identificador ni taula pròpia al model lògic de la base de dades). El cas paradigmàtic és l’adreça. En el nostre exemple, podem veure que les taules Employees,Suppliers i Customers comparteixen un conjunt d’atributs relatius a l’adreça: (address, city, region, postalCode i country). Si volguessim agrupar aquests atributs en una mateixa classe, l’hauriem de mapejar com a objecte i afegir l’anotació @Embedded a la propietat de l’entitat (si no ho fem, Hibernate mirarà igualment si la classe té l’anotació @Embeddable). Per exemple, a Employee tindriem:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Embedded
public Address getAddress() {
return this.address;
}
public void setAddress(Address value) {
this.address = value;
}
@Embeddable
public class Address implements java.io.Serializable {
private String address;
//...
@Column(name="Address", length=60)
public String getAddress() {
return this.address;
}
public void setAddress(String address) {
this.address = address;
}
//...
}
Associacions
Només hem de tenir en compte associacions binàries ja que les associacions no binàries ja les haurem convertit en associacions binàries a l’hora de dissenyar el model del domini. Si que haurem de distingir, però, segons les possibles multiplicitats màximes (a un o a molts).
Multiplicitat màxima un
Al nostre exemple, tenim una associació entre Employee i Order on un Employee té moltes Order però una Order només té un Employee. Hauriem de mapejar, per una banda, la navegabilitat d’Order cap a Employee i, per altra, en el sentit invers. ManyToOne L’associació d’Order cap a Emploeyee és un cas d’associació ManyToOne, que és aquella en que, a la classe actual només hi ha un valor associat mentre que a l’altra en tenim més d’un. JPA defineix l’anotació ManyToOne per indicar una associació d’aquest tipus. Opcionalment, podem indicar la classe a la qual apunta l’associació o podem deixar que JPA la dedueixi a partir del tipus de la propietat anotada. L’anotació JoinColumn ens permet indicar a través de quina columna hem de fer la join (és a dir, quina columna conté la clau forana cap a Employee):1
2
3
4
5
6
7
8
9
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="EmployeeID")
public Employee getEmployee() {
return this.employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
Hem mapejat l’associació com a “lazy loading”. Això significa que, quan carreguem una instància d’Order, no carregarem l’Employee associat fins que hi accedim.
OneToManyPer mapejar l’associació en sentit invers d’Employee a Order) hem de considerar l’associació com a OneToMany i indicar que aquesta associació està mapejada a una altra entitat
1
2
3
4
5
6
7
@OneToMany(mappedBy="employee")
public Set getOrders() {
return this.orders;
}
public void setOrders(Set orders) {
this.orders = orders;
}
En aquest exemple, el subsistema de JPA deduirà, a partir del tipus de la propietat (Set) que l’entitat on està mapejada l’associació en sentit invers és Order i, per tant, no cal indicar-ho.
Multiplicitat màxima molts
Com a exemple d’associació M:N (ManyToMany) agafarem l’associació entre Employee i Territory. Hem d’indicar el nom de la taula intermitja on es guardaran les instàncies de l’associació: No s’ha indicat a l’exemple l’anotació @JoinTable
1
2
3
4
5
@JoinTable(
name="EmployeeTerritories",
joinColumns= @JoinColumn(name="EmployeeID"),
inverseJoinColumns = @JoinColumn(name="TerritoryID")
)
i anotar les dues propietats
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// A Territory
@ManyToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="territories")
public Set getEmployees() {
return this.employees;
}
public void setEmployees(Set employees) {
this.employees = employees;
}
//A Employee
@ManyToMany(mappedBy="employees")
public Set getTerritories() {
return this.territories;
}
public void setTerritories(Set territories) {
this.territories = territories;
}
Conclusions
Hem vist un principi de com es pot anotar un model del domini per indicar les metadades de persistència, però no hem entrat en altres temes importants que s’han de decidir i dependran del sistema que estiguem desenvolupant com ara les estratègies de generació (podem generar el model del domini a partir del model físic de la base de dades o viceversa), les diferents possibilitats de mapeig d’associacions i col·lecions, el tractament de l’herència, les diferents estratègies de generació d’identificadors, etc. Un bon llibre on trobar més informació al respecte és Java Persistence with Hibernate, escrit per dos dels autors de Hibernate i que conté més de 800 pàgines d’informació sobre l’ús de Hibernate i JPA.