CERCA SITEMAP FEED RSS 1280
Ultimo aggiornamento: 30 Agosto 2009

Java Persistence API (JPA)

Spesso il primo passo nello sviluppo di applicazioni enterprise consiste nella creazione del domain model ovvero dell’insieme di entità del dominio e delle relazioni fra esse.
Il domain model è l’immagine concettuale del problema che il sistema deve risolvere, esso descrive oggetti e relazioni ma non si occupa di definire come il sistema agisce su tali oggetti.
L’ obiettivo è quello di identificare le entità che devono essere persistenti e quindi memorizzate nel database.
Gli aspetti sui quali occorre prestare attenzione sono gli oggetti, le relazioni, le molteplicità delle relazioni e l’opzionalità delle relazioni.
Dal punto di vista dello sviluppatore gli oggetti sono oggetti java con stati e comportamenti.
Una relazione è rappresentata dal fatto che un oggetto ha al suo interno un riferimento ad un altro oggetto.

Il riferimento determina la direzione della relazione che può essere:
  • unidirezionale: quando un oggetto referenzia un altro ma non il contrario
  • bidirezionale: quando due oggetti si referenziano a vinceda
La molteplicità entra in gioco quando da una parte della relazione è presente più di un oggetto: ad esempio un oggetto Categoria potrebbe mantenere il riferimento a più oggetti Articolo.

Esistono tre tipi differenti di relazioni:
  • uno a uno: ogni lato della relazione ha al più un oggetto
  • uno a molti: una particolare istanza di un oggetto può fare riferimento a più istanze di un altro
  • molti a molti: entrambi i lati della relazione possono fare riferimento a più istanze di un oggetto
L’ordinalità o opzionalità di una relazione infine determina se la relazione deve esistere obbligatoriamente oppure è opzionale.
In EJB 3 la persistenza è gestita mediante Java Persistence API (JPA).
Tutto ciò che bisogna fare al fine di rendere persistenti gli oggetti del domain model è codificare il domain model in POJO e usare le annotazioni (o deployment descriptor) per fornire al persistence provider informazioni circa:
  • quali sono gli oggetti del domain model
  • come identificare univocamente tali oggetti
  • quali relazioni esistono tra gli oggetti
  • come mappare un oggetto in una tabella del database

Annotazione @Entity

Per marcare un POJO come un oggetto del domain model (entity bean) si fa uso dell’annotazione @Entity
@Entity
public class Categoria 
{
  ...

  public Categoria() 
  { 
    ... 
  }

  public Categoria(String name) 
  { 
    ... 
  }
  
  ...
}
Tutte le entità non astratte devono avere un costruttore vuoto publico o protetto che viene usato per creare una nuova istanza usando l’operatore new:
Categoria categoria = new Categoria();
Una delle caratteristiche più interessanti di JPA è che supporta completamente le caratteristiche di eredità e polimorfismo della programmazione Object Oriented.
E’ possibile cioè avere delle entità che estendono altre entità o classi non entità.
@Entity
public class Utente 
{
  ...
  String id;
  String nome;
  String cognome;
  ...
}

@Entity
public class Giocatore extends Utente 
{ 
  ...
}

@Entity
public class Amministratore extends Utente 
{
  ...
}
Dal momento che la classe Utente è dichiarata come entity bean, tutti i campi ereditati come nome e cognome saranno persistenti in Giocatore e Amministratore.
Se la classe Utente non fosse stata un’entità allora i valori delle corrispondenti proprietà sarebbero stati scartati.
Un modo per evitare ciò consiste nel dichiarare la classe Utente come abstract così che non possa essere salvata o istanziata.

Field-based access e Property-based access

Un’entità mantiene il suo stato usando campi o proprietà (mediante metodi get e set).
Sebbene le convenzioni JavaBeans siano ormai diffuse da anni alcuni programmatori preferiscono avere accesso diretto alle variabili istanza.
Parliamo di field-based access quando il mapping Object/Relational è definito utilizzando le variabili istanza dell’entità, mentre parliamo di property-based access quando il mapping avviene facendo riferimento alle proprietà dell’entità.
Se si vuole usare il field–based access allora ocorre dichiarare publici i campi del POJO e chiedere al persistence provider di ignorare i metodi set e get.
E’ necessario naturalmente fornire alcune indicazioni fra le quali quelle relative alle variabili di istanza che sono usate direttamente per la persistenza.
E’ possibile fare ciò mediante l’annotazione @Id.
Il seguente codice mostra come potrebbe apparire una persistenza field-based:
@Entity
public class Categoria 
{
  @Id
  public Long id;
  public String nome;
  public Date dataCreazione;
  
