Un ORM (Object Relational Mapping) è un sistema per mappare classi e oggetti Java su tabelle e record di
un database relazionale, offrendo le operazioni di salvataggio, aggiornamento, cancellazione
e recupero di oggetti/dati (persistenza dei dati).
Con JEE 5 è stata definita anche la specifica per la persistenza dei
dati con il nome di Java Persistence API. JPA si pone come livello di astrazione al di sopra delle API JDBC, rendendo
il codice per la gestione dei dati indipendente dal database ed introducendo il linguaggio SQL-Like,
che consente di effettuare interrogazioni sul modello Java e non direttamente sulla base dati.
Il mattone base è costituito
dal concetto di Entity, una normale classe Java (POJO) da mappare su una o più tabelle,
in modo che i valori delle variabili di istanza dei suoi oggetti vengano salvati come record. Nell'implementare
le Entities, e le loro relazioni, lo sviluppatore di software può orientarsi su:
- Progettare il modello Java e derivare uno schema relazionale dal modello.
- Avere a disposizione uno schema relazionale dal quale derivare il modello.
I moderni tool di sviluppo forniscono strumenti per l'autogenerazione del modello Java e dello
schema di database. Se si sta sviluppando un nuovo sistema la prima metodologia offre dei vantaggi
rispetto alla seconda, infatti possiamo adottare una progettazione Object Oriented, magari con UML, ed avere gratis
la generazione del database associato al modello con un ORM che gestisce il tutto. Si hanno indubbiamente grossi
benefici in termini di tempi e costi di sviluppo.
La seconda metodologia è più indicata se il database è già esistente
e dobbiamo realizzare un sistema basato su di esso, o se si desidera un controllo completo
sulla struttura della base dati. Nel nostro caso illustreremo il primo approccio senza far uso del tool automatico
per maggiore chiarezza.
Un esempio pratico: gestire prenotazioni alberghiere
Introduciamo quindi un progetto per la gestione delle
prenotazioni delle camere di un albergo. Immaginiamo di aver realizzato un modello UML semplificato delle entità da gestire
e delle relazioni tra esse:
Il sistema mostra che un utente può effettuare una prenotazione e partecipare
a degli eventi, ogni classe rappresenta un'Entity. Esaminiamo lo sviluppo dell'Entity Customer introducendo tutte le annotazioni per mapparla su una tabella del database, iniziamo quindi col creare
un progetto JPA attraverso il menù file di JBoss Developer Studio e seguendo il wizard di creazione
con l'accortezza di impostare i valori come indicato nelle figure seguenti:
JPA richiede l'uso obbligatorio di due sole annotazioni, @Entity
e @Id
, per la persistenza di una classe. Realizziamo quindi la classe Entity Customer:
package it.html.model.hotel;
import java.io.Serializable;
import java.lang.String;
import javax.persistence.*;
@Entity
public class Customer implements Serializable {
private long id;
private String firstName;
private String lastName;
@Id
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
@Entity
indica che la classe deve essere mappata su un database, @Id
quale campo
della classe verrà utilizzato come identificatore di un'istanza (primary key
). Il persistence provider (ORM) assume di default che:
- l'Entity è mappata con il suo stesso nome (
Customer
); - la tabella avrà una colonna per ciascun campo della classe
Customer
con il nome della colonna uguale al nome del campo.
Se applichiamo @Id
su un metodo getter dobbiamo applicare ogni altra annotation che interessa
i campi della classe sui relativi metodi getter (stile JavaBean). Se invece utilizziamo @Id
direttamente
su un campo della classe, stiamo specificando un accesso diretto ai campi della classe. In quest'ultimo caso è possibile evitare l'utilizzo dei getter/setter e cambiare il modificatore di accesso da private
a public
.
Senza entrare nel merito di quale di questi stili sia migliore, scegliamo di
adottare lo stile JavaBean. Se desideriamo customizzare il mapping sulla tabella e sulle sue colonne, possiamo utilizzare le annotation
@Table
e @Column
:
package it.html.model.hotel;
import java.io.Serializable;
import java.lang.String;
import javax.persistence.*;
@Entity
@Table(name="CUSTOMER_TABLE")
public class Customer implements Serializable {
private long id;
private String firstName;
private String lastName;
@Id
@Column(name="CUSTOMER_ID",nullable=false,columnDefinition="integer")
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
@Column(name="FIRST_NAME",length=20,nullable=false)
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
@Column(name="LAST_NAME",length=20,nullable=false)
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
Con l'attributo name
di @Table
e @Column
specifichiamo rispettivamente il nome della tabella e della colonna nel mapping con la classe.
Altri attributi di @Column
sono:
Attributo | Descrizione |
---|---|
nullable | Specificare se la colonna ammette o meno valori null (default=true). |
lenght | Specifica la lunghezza massima nel caso di tipi stringa. |
columnDefinition | Specifica il tipo relazionale sulla colonna. |
precision e scale | Personalizza numeri in virgola mobile. |
insertable e updatable | Specifica se la colonna deve essere inserita negli statement di insert e update (default=true). |
@Basic
è la forma più semplice per il mapping di un campo ed è applicata di default per i tipi Java
String
, byte[]
, Byte[]
, char[]
, Character[]
, BigInteger
, BigDecimal
, java.util.Date
, Calendar
, java.sql.Date
,
java.sql.Timestamp
.
Talvolta può essere utile usarla esplicitamente per sfruttare l'attributo fetch
. Esso, EAGER
di default, può essere impostato su LAZY
. In questo modo quando recuperiamo un record dal database il valore del campo non viene caricato: il caricamento è ritardato al momento dell'invocazione del getter. Un uso particolarmente utile è associato all'applicazione di @Lob
per campi che rappresentano grandi quantità di dati come file immagine:
....
@Lob
@Basic(fetch=FetchType.LAZY)
public byte[] getPicture(){...}
....
Annotation @Temporal e @Transient
@Temporal
consente il mapping di tipi data come java.util.Date
e java.util.Calendar
:
....
@Temporal(TemporalType.DATE)
public Date getDate(){...}
....
Se desideriamo escludere un particolare campo dalla persistenza possiamo farlo attraverso l'uso di @Transient:
....
@Transient
public int getTemporaryValue(){...}
....