Finora abbiamo visto come mappare singole Entity su una tabella. Le tabelle sono in genere relazionate tramite vincoli di foreign key con cui associare un record di una tabella ad uno o più record di un'altra (relazioni uno a uno e uno a molti), o più record di una tabella con diversi record di un'altra (molti a molti).
Gli stessi tipi di relazione si definiscono tra Entity con l'aggiunta, grazie al persistence provider, della direzionalità che specifica relazioni unidirezionali o bidirezionali.
Relazioni OneToOne
Iniziamo affrontando la più semplice delle relazioni: uno a uno, questo tipo di relazione è presente tra le Entity Booking
e Room
. In sostanza stiamo dicendo che ad una prenotazione (Booking
) è associata una singola stanza (Room
).
Supponiamo di trovarci nel caso in cui il database sia già esistente e che le tabelle relazionali siano le seguenti:
Abbiamo due tabelle legate da un vincolo di foreign key che va BOOKING
a ROOM
. Le chiavi primarie delle due tabelle hanno il medesimo nome: ID
, ipotizziamo inoltre che siano state definite le sequenze BOOKING_SEQUENCE
e ROOM_SEQUENCE
per generare i valori delle chiavi.
Utilizziamo quanto appreso nper realizzare le entity Booking
e Room
legate alle tabelle e creiamole all'interno di it.html.model.hotel
del progetto ProgettoJpa1
:
@Entity
@Table(name="BOOKING")
@SequenceGenerator(name="BOOKING_GENERATOR",
sequenceName="BOOKING_SEQUENCE")
public class Booking {
private long id;
private float amount;
private Date checkIn;
private Date checkOut;
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE,
generator="BOOKING_GENERATOR")
@Column(name="ID")
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Column(name="AMOUNT")
public float getAmount() {
return amount;
}
public void setAmount(float amount) {
this.amount = amount;
}
@Column(name="CHECK_IN")
@Temporal(TemporalType.DATE)
public Date getCheckIn() {
return checkIn;
}
public void setCheckIn(Date checkIn) {
this.checkIn = checkIn;
}
@Column(name="CHECK_OUT")
@Temporal(TemporalType.DATE)
public Date getCheckOut() {
return checkOut;
}
public void setCheckOut(Date checkOut) {
this.checkOut = checkOut;
}
}
@Entity
@Table(name="ROOM")
@SequenceGenerator(name="ROOM_GENERATOR",
sequenceName="ROOM_SEQUENCE")
public class Room {
private long id;
private int num;
private String type;
private String free;
private Booking booking;
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE,
generator="ROOM_GENERATOR")
@Column(name="ID")
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Column(name="NUM")
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@Column(name="TYPE")
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Column(name="FREE")
public String getFree() {
return free;
}
public void setFree(String free) {
this.free = free;
}
@OneToOne(mappedBy="room")
public Booking getBooking() {
return booking;
}
public void setBooking(Booking booking) {
this.booking = booking;
}
}
Abbiamo quindi legato l'Entity alla tabella grazie all'annotation @Table
ed utilizzato @Column
per specificare il mapping con le colonne della tabella. Con @SequenceGenerator
e @GeneratedValue
abbiamo infine collegato le sequenze.
Inseriamo ora una relazione OneToOne bidirezionale
sulla base del vincolo di foreign key
. In una relazione bidirezionale ogni lato della relazione
è a conoscenza dell'altro, questo si traduce nella presenza di un campo Room
nella classe Booking
e un campo
Booking
nella classe Room
: la relazione è navigabile da entrambe le direzioni.
Aggiungiamo quindi il seguente codice nella classe Booking
per le annotazioni @OneToOne
e @JoinColumn
:
private Room room;
@OneToOne
@JoinColumn(name="ROOM_ID")
public Room getRoom() {
return room;
}
public void setRoom(Room room) {
this.room = room;
}
Con @OneToOne
informiamo il persistence provider della relazione Booking-Room nel verso indicato, specificando come colonna Join di BOOKING
il campo ROOM_ID
. Inseriamo ora all'interno di Room
:
private Booking booking;
@OneToOne(mappedBy="room")
public Booking getBooking() {
return booking;
}
public void setBooking(Booking booking) {
this.booking = booking;
}
Data una stanza siamo quindi in grado di capire se è oggetto di una prenotazione.
Lato owner e lato inverso
A differenza di Booking
in @OneToOne
di Room
abbiamo specificato l'attributo mappedBy
, infatti
una relazione bidirezionale porta con sé il concetto di lato owner e lato inverso. owner
è l'Entity
la cui annotation di relazione (@OneToOne
) non specifica il mappedBy
. In sostanza, per una relazione
bidirezionale è sempre l'entity collegata alla tabella che detiene il vincolo relazionale di foreign key
Il lato inverso indica esattamente questa informazione ("non sono io l'Owner, guarda l'altra l'entity indicata") inserendo nell'attributo mappedBy
il nome del campo relazione nell'entity opposta (room
dell'entity Booking
).
La relazione OneToOne unidirezionale è possibile specificando l'annotation @OneToOne
soltanto su uno dei due lati (Owner) senza utilizzare mappedBy
, cosi ad esempio la unidirezionale lato Entity Booking, si traduce in:
private Room room;
@OneToOne
@JoinColumn(name="ROOM_ID")
public Room getRoom() {
return room;
}
public void setRoom(Room room) {
this.room = room;
}
Se volessimo invece una relazione unidirezionale lato Room
inseriremmo nell'Entity Room
:
private Booking booking;
@OneToOne
@JoinColumn(name="ROOM_ID")
public Booking getBooking() {
return booking;
}
public void setBooking(Booking booking) {
this.booking = booking;
}
rimuovendo la relazione OneToOne in Booking
.
ROOM_ID
è sempre il campo definito in BOOKING
. Nel caso unidirezionale, all'unica Entity Owner che contiene l'annotation di relazione non corrisponde necessariamente la tabella relazionale con il vincolo di foreign key
. L'Owner di una relazione deve corrispondere alla tabella con il foreign key
soltanto nel caso bidirezionale.