# Hibernate: Mapatge de relacions. Anotacions.
## Anotacions
Les anotacions java són un mecanisme de mapatge alternatiu al mecanisme de treballar amb dos fitxers, és a dir, el bean/classe java i l’arxiu .hbm.xml.
La idea és utilitzar una sèrie de metadades sobre el bean, de tal forma que podrem prescindir de l’arxiu de mapatge.
Així, dins la carpeta **model** situada a com/hibernate/iticbcn, procedirem a crear ara una nova classe anomenada Empleats. L'objectiu serà crear i mapejar una nova taula d'Empleats dins la base de dades de tasques que vam crear en sessions anteriors.
El codi de la classe fent ús d'anotacions serà el següent. No incorpora getters i setters, tot i que és aconsellable posar-los:
```
package com.hibernate.iticbcn.model;
import java.io.Serializable;
import java.time.LocalDate;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name="employees")
public class Empleats implements Serializable {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
@Column(name="emp_no")
private int numeroEmpleat;
@Column(name="birth_date")
private LocalDate DataNaixement;
@Column(name="first_name")
private String nom;
@Column(name="last_name")
private String cognoms;
@Column(name="gender")
private char genere;
@Column(name="hire_date")
private LocalDate dataAlta;
public Empleats(LocalDate dataNaixement, String nom, String cognoms, char genere, LocalDate dataAlta) {
this.DataNaixement = dataNaixement;
this.nom = nom;
this.cognoms = cognoms;
this.genere = genere;
this.dataAlta = dataAlta;
}
}
```
## Significat de les anotacions
Ara anem a desxifrar que signifiquen o la què fan les anotacions emprades:
* **@Entity**: vol dir que la classe es comporta com una nova entitat.
* **@Table**: la taula on es desarà l’entitat declarada a l’@Entity. Fiquem el nom que ha de tenir la taula fent name = "nom_taula".
* **@Id** : vol dir que aquell atribut fa de clau primària.
* **@GeneratedValue**: vol dir que aquell camp és autonumèric
* **@Column**: vol dir que aquell atribut del bean estarà mapejat en un camp de la taula. Quan posem name=”” al costat (entre parèntesi), significa que el camp té un nom diferent al que s'ha emprat a l'atribut del bean.
* **@Transient**: vol dir que aquell atribut no s’ha de persistir a la taula o que no volem que tingui equivalent a la taula.
Aquestes anotacions (a més d'altres) són pròpies dels javabeans en el contexte d'ús d'hibernate. No les confongueu amb les que fan servir altres frameworks com el cas d'Spring, ja que Spring incorpora pròpies. Ho veurem a la UF4.
## Canvis a l'estructura i fitxers de configuració
Segons la versió d'hibernate que utilitzem, podria ser necessari modificar l'**HibernateUtil** del següent mode, afegint el mètode addAnnotatedClass(nom_classe.class). Es farà tants cops com classes amb anotacions tinguem:
```
private static SessionFactory buildSessionFactory(){
try{
return new Configuration().configure().addAnnotatedClass(Empleats.class)
.buildSessionFactory(
new StandardServiceRegistryBuilder().configure().build());
}
catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed."+ex);
throw new ExceptionInInitializerError(ex);
}
}
```
En la versió que utilitzem a hores d'ara sembla no ser necessari fer canvis a la HibernateUtil
Per altra banda, **el fet d'utilitzar anotacions fa que poguem prescindir dels arxius .hbm.xml**, ja que són les anotacions les que s'encarregaran d'establir el mapatge.
Ja per últim, a l'arxiu hibernate.cfg.xml si que cal declarar quines classes amb anotacions fan mapatge. **Fem ús de les etiquetes *mapping class*, situades al final**. Pel que fa a les classes que utilitzen anotacions, no cal fer ús ni declarar res al mapping resource ja que no disposem d'arxius .hbm.xml:
```
<session-factory>
<!-- Mapeo DENTRO DE LA CLASE -->
<mapping class="com.hibernate.iticbcn.model.Empleats"/>
</session-factory>
```
## Canvis a la Classe App/Main. Execució.
Com ja vam fer amb tasques, anem a persistir un nou empleat. Pensem que en aquest moment no hem creat la taula d'empleats a la base de dades, per tant deixarem que hibernate ho faci:
```
package com.hibernate.iticbcn;
import org.hibernate.SessionFactory;
import com.hibernate.iticbcn.model.Empleats;
import java.time.LocalDate;
import java.time.Month;
import org.hibernate.Session;
public class App
{
public static void main( String[] args )
{
SessionFactory sesion = HibernateUtil.getSessionFactory();
Session session = sesion.openSession();
session.beginTransaction();
Empleats e = new Empleats(LocalDate.of(1970, Month.APRIL,3),"Pere" ,"Martinez", 'M',LocalDate.now());
System.out.println(e);
session.persist(e);
//commit y cierre de sesion
session.getTransaction().commit();
session.close();
}
}
```
L'execució d'aquest codi ens donarà com a resultat això:
```
Hibernate: create table employees (emp_no integer not null auto_increment, birth_date date, last_name varchar(255), hire_date date, gender char(1), first_name varchar(255), primary key (emp_no)) engine=InnoDB
Hibernate: create table tasks (idTask integer not null auto_increment, descTask varchar(255), numHours integer, finalitzada bit, primary key (idTask)) engine=InnoDB
com.hibernate.iticbcn.model.Empleats@2a7ebe07
Hibernate: insert into employees (birth_date,last_name,hire_date,gender,first_name) values (?,?,?,?,?)
```
I si anem a MariaDB, veurem la taula creada i el registre inserit:
```
ariaDB [tasks]> show columns from employees;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| emp_no | int(11) | NO | PRI | NULL | auto_increment |
| birth_date | date | YES | | NULL | |
| last_name | varchar(255) | YES | | NULL | |
| hire_date | date | YES | | NULL | |
| gender | char(1) | YES | | NULL | |
| first_name | varchar(255) | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
6 rows in set (0,001 sec)
```
```
MariaDB [tasks]> select * from employees;
+--------+------------+-----------+------------+--------+------------+
| emp_no | birth_date | last_name | hire_date | gender | first_name |
+--------+------------+-----------+------------+--------+------------+
| 1 | 1980-04-03 | Martin | 2024-03-05 | M | Pablo |
| 2 | 1970-04-03 | Martinez | 2024-03-11 | M | Pere |
+--------+------------+-----------+------------+--------+------------+
2 rows in set (0,001 sec)
```
## Mapatge de relacions
Les relacions poden ser:
### **Unidireccionals: OneToOne**
D’una entitat (d’origen) s’obté una altra entitat (de destí). En altres paraules, una de les entitats és la que té referència directa a l'altra.
Per identificar una relació així, fem servir l'anotació **@OneToOne**. I ho fem a una de les dues classes.
Imaginem una taula de despatxos o llocs de treball on cada empleat té associat un únic despatx. Però no volem saber o no necessitem que la taula despatx contingui informació de l'empleat que l'ocupa. Per resoldre-ho emprem una relació en una direcció, d'empleat a despatx.
Comencem per l'entitat Despatx, on notarem que és una entitat sense cap lligam amb Empleats.
```
package com.iticbcn.hibernate.Model;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import java.io.Serializable;
import jakarta.persistence.Id;
import jakarta.persistence.Column;
@Entity
@Table(name="despatxos")
public class Despatx implements Serializable{
@Id
@Column(name="off_no")
private int idDespatx;
@Column(name="location")
private String lloc;
public Despatx(){}
public Despatx(int idDespatx, String lloc) {
this.idDespatx = idDespatx;
this.lloc = lloc;
}
public int getidDespatx() {
return idDespatx;
}
public void setidDespatx(int idDespatx) {
this.idDespatx = idDespatx;
}
public String getLloc() {
return lloc;
}
public void setLloc(String lloc) {
this.lloc = lloc;
}
}
```
Ara veiem l'entitat Empleats, on mitjançant l'anotació OneToOne establim la relació amb Despatx:
```
package com.iticbcn.hibernate.Model;
import java.io.Serializable;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
@Entity
@Table(name="empleats")
public class Empleats implements Serializable {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
@Column(name="emp_no")
private int numeroEmpleat;
@Column(name="emp_name")
private String nomEmpleat;
@OneToOne(cascade=CascadeType.ALL)
@JoinColumn(name="despatx",
referencedColumnName="off_no",updatable=false)
private Despatx despatx;
public Empleats() {}
public Empleats(String nomEmpleat, Despatx despatx) {
this.nomEmpleat = nomEmpleat;
this.despatx = despatx;
}
public int getNumeroEmpleat() {
return numeroEmpleat;
}
public void setNumeroEmpleat(int numeroEmpleat) {
this.numeroEmpleat = numeroEmpleat;
}
public String getNomEmpleat() {
return nomEmpleat;
}
public void setNomEmpleat(String nomEmpleat) {
this.nomEmpleat = nomEmpleat;
}
public Despatx getDespatx() {
return despatx;
}
public void setDespatx(Despatx despatx) {
this.despatx = despatx;
}
}
```
En aquest enllaç a github teniu el codi amb el Main que persisteix empleat i arrossega despatx: [One To One - unidirectional](https://github.com/atalens1/Hibernate_One_To_One_1Directional.git).
Més coses a considerar:
* Amb l'anotació **@JoinColumn** especifiquem quins són els camps entre les entitats que tenen relació.
* Fixeu-vos que el valor del ***referencedColumnName*** és la clau primària de la taula despatxos, és a dir el camp **off_no**.
* Per últim **updatable=false** vol dir que Empleats no podrà canviar el valor del camp off_no un cop el despatx estigui persistit a la taula despatxos. Per poder entendre millor això, imaginem un despatx ja persistit del qual volem canviar el valor de la seva clau d'aquesta forma:
```
try {
Despatx d2 = new Despatx();
d2 = session.find(Despatx.class,1006);
d2.setidDespatx(1007);
e1.setDespatx(d2);
session.merge(e1);
session.getTransaction().commit();
} catch (ConstraintViolationException cve) {
System.err.println("Error de restricció de base de dades: " + cve.getMessage());
cve.printStackTrace();
throw cve;
}
```
Si ho executem s'obtè el següent resultat:
```
13:22:49.862 [main] ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper - (conn=17) Duplicate entry '1006' for key 'UK_5u81di7jbf76cqisb28u1ufu7'
Error d'Hibernate: could not execute statement [(conn=17) Duplicate entry '1006' for key 'UK_5u81di7jbf76cqisb28u1ufu7'] [insert into empleats (despatx,emp_name) values (?,?)]
org.hibernate.exception.ConstraintViolationException: could not execute statement [(conn=17) Duplicate entry '1006' for key 'UK_5u81di7jbf76cqisb28u1ufu7'] [insert into empleats (despatx,emp_name) values (?,?)]
```
És a dir, l'excepció de violació de restricció en la clau forana.
### La propietat **CASCADE**
Dins l'anterior exemple hem observat l'aparició d'una propietat anomenada CASCADE, la qual està relacionada amb les accions que pot fer una entitat propietària de la relació sobre l'altra. Comencem per detallar el valor més permissiu i finalitzem amb els valors més específics:
* **CascadeType.ALL**
És l’entitat que té aquest atribut (propietària) la que s’encarrega de la persistència de les entitats amb les que té la relació, sigui quina sigui l’acció (inserir, modificar, ..).
En aquest exemple l'empleat pot inserir el despatx, però si s'esborra l'empleat també s'esborra el despatx.
* **CascadeType.PERSIST**
Vol dir que l’entitat que té aquest atribut pot persistir objectes nous de l’altra entitat, és a dir, sols es fa inserció i no cap altra acció.
Seguint amb aquest exemple l'empleat pot inserir un nou despatx, però no es permet l'esborrament o la modificació d'un despatx ja persistit des de l'empleat.
* **CascadeType.MERGE**
Vol dir que l’entitat que té aquest atribut sols podrà fer canvis sobre objectes de les altres entitats amb les que guarda relació, suposant que aquests objectes ja estiguin persistits.
* **Altres casos: CascadeType.REMOVE, CascadeType.DETACH, …**
Fan l’acció que indiquen, especial atenció quan es fa el REMOVE per l’efecte que pot tenir a l’esborrar des de l’entitat origen.
### **Bidireccionals**
La relació entre entitat d'origen i entitat de destí té el mateix pes, és a dir, podem accedir d’una cap a l’altra i viceversa.
Imaginem per exemple les classes llibre i autor. A llibre hi haurà un atribut que ens dirà l’autor, per tant d’aquesta forma relacionem llibre i autor. I a autor hi haurà un atribut en forma de col·lecció de llibres (dels llibres que ha escrit). Així es relaciona autor amb llibres i s’observa la doble direcció.
Els tipus de relacions bidireccionals són:
### **OneToOne**
En aquest cas OneToOne si considera que l'entitat d'origen ha d'accedir a la de destí i viceversa.
Posem el cas que cada empleat té un vehicle d'empresa, però aquest vehicle pot canviar d'empleat. En aquest cas es fa necessari que vehicle pugui modificar empleat (i afegir un de nou si es vol en cas de no estar prèviament persistit). Els beans empleat i vehicle quedarien així:
```
@Entity
@Table(name="employees")
public class Empleat implements Serializable{
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
@Column(name="emp_no")
private int numeroEmpleat;
// ...
@OneToOne(cascade=CascadeType.ALL)
@JoinColumn(name="vehicle",
referencedColumnName="matricula",updatable=false)
private Vehicle vehicle;
//...
```
```
@Entity
@Table(name="cars")
public class Vehicle {
@Id
private String matricula;
// ...
@OneToOne(mappedBy="vehicle")
private Empleat empleat;
//...
```
Sota aquest escenari, la nostra app instanciaria i persistiria els dos objectes:
```
package com.hibernate.iticbcn;
import org.hibernate.SessionFactory;
import com.hibernate.iticbcn.model.Empleats;
import com.hibernate.iticbcn.model.Vehicles;
import java.time.LocalDate;
import java.time.Month;
import org.hibernate.Session;
public class App
{
public static void main( String[] args )
{
SessionFactory sesion = HibernateUtil.getSessionFactory();
Session session = sesion.openSession();
session.beginTransaction();
Empleats e = new Empleats(LocalDate.of(1970, Month.APRIL,3),"Pere" ,"Martinez", 'M',LocalDate.now());
Vehicles v = new Vehicles("1234KJK","Peugeot 3008",e)
//persistim directament vehicle, ja que dins conté empleat.
session.persist(v);
//commit y cierre de sesion
session.getTransaction().commit();
session.close();
}
}
```
Fixeu-vos com a l'hora de persistir no persistim els dos objectes per separat. Sols ho fem amb el darrer, el qual en la seva definició ja incorpora l'altre objecte a persistir (és a dir, vehicle incorpora l'empleat).
### **OneToMany i ManyToOne**
Podeu trobar el codi complet a [tasks-OneToMany](https://github.com/atalens1/hibernateLibrary)
Si bé es poden emprar de forma unidireccional, anem a veure-les com bidireccionals.
En aquest cas considerem l'autor que ha escrit molts llibres:
```
@Entity
@Table(name=”Books”)
public class Llibre implements Serializable{
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private int idLlibre;
/…/
@ManyToOne(cascade=CascadeType.PERSIST)
@JoinColumn(name=”idAutor”,foreignKey =
@ForeignKey(name=”FK_LIB_AUT”))
private Autor autor;
…}
```
```
@Entity
@Table(name=”Authors”)
public class Autor implements Serializable{
/…/
@OneToMany(mappedBy=”autor”,cascade=CascadeType.PERSIST,fetch=FetchType.LAZY)
private Set<Llibre> llibres;
…
}
```
En el cas que es vulgui especificar que aquell camp és una clau forana utilitzem l'anotació @ForeignKey.
Pel que fa a com s'implementen dins l'app:
```
public class App
{
public static void main( String[] args )
{
SessionFactory sesion = HibernateUtil.getSessionFactory();
Session session = sesion.openSession();
session.beginTransaction();
Autor a= new Autor("Juan Gomez-Jurado", "ESP");
Llibre l1 =new Libro("El paciente", "Thriller",a);
Llibre l2 =new Libro("Amanda Black 1 - Una herencia peligrosa", "Thriller",a);
//persistim cada objecte per separat
session.persist(l1);
session.persist(l2);
//commit y cierre de sesion
session.getTransaction().commit();
session.close();
}
}
```
### La propietat FetchType
Pel que fa als tipus de FetchType que ja heu pogut observar dins el JoinColumn tenim:
* **EAGER**
* És una càrrega complerta.
* Quan es carrega l’autor, hibernate resol la relació de forma immediata i carregarà tots els llibres.
* **LAZY**
* És una carrega en diferit, les dades es carreguen quan fan falta.
### **ManyToMany**
Un cas d’aquests tipus de relacions és el professor que imparteix diferents mòduls i els mòduls que són impartits per diferents professors.
Des d'un punt de vista d'entitat-relació, normalment ho resoldríem així:

Pel que fa a les classes mòdul i professor, les implementem així:
```
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
@Entity
@Table(name="Teachers")
public class Professor implements Serializable {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private int professor_id;
@Column
private String nom_professor;
@ManyToMany(cascade=CascadeType.PERSIST,fetch=FetchType.LAZY)
@JoinTable(name="Docencia",
joinColumns = { @JoinColumn(name="professor_id",foreignKey = @ForeignKey(name="FK_DOC_PROF")) },
inverseJoinColumns = { @JoinColumn(name="modul_id",foreignKey = @ForeignKey(name="FK_DOC_MOD")) })
private Set<Moduls> moduls = new HashSet<>();;
public Set<Moduls> getModuls() {
return moduls;
}
public void addModul(Moduls m) {
if(!this.moduls.contains(m)) {
this.moduls.add(m);
}
m.getProfessors().add(this);
}
public Professor() {}
public Professor(String nom_professor) {
this.nom_professor = nom_professor;
}
}
```
```
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
@Entity
@Table(name="Modules")
public class Moduls implements Serializable{
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private int modul_id;
@Column
private String descModul;
@ManyToMany(cascade=CascadeType.PERSIST,fetch=FetchType.LAZY,mappedBy = "moduls")
private Set<Professor> professors = new HashSet<>();;
public Set<Professor> getProfessors() {
return professors;
}
public void addProfessor(Professor p) {
if(!this.professors.contains(p)){
professors.add(p);
}
p.getModuls().add(this);
}
public Moduls() {}
public Moduls(String descModul) {
this.descModul = descModul;
}
}
```
On introduïm l'anotació **@ManyToMany** a totes dues classes. En aquests exemples caldria afegir (no ho hem fet) els getters i els setters. Fixeu-vos que Professor és l'entitat responsable de crear la taula filla "Docencia" amb l'entitat Mòduls i és per aquesta raó que com veureu l'entitat que persistim és professor i aquesta arrossega mòduls i fa la creació i inserció sobre l'entitat filla.
Per altra banda els mètodes *addModul* o *addProfessor* **asseguren la bidireccionalitat** de la relació, de tal forma que una entitat pugui persistir l'altra i viceversa.
Dit això, la forma en la que la classe App persisteix mòduls i professors és:
```
public class App
{
public static void main( String[] args )
{
SessionFactory sesion = HibernateUtil.getSessionFactory();
Session session = sesion.openSession();
session.beginTransaction();
Professor p1 = new Professor("Antonio Talens");
Professor p2 = new Professor("Roger Sobrino");
Modul m1 = new Modul("Acces a Dades");
Modul m2 = new Modul("Programacio");
Modul m3 = new Modul("Sistemes");
p1.addModul(m2);
p1.addModul(m1); // afegim mòduls a p1
p2.addModul(m3);
p2.addModul(m1); // afegim mòduls a p2
//desem (professors i mòduls en cascada)
session.persist(p1);
session.persist(p2);
//commit i tancament de sessió
session.getTransaction().commit();
session.close();
}
}
```
Podeu veure aquest codi al repositori de github: [Hibernate Many To Many](https://github.com/atalens1/Hibernate_Many_To_Many)
## Canvis a les taules un cop mapejades les entitats
Si les entitats ja estan mapejades en una taula de la base de dades i volem alterar l'estructura, afegint un camp nou a la taula o afegint algun tipus de restricció cal tenir en compte el següent:
* Si la propietat **hbm2ddl.auto** té el valor **update**, cal saber que un cop creades i mapejades les taules **l'hibernate no farà cap canvi sobre l'estructura** de forma automàtica (com si ho fa amb la seva creació). Quines alternatives tenim:
* Modificar l'estructura via instruccions DDL SQL, és a dir:
* Fer ALTER TABLE ADD COLUMN per afegir el camp o afegir cap restricció (tipus unique, not null, etc.).
* Fer l'ALTER TABLE per afegir la CONSTRAINT de FOREIGN KEY en cas que necessitem establir o canviar la restricció de clau forana entre dues o més taules.
* Canviar el valor de la propietat **hbm2ddl.auto** a **create**, però COMPTE! això esborra tota la taula amb les seves dades, per tant es desaconsella fer servir aquesta opció.
## EXERCICIS PROPOSATS
1. Preneu el cas de les classes empleat i vehicle, explicat a l'apartat de les relacions OneToOne bidireccionals i feu els canvis necessaris al projecte per incorporar la classe nova (vehicle), les relacions i executar l'aplicació. Quin resultat s'obté?
SOLUCIÓ EXPLICADA: [SOL. EX. 1](https://docs.google.com/document/d/1tmNXhi-90VH_SwK-GX3Q0xTntcY41PTkZdv1Fu655Tw/edit?usp=sharing)
2. Per modificar i/o esborrar un objecte cal abans carregar-lo. Descriviu com modificaríeu el main per tal de fer aquestes accions.
3. Creeu dues entitats noves anomenades personal i departament. Personal ha de ser semblant a empleats, però incorporant el salari i el id de departament. Aquest id de departament és la relació entre personal i departament. Penseu que cada persona a personal sols pot estar en 1 departament, però cada departament pot tenir vàries persones.
4. A partir de l'anterior, feu un programa que mo modifiqui el salari i el departament d'un empleat existent a personal, sumant 1000 al salari i asignant-li un nou departament.