CERCA SITEMAP 1280
Ultimo aggiornamento: 30 Agosto 2009

L'interfaccia EntityManager in JPA

Le annotazioni ORM descrivono come le entità devono essere rese persistenti, l’interfaccia EntityManager invece si occupa di rendere persistenti le entità e di effettuare operazioni CRUD (Create, Read, Update e Delete) su di esse.
L’interfaccia EntityManager è probabilmente la più importante delle Java Persistence API perchè costituisce un ponte tra il mondo Object-Oriented e quello relazionale.
Quando noi richiediamo la creazione di un’entità del dominio, l’EntityManager traduce l’entità in un nuovo record nel database, se richiediamo l’aggiornamento di un’entità esso preleva i dati relazionali corrispondenti e li aggiorna, se inevece ne richiediamo la cancellazione l’EntityManager provvede a rimuoverne il record.
Viceversa quando richiediamo un’entità presente nel database, l’EntityManager crea l’entity bean, lo popola con i dati relazionali e lo restituisce indietro.

Ciclo di vita di un'entità

Un’entità ha un ciclo di vita, e l'EntityManager non sa nulla di un POJO e di come questo sia annotato finchè non comunichiamo di trattare il POJO come un’entità.
Questo comportamento è opposto rispetto ai POJO annotati come session bean o message-driven bean, i quali sono caricati e gestiti dal conteiner fin dall’avvio dell’applicazione.
Un’entità di cui l’EntityManager tiene traccia è detta attached o managed, viceversa quando un EntityManager non gestisce più un’entità questa è detta detached.
Un’entità che non è mai stata gestita dall’EntityManager è detta transient.

Entità managed

Quando parliamo di gestione dello stato di un’entità facciamo riferimento al fatto che l’EntityManager assicura una sincronizzazione fra i dati dell’entità e quelli presenti nel database.
Innanzitutto quando chiediamo all’EntityManager di gestire un’entità, questo sincronizza lo stato dell’entità con i dati presenti nel database, quindi assicura che eventuali cambiamenti apportati ai dati dell’entità siano riflessi sui record del database.
Ciò viene realizzato attraverso un controllo periodico di eventuali cambiamenti: ogni qualvolta l’EntityManager individua un cambiamento dei dati dell’entità automaticamente provvede ad aggiornare il database.
La gestione dell’entità termina quando questa viene cancellata o rimossa dal campo d’azione dell’EntityManager.
Un’entità diventa attached quando viene passata ai metodi persiste, merge o refresh oppure quando viene recuperata usando il metodo find o una query all’interno di una transazione.
Quando un’entità viene istanziata invece questa si trova ancora nello stato transient perché l’EntityManager non ne conosce ancora l’esistenza, diventa managed quando viene invocato il metodo persist che crea un nuovo record nel database per l’entità.
manager.persist(miaentita);
Un’entità managed diventa detached quando viene rimossa, serializzata o clonata.
I metodi merge e refresh sono usati per le entità che sono state recuperate dal database e sono in uno stato detached, entrambi questi metodi fanno si che l’entità diventi attached e quindi gestita dall’EntityManager.
Il metodo merge aggiorna il database con i dati memorizzati nell’entità mentre il metodo refresh aggiorna l’entità con i dati memorizzati nel database.

Entità detached

Un’entità detached è un’entità che non è più gestita dall’EntityManager e il cui stato non è più sincronizzato con il database.
E’ possibile ad esempio che un’entità venga passata al web tier, aggiornarta e inviarta nuovamente indietro all’EJB tier per effettuarne il merge al persistence context.
Essenzialmente un’entità diventa detached non appena esce fuori dallo scope dell’EntityManager.
Le entità diventano detached anche a seguito di clonazione o serializzazione: ciò accade perché l’EntityManager tiene traccia delle entità mediante riferimenti e le istanze clonate o serializzate non hanno lo stesso riferimento rispetto all’oggetto originale.
Anche il metodo clear forza tutte le entità ad essere detached così come il metodo remove che cancella i dati associati all’entità dal database.
manager.remove(miaentita);