  public Categoria() 
  {}

}
In questo caso il persistence provider inferisce che i campi id, nome e dataCreazione dovrebbero essere persistenti dal momento che l’annotazione @Id è usata sul campo id.
Se si volesse usare la persistenza property-based l’annotazione andrebbe applicata alla proprietà get mentre le annotazioni usate sui metodi set vengono ignorate.
Naturalmente non è possibile usare contemporaneamente i due tipi di accesso.
Se necessario è possibile evitare che una proprietà diventi persistente marcandola con l’annotazione @Transient.
@Entity
public class Categoria 
{
  ...
  @Transient
  protected Long iscritti;
  ...
}
E’ possibile ottenere lo stesso effetto usando @Transient sui metodi get delle relative proprietà quando si usa un accesso property-based.
Definire un campo con il modificatore transient è equivalente ad applicare l’annotazione @Transient.
@Entity
public class Categoria 
{
  ...
  transient protected Long iscritti;
  ...
}
I tipi di dati che posono essere resi persistenti sono:
  • i tipi primitivi
  • il tipo String
  • le classi che implementano l’interfaccia Serializable
  • gli Array
  • le collezioni di entità
  • le classi annotate con @Embeddable

Definire l'identità delle entità

Ogni entità del domain model deve essere univocamente identificabile.
Questo requisito è legato al fatto che l’entità viene resa persistente memorizzandola in una riga di una tabella del database.
Per rendere un’entità univocamente identificabile occorre definire una chiave primaria attraverso uno dei seguenti modi:
  • usando l’annotazione @ID
  • usando l’annotazione @IdClass
  • usando l’annotazione @EmbeddedId

L'annotazione @Id

L’uso dell’annotazione javax.persistence.Id rappresenta il modo migliore per comunicare al persistence provider dove è memorizzata l’identità di una entità.
L’annotazione @Id marca infatti un campo o una proprietà dell’entità come identificativo univoco.
@Entity
public class Categoria 
{
  ...
  protected Long id;
  ...

  @Id
  public Long getId() 
  {
    return this.id;
  }

  public void setId(Long id) 
  {
    this.id = id;
  }

  ...
}
L’annotazione @Id va bene soltanto nel caso in cui l’identificativo univoco dell’entità è costituito da un solo campo o proprietà.
In realtà esistono dei casi in cui l’identificativo è composto da più campi in questi casi si fa uso dell’annotazione @IdClass o dell’annotazione @EmbeddedId.

L'annotazione @IdClass

L’annotazione @IdClass viene utilizzata nel caso in cui l’identità di un entity bean è composta da più campi:
public class CategoriaPK implements Serializable 
{
  String nome;
  Date dataCreazione;

  public CategoriaPK() 
  {}
   
  public boolean equals(Object oggetto) 
  {
    if (oggetto instanceof CategoriaPK) 
    {
      CategoriaPK altrachiave = (CategoriaPK)oggetto;
      return (altrachiave.nome.equals(nome) && altrachiave.dataCreazione.equals(dataCreazione));
    }
    return false;
  }

  public int hashCode() 
  {
    return super.hashCode();
  }
}

@Entity
@IdClass(CategoriaPK.class)
public class Categoria 
{
  public Categoria() 
  {}
  
  @Id
  protected String nome;
  
  @Id
  protected Date dataCreazione;
  
  ...
}
La classe CategoriaPK è designata come IdClass per Categoria che ha due campi che formano la chiave primaria marcati con l’annotazione @Id (nome e dataCreazione): questi due campi sono presenti anche nella classe CategoriaPK.
Il costruttore è usato per creare un’istanza della primary key, il metodo equal implementato nella classe CategoriaPK confronta i due campi che costituiscono la Primary Key per determinare se due entità sono uguali o meno.
Ciò viene effettuato a runtime, il persistence provider determina se due oggetti Categoria sono uguali copiando i campi marcati con @Id nei corrispondenti campi di CategoriaPK e usando il metodo equals.
Ogni IdClass deve essere Serializable e fornire una implementazione hashCode valida.
Lo svantaggio è legato alla ridondanza di codice necessaria a garantire l’utilizzo multiplo dell’annotazione @Id.

