4. Relacions
1. Mapejant Relacions
Com vam esmentar a la introducció, analitzarem com mapejar els diferents tipus de relacions. Abans de començar a discutir la cardinalitat de les relacions, hem de considerar el significat d'aquestes relacions, i revisarem el concepte de direccionalitat de les relacions.
- Unidireccional → Direm que una relació és unidireccional quan accedim a l'objecte relacionat (component) des d'un altre objecte (propietari). Per exemple, si muntem un motor en un cotxe, el lògic és que el propietari sigui el cotxe, i des d'aquest obtindrem el motor. En aquest cas, dins de l'objecte Cotxe apareixerà un objecte Motor, i el Motor no tindrà una existència pròpia.
- Bidireccional → Són relacions en les quals els elements relacionats solen tenir el mateix pes o entitat. Per exemple, un Grup d'un institut i un Tutor. Des d'un grup té sentit conèixer el tutor, i també podem des d'un professor (el tutor), accedir al grup que tutoritza. En aquest cas, dins de l'objecte Grup tenim una referència a l'objecte Tutor i viceversa.
Avís
En aquest tipus de referències, com es pot deduir, hi ha una recursió intrínseca. Per tant, quan gestionem aquest tipus de relacions bidireccionals, tingueu molta cura de no causar bucles, ja que fins i tot una cosa tan senzilla com imprimir pot fer que el nostre programa es bloquegi i aparegui la coneguda StackOverflowException.
A partir d'ara, podríem estudiar totes les representacions amb JPA.
2. Relacions Un a Un
Per a l'explicació dels exemples, veurem el disseny i la implementació a la base de dades de cada cas i com es veu a Hibernate. Per a aquest exemple representarem una relació 1:1 entre Grup i Professor, on es pot veure que un Grup té un Tutor, i un Tutor només pot tutoritzar un Grup.

Primerament, la classe que és apuntada per la clau forana. Molt fàcil, perquè no necessitem fer res.
| Java | |
|---|---|
I ara, la classe que conté la clau aliena. Aquí hem de marcar que un Grup necessita un Professor com a tutor. Vegem-ho:
Grupo conté un camp anomenat tutor de la classe Profesor, i:
@OneToOne(cascade = CascadeType.ALL)marquem aquesta relació com a 1:1. A més, especifiquem l'atributcascade, que és el més important. El cascading és la manera de dir que quan realitzem alguna acció sobre l'entitat objectiu (Grupo), la mateixa acció s'aplicarà a l'entitat associada (Profesor). Revisem les opcions més rellevants:CascadeType.ALLpropaga totes les operacions. La mateixa operació que fem en l'objectiu es farà en l'associat.CascadeType.PERSISTpropaga només l'operació de persistència a la base de dades (guardar).CascadeType.SAVE_UPDATEés de Hibernate, no de JPA, i propaga el mètodesaveOrUpdate(). És molt similar a persist.CascadeType.REMOVEoCascadeType.DELETEpropaga l'eliminació d'entitats. Tingueu molta cura amb aquesta opció per evitar perdre dades.- En el
@JoinColumnestablim: - el nom de la columna a la nostra base de dades
- el nom de la columna referenciada en l'entitat objectiu
Profesor unique=trueper assegurar que la relació és 1:1 (un professor no pot estar relacionat amb cap altre grup)- [opcional] per establir el nom de la restricció de clau forana, en cas que vulgueu canviar-lo o eliminar-lo en operacions futures.
Més informació a la següent web baeldung
2.1. Un a Un bidireccional
Si volem emmagatzemar en Professor els grups que està tutoritzant, necessitem afegir una referència al Grup. Com que hem fet la clau forana en Grup, serà molt fàcil:
Amb mappedBy="tutor" estem dient que a la classe Grupo existeix un camp anomenat tutor amb tota la informació sobre la relació. Tingueu en compte que no s'afegiran camps addicionals a Profesor, perquè la informació sobre la relació es troba a la taula Grupo.
3. Un a Molts
Per a aquesta explicació començarem amb el següent model, en el qual un Llibre té un Autor que l'ha escrit, i un Autor pot haver escrit diversos Llibres. En l'esquema relacional, la relació és des de idAutor en Llibres, que és clau forana a la taula Autor (ID).