Persistence Context

Il persistence context gioca un ruolo vitale nelle funzionalità interne dell’EntityManager.
Sebbene noi invochiamo i metodi dell’EntityManager, questo non tiene direttamente traccia del ciclo di vita di ogni singola entità ma delega questo compito al persistence context corrente.
Il persistence context è quindi una collezione di entità gestita dall’EntityManager all’interno di un dato scope.
Lo scope è l’intervallo di tempo entro il quale le entità rimangono gestite ed è usato per il persistence context allo stesso modo in cui è usato per le variabili di scope.
Esistono due tipi differenti di persistence scope: exended e transaction.
Nel primo caso, l’EntityManager può essere usato esclusivamente con sessionbean di tipo stateful.
Una volta che l’entità viene attaccata, questa viene gestita finchè l’istanza dell’EntityManager lo è: un’EntityManager con scope extended manterrà la gestione di tutte le entità attached finchè non verrà chiuso o il bean stesso distrutto.
Nel secondo caso la gestione delle entità avviene esclusivamente entro i confini della transazione.

Creazione di un'istanza EntityManager

La prima cosa da fare per poter gestire la persistenza è ottenere un’istanza dell’EntityManager.
Se siamo all’interno di un container è possibile usare l’annotazione ext, in tal modo il container si prende cura delle operazioni di lookup e dell’apertura e chiusura dell’EntityManager.
Laddove diversamente specificato lo scope di default dell’EntityManager è TRANSACTION.
JPA supporta anche il supporto di EntityManager application-managed che vengono esplicitamente creati, usati e rilasciati dall’applicazione per l’utilizzo al di fuoi del container.

EntityManager container-managed

L’utilizzo dell’annotazione ext consente di ottenere l’istanza di un EntityManager container-managed.

Tale annotazione è così definita:
({TYPE, METHOD, FIELD})
(RUNTIME)
public  PersistenceContext 
{
  String name() default "";
  String unitName() default "";
  PersistenceContextType type default TRANSACTION;
  PersistenceProperty[] properties() default {};
}
Il primo elemento dell’annotazione (name) specifica il nome JNDI del persistence context.
Questo elemento è usato nel caso in cui si voglia indicare esplicitamente il nome JNDI per una data implementazione.
L’elemento unitName invece specifica il nome della persistence unit che è essenzialmente un raggruppamento di entità usate dall’applicazione.
L’idea, quando si hanno applicazioni di una certa dimensione, è quella di separarle in aree logiche delle persistence configurate attraverso il deployment descriptor persistence.xml.
ext(unitName="nomeunita")
EntityManager manager;
L’elemento type specifica lo scope dell’EntityManager: i valori possibili sono TRANSACTION O EXTENDED.
Di default il valore dello scope è TRANSACTION, se si vuole specificare EXTENDED si può usare il seguente codice:
ext(type=PersistenceContextType.EXTENDED)
EntityManager manager;
Naturalmente non è possibile usare lo scope EXTENDED nel caso di session bean di tipo stateless o message-driven bean: lo scope EXTENDED infatti su usa quando si ha la necessità di gestire le entità attraverso invocazioni multiple dei metodi di un session bean stateful.

EntityManager application-managed

Gli EntityManager application-managed sono appropriati nel caso di ambienti nei quali non è disponibile alcun container: in questo caso occorre scrivere il codice per controllare ogni aspetto del ciclo di vita dell’EntityManager.
Un possibile uso dell’EntityManager application-managed in ambiente Java EE potrebbe essere giustificato dalla necessità di mantenere un confrollo fine sul ciclo di vita dello stesso.
E’ possibile ottenere un’istanza di un EntityManager application-managed mediante l’utilizzo dell’interfaccia EntityManagerFactory che in ambiente Java EE può essere otteuta mediante l’annotazione :
({TYPE, METHOD, FIELD})
(RUNTIME)
public  PersistenceUnit 
{
  String name() default "";
  String unitName() default "";
}
Gli elementi name e unitName hanno lo stesso significato visto per l’annotazione ext.
Il metodo createEntityManager dell’EntityManagerFactory crea un EntityManager application-managed.
Questo non partecipa automaticamente ad una transazione, piuttosto bisogna chiederlo invocando il metodo joinTransaction.