L'annotazione @EmbeddedId

L’annotazione @EmbeddedId consente di spostare l’IdClass all’interno dell’entità.
@Embeddable
public class CategoriaPK 
{
  String nome;
  Date dataCreazione;

  public CategoriaPK() 
  {}
  
  public boolean equals(Object oggetto) 
  {
    if (oggetto instanceof CategoriaPK) 
    {
      CategoriaPK altrachiave = (CategoriaPK)oggetto;
      return (altrachiave.nome.equals(nome) && altrachiave.dataCreazione.equals(dataCreazione));
    }
    return false;
  }

  public int hashCode() 
  {
    return super.hashCode();
  }
}

@Entity
public class Category 
{
  public Category() 
  {}
  
  @EmbeddedId
  protected CategoriaPK categoriaPK;
  ...
}
Dal codice è possibile notare che i campi identità name e createDate sono assenti dalla classe Categoria e al loro posto viene usato un oggetto categoriaPK annotato con @EmbeddedId.
L’unica differenza per quanto riguarda l’oggetto CategoriaPK è che questo non deve essere Serializable.
Notiamo inoltre che l’annotazione @Id è omessa in quanto ridondante.

Annotazione @Embeddable

Supponiamo di voler evitare di inserire i campi via, civico, citta, cap, provincia all’interno di un oggetto Utente ma di voler definire piuttosto un oggetto Indirizzo che possa essere inserito al suo interno.
Per fare ciò possiamo fare uso dell’annotazione @Embeddable usata per progettare oggetti persistenti che non hanno una propria identità ma che sono identificati dall’entità dell’oggetto all’interno del quale sono innestati.
@Embeddable
public class Indirizzo 
{
  protected String via;
  protected int civico;
  protected String citta;
  protected int cap;
  protected String provincia;
  ...
}

@Entity
public class Utente 
{
  @Id
  protected Long id;
  protected String nome;
  protected String cognome;
  @Embedded
  protected Indirizzo indirizzo;
  protected String email;
  ...
}
L’oggetto embeddable Indirizzo è inserito dentro l’entità Utente e condivide l’identità marcata con @Id.
Un oggetto Embeddable non può avere un’identità propria e nella maggior parte dei casi viene mappato nello stesso record dell’oggetto che lo incapsula materializzandosi soltanto nel mondo Object Oriented.

Relazioni fra entità

Una relazione essenzialmente si traduce nel fatto che un’entità fa riferimento ad un'altra.
Una relazione può essere unidirezionale o bidirezionale a seconda di dove sono piazzati i riferimenti.
Le relazioni possono essere uno ad uno, uno a molti, molti a uno e molti a molti: ciascuna di queste ha una sua annotazione.

Annotazione @OneToOne

L’annotazione @OneToOne è usata per marcare una relazione unidirezionale o bidirezionale uno ad uno.
Supponiamo che un oggetto Utente abbia una relazione uno ad uno con un oggetto InfoUtente che contiene le informazioni dell’utente e che la relazione sia unidirezionale ovvero l’oggetto Utente possiede un riferimento all’oggetto InfoUtente ma non viceversa.

Per descrivere questa situazione possiamo usare l’annotazione @OneToOne nel seguente modo:
@Entity
public class Utente 
{
  @Id
  protected String id;
  @OneToOne
  protected InfoUtente Info;
}
   
@Entity
public class InfoUtente
{
  @Id
  protected Long id;
  protected String info;
  protected String info2;
  ...
}
La classe Utente mantiene un riferimento a InfoUtente nel campo persistente Info.
L’annotazione @OneToOne indica che il persistence provider dovrebbe mantenere questa relazione nel database.