Primer, podem decidir qui és el propietari de la relació. Realment no importa, però en diversos dissenys és molt clar, per exemple entre Estudiant i Email, on òbviament el propietari és Estudiant. Normalment hauria de ser la classe amb cardinalitat molts el propietari. Vegem l'exemple.
Llibre té un autor (únic). Ho implementem emmagatzemant una referència a un objecte Autor, anomenat elAutor dins del nostre Llibre. Hem d'escriure la informació de la relació en aquest camp:
- Hem de marcar aquest camp com
@ManyToOne, perquè Llibre està al costat dels molts de la relació (recordeu que un Autor pot escriure diversos Llibres) - La clau forana serà anotada amb l'etiqueta
@JoinColumn, amb diversos atributs: - Com que
elAutorés el punt inicial de la clau forana, que apunta a la taulaAuthor, necessitem dir el nom de la clau primària en aquesta classe. Aquest atribut és opcional, però és una bona opció per millorar el nostre codi. - Opcionalment, podem anomenar la restricció de la clau forana, amb un nom ben estructurat, amb l'atribut
foreignKey
La classe Autor està en el costat un, i això significa que pot escriure molts Llibres. Per aquesta raó, emmagatzemem tots els llibres que ha escrit en un Set de llibres. Les anotacions seran:
- Com que un Autor pot escriure molts llibres, marquem el Set de llibres com
@OneToMany. Com que hem escrit l'especificació de la relació en Llibre, podríem dir que la relació està mapejada en el campelAutordins de la classeLlibre, ambmappedBy="elAutor"fàcilment.
Decisió
En lloc d'emmagatzemar llibres en un Set, es poden emmagatzemar en una Llista. La principal diferència és respondre a aquesta pregunta: és important l'ordre?. Si respons sí, has d'utilitzar una Llista. Si la resposta és no, has d'utilitzar un Set.
Espai
La relació 1:N que hem explicat és bidireccional. Això vol dir que des d'un Autor podem obtenir tots els Llibres que ha escrit, i des d'un Llibre podem obtenir l'Autor.
Podeu trobar diverses pàgines i llibres que expliquen les relacions unidireccionals 1:N. Això vol dir que amb aquest tipus d'implementació només podem viatjar en una direcció. En aquest cas, hem d'emmagatzemar només dins d'un Llibre qui és l'autor, perquè el Llibre és el propietari. Hem d'eliminar el conjunt de llibres en l'autor per obtenir una relació unidireccional.
3.1. Tipus de Càrrega Fetch
Aquest atribut sol aparèixer quan tenim una relació 1:N o N:M en una classe que té una col·lecció de classes relacionades (també es pot especificar amb un 1:1 però és menys comú). Quan Hibernate carrega un objecte, carregarà els seus atributs generals (nom, nacionalitat, etc...), però què passa amb els Llibres que ha escrit, els carrega o no?
DateType.EAGER→ Literalment traduït com ansiós. No podem esperar, i quan es carrega l'Autor, Hibernate resoldrà la relació i carregarà tots els llibres amb totes les dades internes de cada llibre. Tenim totes les dades en el moment.DateType.LAZY→ Literalment com mandrós (vago), però més representatiu com càrrega mandrosa. Si carreguem l'Autor, Hibernate només carrega els atributs propis de l'Autor, sense carregar els seus Llibres. Quan intentem accedir als seus llibres des del nostre programa, Hibernate s'activa i els carrega. És a dir, en mode LAZY, les dades es carreguen quan es necessiten.
"Què farem?
Què és millor o pitjor? La resposta no és senzilla, ja que ambdós tenen pros i contres:
- En
EAGERnomés es fa un accés, mentre que enLAZYdos accessos o més. - En
EAGERes carreguen totes les dades, fins i tot si no són necessàries, enLAZYnomés es carrega el que és necessari.
El programador ha de valorar i equilibrar la quantitat d'informació requerida en un moment donat i el cost d'accés a la base de dades.
4. Molts a Molts
En aquesta secció acabarem amb l'últim tipus de relacions que podem trobar en el model E/R, que són les relacions molts a molts. Poden aparèixer altres relacions amb cardinalitats més altes, com les relacions ternàries, però com es va estudiar en el primer any, totes elles es poden modelar amb transformacions binàries.
Dins de les relacions binàries, podem trobar dues possibilitats:
- Relacions que simplement indiquen la relació (per exemple, que un personatge pot o no portar un cert tipus d'arma en un joc de rol) o
- Relacions que, a més d'indicar-ho, afegeixen nous atributs. Per exemple, un actor participa en una pel·lícula en un tipus de paper: principal, secundari, etc.
En el model relacional, ambdós casos acaben sent modelats com una nova taula (amb o sense l'atribut). Si ens trobem en el segon cas, una nova taula amb els atributs que posseeix ha de ser modelada amb una classe, així que la relació N:M entre dues taules es convertirà en dues relacions un a molts 1:N i N:1 (actor-actuació i actuació-pel·lícula). Ens centrarem en el primer cas, ja que ja estem preparats per resoldre el segon.
Anem a modelar el cas típic d'un Professor que imparteix diversos Mòduls, els quals poden ser impartits per diversos professors. L'esquema és el següent:
Com podem veure, la típica taula central de la relació N:M es manté. Com es va esmentar anteriorment, la taula Docència no existirà en el model OO, ja que només serveix per relacionar els elements.
Les classes Mòdul i Professor són les següents (només es mostra la part relacionada amb la relació) triant en aquest cas Professor com el propietari de la relació:
| Java | |
|---|---|
Aquesta és l'especificació més complicada, anem-hi:
- En ambdues classes el mapeig és
@ManyToMany - En ambdós casos indiquem com gestionem les operacions en cascada (
cascade) i la càrrega dels objectes relacionats de l'altra classe (fetch) - A la classe propietària (
Professor) es maparà unSet<Module>amb la relació que començarà des de la meva classe actualProfessor\(\rightarrow\) docència \(\rightarrow\)Modulo(el tipus base del Set) - Amb
@JoinTables'indica que la relació enllaça amb una taula amb nomDocencia, on: - S'enllaçarà (
joincolumns), i l'enllaç és amb@JoinColumn:- Començant des del camp
idProfesordins de la taulaDocencia - Acabant a la clau primària de
Profesor, - La FK es nomena
foreignKey = @ForeignKey(name = "FK_DOC_PROF" ).
- Començant des del camp
- Tingueu en compte que els noms dins de @JoinTable són noms a la taula Docencia (existent només a la base de dades).
- Es mapeja des de la taula
Docenciaa l'entitat fontModuloinversament (des del punt fins a l'origen de la fletxa): - Això s'aconsegueix amb
inverseJoinColumns:- Enllaçant des del camp
idModule(@JoinColumn). - També nomenem la FK.
- Enllaçant des del camp
- A la classe relacionada (
Modulo), que no és la propietària, simplement indiquem que la propietària ésProfessor, mitjançantmappedBy="losModulos".
Un codi d'exemple seria així:
i la sortida de Hibernate serà semblant a:
Despres de crear les taules, Hibernate crearà les claus alienes, i llavors inserirà els registres. Primer Professor i Mòdul i seguidament la relació entre ells de Docència
Molts a Molts amb atributs
Per a una visió completa, aquest tutorial explica com fer-ho N_M amb atributs.
Anem nosaltres a iplementar un exemple complet.
TO DO