Persistenza delle entità

L’interfaccia EntityManager fornisce oltre alle funzionalità CRUD (Create, Read, Update e Delete) una serie di operazioni comunemente usate come il flushing e il refreshing.
Supponiamo di voler definire il metodo di un session bean che crea un oggetto Categoria e lo rende persistente all’interno del database, l’entità Categoria fa anche riferimento all’Utente che l’ha creata (esiste cioè una relazione uno a molti fra Utente e Categoria).
Per fare ciò tale metodo dovrà creare un’istanza dell’oggetto Categoria, popolarlo con i dati inviati dall’utente, recuperare l’entità Utente di cui si conosce l’id dal database e inserirla nell’istanza di Categoria creata ed infine invocare il metodo persist dell’EntityManager:
public Categoria aggiungiCategoria(String titolo, String descrizione, long idutente) 
{
  Categoria categoria = new Categoria();
  categoria.setTtitolo(titolo);
  categoria.setDescrizione(descrizione);
   
  Utente utente = entityManager.find(Utente.class, idutente);
  categoria.setUtente(utente);
  manager.persist(categoria);
  return categoria;
}
Se si prova a rendere persistente un’entità che viola i vincoli di integrità del database il persistence provider restituisce una java.persistence.PersistenceException inoltre il metodo persist causa anche il fatto che l’entità diventa managed.
L’istruzione INSERT che crea il record corrispondente all’entità non viene necessariamente eseguita immediatamente infatti per EntityManager con scope TRANSACTION, questa viene eseguita quando si chiude la transazione con un commit (nel nostro caso quando il metodo aggiungiCategoria ritorna) mentre per EntityManager con scope EXTENDED, l’istruzione INSERT viene eseguita prima della chiusura dell’EntityManager ovvero in qualsiasi punto venga effettuato il flush dello stesso.
Se non è presente alcuna transazione quando viene invocato il metodo persist viene prodotta una TransactionRequiredException (in application-managed).
Uno degli aspetti più interessanti delle operazioni di persistenza è la gestione delle relazioni fra le entità.
Nell’esempio precedente ad esempio abbiamo visto l’utilizzo del metodo find per recuperare l’entità Utente, già esistente, dal database e inserirla nell’entità Categoria prima dell’invocazione del metodo persist.
Supponiamo ora di voler creare una nuova istanza di Utente insieme alle informazioni InfoUtente associate (relazione uno a uno fra Utente e InfoUtente).
Il metodo del session bean potrebbe apparire nel seguente modo:
public Utente aggiungiUtente(String nome, String cognome, String info1, String info2) 
{
  Utente utente = new Utente();
  utente.setNome(nome);
  utente.setCognome(cognome);
    
  InfoUtente info = new InfoUtente();
  info.setInfo1(info1);
  info.setInfo2(info2);
    
  manager.persist(info);
  utente.setInfo(info);
  manager.persist(utente);
    
  return utente;
}
Come vediamo vengono create le istanze di Utente e InfoUtente, queste vengono popolate con i dati passati come parametri del metodo, quindi si aggiunge il riferimento all’istanza InfoUtente nell’istanza Utente e si invoca il metodo persist dell’EntityManager sull’istanza di InfoUtente e sull’istanza di Utente.
In questo caso occorre invocare due volte il metodo persist perché di default JPA non rende persistenti le entità referenziate, il che significa che qualora avessimo invocato il metodo persist esclusivamente sull’istanza di Utente, l’istanza di InfoUtente non sarebbe stata resa persistente.
Tale comportamento può essere modificato facendo uso dell’attributo cascade all’interno dell’annotazione
public class Utente 
{
  (cascade=CascadeType.PERSIST)
  public void setInfo(InfoUtente info) 
  {
    ...
L’elemento cascade in questo caso comunica all’EntityManager come propagare un’operazione di persistenza su una particolare entità verso le entità a questa collegate.
Di default l’elemento cascade è vuoto e quindi le operazioni di persistenza non vengono propagate.
I valori possono essere settati a ALL, MERGE, PERSIST, REFRESH, REMOVE.

Recuperare entità attraverso la primary key

JPA supporta diversi modi per recuperare le istanze di entità dal database, il modo più semplice è quello di recuperare un’entità attraverso la sua chiave primaria mediante il metodo find dell’EntityManager.
Utente utente = manager.find(Utente.class, idutente);
Il primo parametro del metodo specifica il tipo dell’entità che deve essere recuperata, il secondo invece specifica il valore della primary key dell’istanza da recuperare.
Il metodo find è in grado di supportare anche l’utilizzo di chiavi primarie composte: se ad esempio assumiamo che l’indentità di un utente sia costituita dal nome de dal cognome, allora questa può essere incapsulata in una classe primary key annotata con e quindi popolata e passata al metodo find.
UtentePK chiave = new UtentePK();
chiave.setNome(nome);
chiave.setCognome(cognome);
Utente utente = manager.find(Utente.class, chiave);
Se non esiste alcuna istanza corrispondente alla chiave specificata, il metodo find restituirà null o un’entità.
Una delle caratteristiche più importanti del metodo find è che utilizza il caching se il persistence provider lo supporta, quindi laddove è possibile il persistence provider restituira l’istanza in cache piuttoto che recuperarla dal database.

Modalità di fetch

L’EntityManager normalmente carica tutti i dati di un’istanza quando questa viene recuperata dal database, tale modalità è definita eager fetching o eager loading.
Il problema di questo approccio nasce quando insieme ad altri dati si fa uso anche di Large Binary Object (BLOB) il cui caricamento costituisce un’operazione particolarmente onerosa.
In questi casi si potrebbe evitarne il caricamento del BLOB ed effettuarlo esclusivamente quando è necessario: questa strategia è nota come lazy fetching.
JPA ha diversi meccanismi per supportare il lazy fetching, il piu semplice consiste nell’utilizzare l’annotazione per annotare la colonna BLOB.
(name="IMMAGINE")
@Lob
(fetch=FetchType.LAZY)
public byte[] getImmagine() 
{
  return immagine;
}
Un’eventuale istruzione SELECT generata dal metodo find per recuperare un’entità Utente in questo caso non carica la colonna IMMAGINE della tabella UTENTI, questa verrà eventualmente caricata quando la proprietà sarà acceduta mediante il metodo getImmagine() attraverso una seconda opeazione di SELECT.
Le annotazioni e sono definite di default come lazy loading.
Questo perché entrambe queste relazioni hanno una o più entità corrispondenti e quindi prelevarle tutte è costituisce un’operazione computazionalmente onerosa.

Detach e merge di entità

L’EntityManager assicura che i cambiamenti fatti alle entità attached siano salvati nel database, questo significa che la nostra applicazione non necessita di chiamare alcun metodo per aggiornare le entità.
Sebbene le entità managed siano estremamente utili, è difficile mantenerle sempre nello stato di attached, a volte infatti è necessario effettuare il detach delle entità e serializzarle al web tier dove l’entità viene modificata al fuori dallo scope dell’EntityManager.
Ad esempio se l’EntityManager ha uno scope TRANSACTION e si fa uso di CMT (Container-Managed Transaction), le entità diventano detached alla fine del metodo.
Se vogliamo “riattaccare” al persistence context così da poterla sincronizzare col database possiamo usare il metodo merge:
public Utente mergeUtente(Utente utente) 
{
  manager.merge(utente);
  return utente;
}
Il metodo merge può essere usato solo per un’entità che è esistente nel database altrimenti produce una IllegalArgumentException.
Di default le entità collegate ad un’entità di cui si è effettuato il merge non sono attached a loro volta.
Questo comportamento però può essere controllato mediante l’elemento cascade delle annotazioni , e .
Ponendo a MERGE o ALL il valore di questo elemento, le entità diventano managed.
public class Categoria 
{
  (cascade=CascadeType.MERGE)
  public Utente getUtente() 
  {
    ...
Il metodo merge deve essere chiamato da un contesto transazionale altrimenti genererà una TransactionRequiredException.

Cancellare entità

Per rimuovere un’entità dal database si fa uso del metodo remove:
public void cancellaUtente(Utente utente) 
{
  manager.remove(manager.merge(utente));
}
Affinchè il metodo remove funzioni è necessario che l’entity sia attached (ciò spiega il metodo merge nell’esempio), altrimenti si genera una IllegalArgumentException.
Così come abbiamo visto per merge o persist, affinchè le entità collegate possano essere rimosse a loro volta è necessario settare l’elemento cascade a ALL o REMOVE.

public class Utente 
{
  (cascade=CascadeType.REMOVE)
  public InfoUtente getInfoUtente() 
  {
    ...

Operazioni di flush

Le operazioni persist, merge e remove non vengono eseguite immediamente, per poterle eseguire immediatamente è necessario effetture il flush dell’EntityManager.
Di default la modalità di flush è AUTO il che significa che l’EntityManager efettua il fush automaticamente quando è necessario: ovvero alla fine di una transazione per i EntityManager con scope TRANSACTION oppure quando il persistence context viene chiuso per gli EntityManager application-managed o con scope EXTENDED.
Alternativamente è possibile settare il flush mode su COMMIT così da effettuare la sincronizzazione solo col commit della transazione.
manager.setFlushMode(FlushModeType.COMMIT);
In ogni caso è possibile effettuare la sincronizzazione quando si vuole invocando il metodo flush dell’EntityManager.

Refresh delle entità

L’operazione refresh ripopola un’entità con i dati presenti nel database.
In altre parole l’entità viene resettata e quindi popolata con i dati nel database.
public Utente annullaModifiche(Utente utente) 
{
  manager.refresh(manager.merge(utente));
  return utente;
}
L’operazione di merge viene fatta prima perché refresh lavora solo con entità managed.

Entity listener

Così come è possibile monitorare il ciclo di vita di session bean e message-driven bean mediante le annotazioni e , allo stesso modo è possibile seguire il ciclo di vita delle entità allo scopo di effettuare operazioni di logging, validazione di dati ...
I metodi lifecycle non devono essere necessariamente definiti nell’entità ma è possibile definire una classe entity listener separata.
Per farlo è sufficiente definire una classe listener e annotarne i metodi con , ,… e quindi fare uso dell’annotazione per utilizzare il listener sull’entity bean.
public class MioListener 
{
  ...
  public MioListener() 
  {
  }
   
  
  
  public void listenUtente(Utente utente) 
  {
    ...
  }
}
   
   

(miopackage.MioListener.class)
public class Utente implements Serializable 
{
  ...
Se si verifica un’eccezione nel metodo l’operazione di persistenza viene interrotta.
In alcuni casi è opportuno definire dei listener di default, applicati a tutti gli entity bean, nel deployment descriptor persistence.xml:

  ...
  
    miopackage.MioListener.class
  
Se un’entità possiede listener di default, listener specifici e listener ereditati allora i listener di default sono eseguiti per primi, quindi vengono invocati i listener ereditati.
Se ad ogni livello sono definiti più listener questi vengono invocati nell’ordine di definizione.
Non è possibile controllare da programma l’ordine di esecuzione dei listener ma è possibile disabilitare i listener di default e quelli ereditati mediante le annotazioni isteners e ssListener

isteners
ssListeners
(miopackage.MioListener.class)
public class Giocatore extends Utente 
{
  ...