L’annotazione @OneToOne è così definita:
@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface OneToOne 
{
  Class targetEntity() default void.class;
  CascadeType[] cascade() default {};
  FetchType fetch() default EAGER;
  boolean optional() default true;
  String mappedBy() default "";
}
e può essere applicata sia a campi che a proprietà dal momento che Target può essere METHOD o FIELD.
L’elemento targetEntity indica quale è la classe referenziata: in molti casi questa informazione è ridondante perché il container può inferirlo da solo dal tipo del campo o dal tipo restituito dalla proprietà get e set.
Il parametro cascade controlla cosa accade ai dati collegati quando la relazione è alterata o cancellata, mentre fetch specifica quando e come i dati collegati sono estratti dalle tabelle di un database.
L’elemento optional comunica al persistence provider se l’elemento collegato deve essere sempre presente, di default è pari a true (nel nostro caso stiamo ipotizzando che non tutti gli Utenti abbiano un InfoUtente ovvero che la relazione è opzionale e il campo info può esere pari a null)
Il fatto che l’oggetto Utente mantenga un riferimento all’oggetto InfoUtente ci consente di ottenere facilmente l’accesso alle informazioni utente.
In alcuni casi può essere necessario poter accedere alle entità di una relazione one to one da entrambi i lati della relazione: occorre cioè definire una relazione uno a uno bidirezionale.

Cioò può essere ottenuto utilizzando l’annotazione @OneToOne da entrambi i lati:
@Entity
public class Utente 
{
  @Id
  protected String id;
   
  @OneToOne
  protected InfoUtente info;

  ...
}

@Entity
public class InfoUtente 
{
  @Id
  protected Long id;
  protected String info;
  protected String info2;

  ...

  @OneToOne(mappedBy="info", optional="false");
  protected Utente utente;

  ...
}
L’annotazione @OneToOne su utente ha due aspetti interessanti: il primo è mappedBy="info" che comunica al container che il corrispondente riferimento in Utente è la variabile info, il secondo aspetto è il parametro optional che settato a false significa che non può esistere un oggetto InfoUtente senza un Utente ad esso collegato.

Annotazioni @OneToMany e @ManyToOne

Le relazioni uno a molti e molti a molti sono molto comuni; in queste relazioni un’entità può mantenere uno o più riferimenti ad un’altra.
In java si fa uso delle classi Set o List.
Se l’associazione è bidirezionale allora da un lato sarà uno a molti (@OneToMany) e dall’altro molti a uno (@ManyToOne).
@Entity
public class Categoria 
{
  @Id
  protected Long id;
  protected String titolo;
  protected String descrizione;
  ...

  @OneToMany(mappedBy="categoria")
  protected Set<Bid> articoli;
  ...
}

@Entity
public class Articolo 
{
  @Id
  protected Long id;
  protected String titolo;
  protected String autore;

  ...
  
  @ManyToOne
  protected Categoria categoria;

  ...
}
L'annotazione @OneToMany è così definita:
@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface OneToMany 
{
  Class targetEntity() default void.class;
  CascadeType[] cascade() default {};
  FetchType fetch() default LAZY;
  String mappedBy() default "";
}
ed è identica alla definizione dell'annotazione @OneToOne. L’annotazione @ManyToOne è invece definita nel seguente modo
@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface ManyToOne 
{
  Class targetEntity() default void.class;
  CascadeType[] cascade() default {};
  FetchType fetch() default EAGER;
  boolean optional() default true;
}
Dal momento che nel caso di relazioni bidirezionali uno a molto, il lato ManyToOne è proprietario della relazione, l’elemento mappedBy nella definizione dell’annotazione non esiste.

Annotazione @ManyToMany

Le relazioni molti a molti sono quelle nelle quali da entrambi i lati della relazione è possibile fare riferimento a più istanze di uno stesso oggetto.
Una persona ad esempio può far parte di più associazioni e allo stesso tempo un’associazione può essere costituita da più persone:
@Entity
public class Associazione 
{
  @Id
  protected Long id;
  protected String nome;

  ...
  
  @ManyToMany
  protected Set<Persona> persone;
  ...
}

@Entity
public class Persona 
{
  @Id
  protected Long id;
  protected String nome;
  protected String cognome;
   
  ...
  @ManyToMany(mappedBy="persone")
  protected Set<Associazione> associazioni;
  ...
}
La definizione dell’annotazione @ManyToMany è uguale a quella dell’annotazione @OneToMany:
@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface ManyToMany 
{
  Class targetEntity() default void.class;
  CascadeType[] cascade() default {};
  FetchType fetch() default LAZY;
  String mappedBy() default "";
}
E’ possibile utilizzare l’attributo mappedBy per indicare l’entità proprietaria della relazione nell’entità subordinata.