Java EE: Persistenza e Transazioni

Java EE: Persistence Api, Transazioni ed EntityManager
G.Morreale

Introduzione:

L'obiettivo di questo articolo è spiegare come poter controllare le transazioni dell'Entity Manager confrontando una gestione JTA da una gestione Locale.

Ripasso sulle Transazioni

Un transazione è una sequenza di operazioni.

Esempio di transazione in mysql:

BEGIN;
UPDATE Tabella3 SET colonna3 = x;
SELECT colonna1 FROM Tabella;
UPDATE Tabella2 SET colonna2 = y;
COMMIT;


La transazione può terminare correttamente, in questo caso una particolare istruzione: COMMIT, indica al gestore delle transazioni o al DBMS di rendere persistenti tutte le operazioni che compongono la transazione.
Oppure la transazione può terminare NON correttamente (in anticipo), in questo caso si utilizza un istruzione di ROLLBACK con la quale l'effetto delle operazioni finora eseguite dalla transazione non viene reso persistente.
Rifacendoci all'esempio se la lettura di "colonna1" ha un valore tale da richiedere l'annullamento della prima UPDATE allora si richiede il ROLLBACK, utilizzando l'apposita istruzione.

La transazione per poter essere tale deve godere di un insieme di proprietà, particolarmente significative nei sistemi in cui possono essere lanciate diverse transazioni contemporaneamente.

L'insieme di proprietà che caratterizzano la transazione viene indicato con l'acronimo ACID

  • Atomicità: La transazione è un elemento indivisibile. Non è ammessa un esecuzione parziale della transazione.

  • Coerenza: Una transazione deve rispettare i vincoli di integrità

  • Isolamento: Se più transazioni vengono eseguite contemporaneamente, l'effetto deve prescindere dalle altre transazioni.

Riguardo L'isolamento sono previsti quattro livelli di isolamento:

      • Read Uncommitted: Una transazione può leggere i dati scritti dalle transazioni concorrenti. Ciò vuol dire che se una transazione fallisce le altre possono aver letto dei dati non validi.

      • Read Committed: Gli aggiornamenti di una transazione T1 vengono resi visibili a una transazione T2 non appena T1 effettua il consolidamento(Transazione terminata a buon fine - COMMIT)

      • Repeatable Read: Gli aggiornamenti di una transazione T1 vengono resi visibili al consolidamento, in più T2 se è partita prima del consolidamento continuerà a leggere gli stessi dati(quelli non aggiornati da T1) fino alla sua fine(intendo la fine di T2).

      • Serializable: Ha gli stessi vincoli del caso precedente, in più la lettura di un dato da parte di T1 provoca il blocco degli aggiornamenti fino al termine della transazione.

  • Durabilità: Gli effetti di una transazione terminata correttamente devono essere persistenti nel tempo.

La concorrenza della transazioni porta a diverse anomalie(per capire attraverso degli esempi clicca qui)

  • Lost Update: perdita di aggiornamenti effettuati da una transazione
  • Dirty Read:una transazione T1 legge uno stato non più effettivo a causa di un rollback da parte di T2
  • Unrepeatable Read:T1 rilegge gli stessi dati e li trova cambiati
  • Phantom Read:Per effetto di inserimenti di nuove tuple una transazione riesegue una query ottenendo risultati diversi.

Di seguito una tabella riepilogativa che mostra quali anomalie vengono risolte con i diversi tipi di isolamento:

Dirty readNon repeatable readPhantom readLost updates
Read uncommittedSISISISI
Read committed
NOSISINO
Repeatable readNONOSINO
SerializableNONONONO

Java EE e la persistenza

Dopo aver brevemente richiamato i concetti chiave relativi alle transazioni, cerchiamo di capire quali sono gli attori e gli elementi che entrano in gioco quando si parla di ejb e persistenza in ambito Java EE.

Mapping ORM

Gran parte dei progetti software richiedono l'esistenza di un dbms per la gestione dei dati.
I dbms nella maggior parte dei casi sono di tipo relazionale, quindi l'elemento chiave è la relazione(una relazione è una tabella.. ) a differenza della programmazione object oriented  dove l'elemento chiave è l'oggetto.

Per far convivere questi due modi di vedere delle componenti esistono dei framework che si occupano di creare una corrispondenza biunivoca tra una rappresentazione ad oggetti e una relazione(http://it.wikipedia.org/wiki/Object-relational_mapping).

Di seguito un elenco di alcuni di questi framework:


Grazie a questi framework è rendere portabile(o quasi!!) un componente software rispetto al dbms usato(E' possibile ad esempio passare da mysql ad oracle senza modificare il codice del progetto).


JSR 220 - Enterprise Java Bean

La specifica JSR-220 prevede la definizione delle specifiche riguardanti le varie tipologie di Java Bean.
Visto la tipologia di articolo mi soffermerò su accennare solo a parte di tale specifica, ovvero quella relativa alle Persistence API.

Si è parlato di mapping tra relazione ed oggetti.

Bene, gli oggetti sui quali si mappano le relazioni (Tabelle e righe) sono gli EntityBean.
Un EntityBean è una classe dotata di particolari annotation (Es. @Entity) (o in alternativa di opportuni descrittori xml) e altri vincoli.. in grado di rappresentare una relazione.

Chi si occupa di gestire gli EntityBean?
Gli EntityBean sono gestiti dall'EntityManager

L'EntityManager, è l'oggetto associato al persistence context.

Il persistence context è un insieme di istanze di entity identificate univocamente. All'interno del persistence context gli entity e il loro lifecycle sono managed(sotto il controllo del persistence context).

Persistence Unit:Indica quali classi sono gestite da un determinato EntityManager nonché a quale datasource fanno riferimento, è una sorta di "elemento di configurazione"

L'EntityManager, quindi, interagisce con un persistence context al fine di effettuare creazione, eliminazione, aggiornamento, ricerca e query sui vari entity del contesto, nonchè comunicare con il db al fine di permettere il mapping tra la rappresentazione ad oggetti e quella relazionale dei dati.



Transazioni, Scope etc.. Operare delle scelte

Le scelte da operare sono diverse:

Chi decide quando creare o eliminare un EntityManager? L'applicazione o il container?

Le opzioni sono due, o lo gestisce il container o lo gestisce l'applicazione generando e chiudendo con apposite istruzioni l'entityManager.
Se vogliamo che in automatico venga generato e distrutto un entityManager dal container l'unica possibilità è quella di optare per un EntityManager JTA

Container-Managed Persistence Context (JTA Entity Manager)

@Stateless
public class TestImpl implements TestLocal
{
//EntityManager ottenuto tramite dependency injection
@PersistenceContext EntityManager em;

    public Dato getDato(Long id)
    
        return em.find(Dato.class, id); //Uso l'entityManager senza occuparmi di crearlo, e distruggerlo
    }
}

Se invece decidiamo di controllare la vita dell'entityManager si può optare per un entityManager JTA oppure entityManager ResourceLocal.

Application-Managed Persistence Context (JTA Entity Manager)

@Stateless
public class TestImpl implements TestLocal
{
//EntityManager ottenuto tramite dependency injection
@PersistenceUnit EntityManagerFactory emf;

    public Dato getDato(Long id)
    
             EntityManager em = emf.createEntityManager();//Inizializzo l'entityManager
        Dato = (Dato)em.find(Dato.class, id); //Uso l'entityManager
             em.close();//Chiudo l'entityManager            
    }
}

Application-Managed Persistence Context (Resource Local Entity Manager)

@Stateless
public class TestImpl implements TestLocal
{


    public Dato getDato(Long id)
    
             EntityManagerFactory emf = Persistence.createEntityManagerFactory();
             EntityManager em = emf.createEntityManager();//Inizializzo l'entityManager
             em.getTransaction().begin();
        Dato = (Dato)em.find(Dato.class, id); //Uso l'entityManager
             em.getTransaction().commit();
             em.close();//Chiudo l'entityManager            
    }
}

In entrambi i casi viene creato(createEntityManager()) e distrutto(close()) dall'applicazione.
Però con la gestione JTA la transazione inizia e termina con l'inizio e la fine del metodo nel secondo caso invece l'inizio e la fine della transazione sono decisi dallo sviluppatore (quindi dall'applicazione)

Inoltre nel caso di transazioni JTA controlled, il rollback viene richiamato nel caso in cui si verifica un eccezione all'interno del metodo. Altra possibilità di Rollback è l'invocazione del metodo setRollbackOnly (appartenente all'interfaccia EJBContext).


Un'altra scelta da effettuare, in qualsiasi dei casi visti precedentemente, è quella relativa allo scope del persistence context.
Le opzioni sono due:

  • Transaction Scoped Persistence Context
  • Extended Scoped Persistence Context


Questa scelta influisce sulla vita di un persistence context.

Nel primo caso esso viene generato e distrutto insieme all'inizio e alla fine di una transazione sia essa gestita da un JTA EntityManager che da un ResourceLocal EntityManager.

Nel secondo caso invece, la vita del persistence context non è legata all'inizio e alla fine di una transazione, ma è legata alla vita dell'EntityManager.
Nel caso in cui l'EntityManager è gestito dal container il persistence context viene creato non appena esso è ottenuto attraverso operazione di lookup o dependency injection, nel caso di EntityManager gestito dall'applicazione esso viene creato e distrutto rispettivamente sulle istruzioni EntityManagerFactory.createEntityManager(), EntityManager.close().

Conclusione

Questo è un articolo in un certo senso "difficile" in quanto entrano in gioco diversi e diversi concetti.
Più che altro è un quadro d'insieme per chi già conosce questi concetti, spero però che sia anche uno punto di partenza per chi non li conosce ancora.

Riferimento: JSR 220 Documentation.



18 comments:

Bako said...

Ciao.
ho data un'occhiata veloce all'articolo, ma non ho ben capito.
Se all'interno di uno stateless ejb specifico:
@PersistenceContext
private EntityManager entityManager;

e poi all'interno di un metodo richiamo dell query normali tipo MyObject myObject = (MyObject) em.createQuery("select * from a where id = 1").getSingleResult();

myObject.setQuantity(myObject.getQuantity() - 1);

dove Quantuty è una colonna della tabella.

ora, la transizione è gestita in automatico? o nessuno la gestisce?
la scrittura sul db avviene ad ogni setter dell'entitymanager?

grazie

Giuseppe Morreale said...

Se l'entitymanager è configurato come jta (Vedi il persistence.xml) allora la transazione è gestita in automatico (ad esempio il jta fà partire il commit sull'uscita dal metodo in question)

se l'entitymanager ha una gestione locale allora il commit viene lanciato da un istruzione specifica che inserisce il programmatore a sua esigenza.

quindi la scrittura sul db non viene ad ogni setter, ma ad ogni committ. e i commit avvengono in momenti diversi a secondo del tipo di entity manager.

Bako said...

Ciao
sei io configuro così:



<persistence version="1.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">

&#8722;

<persistence-unit name="LoginModulePU" transaction-type="JTA">

<provider>org.hibernate.ejb.HibernatePersistence</provider>

<jta-data-source>java:/DefaultDS</jta-data-source>

&#8722;

<properties>

<property name="hibernate.hbm2ddl.auto" value="update"/>

</properties>

</persistence-unit>

</persistence>"

ho un jta che gestisce in automatico le transazioni. ora, di che tipo è? è Read Committed?
altra cosa, nella fine del post parli Transaction Scoped, Extended Scoped. Ora, specificiato com'è ho un Extended scope, giusto?

grazie ciao

Giuseppe Morreale said...

Lo scope è TransactionScope.
Riguardo lo stato di isolamento della transazione se non sbaglio devi guardarlo e configurarlo a livello di datasource.

Bako said...

grazie mille per tutto.
hai mica una guida approfondita per queste cose?

un'altra cosa, se specifico:
@PersistenceContext

ho le transazioni applicate tutte le volte che utilizzo? (quindi spreco di tempo?)

Giuseppe Morreale said...

La guida la trovi scaricandoti le specifiche per "JSR 200 Java Persistence API".

L'ultima domanda non l'ho capita bene, spiegati meglio :)

Bako said...

se specifico:
@PersistenceContext
private EntityManager entityManager;

ho le transazioni create in automatico, giusto?
queste transazioni vengono create ogniqualvolta sia richiesta un'azione sul db e chiusa alla fine del metodo.
giusto?
così facendo non ho una perdita in performance avendo transizioni per ogni metodo (se ho delle select non è detto che mi serva una transizione, o no?)?

Giuseppe Morreale said...

ho le transazioni create in automatico, giusto?

SI

queste transazioni vengono create ogniqualvolta sia richiesta un'azione sul db e chiusa alla fine del metodo.
giusto?

SI

così facendo non ho una perdita in performance avendo transizioni per ogni metodo (se ho delle select non è detto che mi serva una transizione, o no?)?

NO, in linea di massima non hai perdita di prestazioni.

Bako said...

scusa ma una domanda mi sorge spontanea, forse può sembrarti sciocca ma:
Perchè diavolo dovrei fare le transazioni a mano se ho la possibilità di averle in automatico?
dove sta il problema per cui hanno lasciato la libertà di poter scrivere le transazioni a mano nel coidice?

grazie

Giuseppe Morreale said...

La transazione ha la caratteristica di essere un operazione atomica.
inoltre se all'interno di un transazione una delle operazioni che la compongono no và a buon fine viene fatto un roolback annullando anche quelle precedenti.
Quindi uno dei motivi per cui hai libertà di effettuare il commit a "mano" è per darti la possibilità di rendere indipendenti una parte di operazioni all'interno di un metodo in modo che un errore su quelle successive non annullano le precedenti.

Cmq per capire bene devi studiare il concetto di transazione a se.
poi applicarlo al discorso java ee.

Bako said...

giusto, è una cosa a cui non avevo pensato. più che altro perchè i miei metodi sono tutti fatti a transazione: cioè ogni metodo deve essere completo al 100% per avere il commit e non ho casi come i tuoi citati. quindi posso tranquillamente usare il context.

ma anche sulle select applica una transazione?

Giuseppe Morreale said...

certo, perchè i risultati della select all'interno di una transazione tengono conto di eventuali insert o update avvenute nella transazione stessa precedentemente.

Bako said...

Ciao,
mi sono trovato con un problema:
ho diciamo un entity per ogni tabella.
ora, ho un stateful ejb che richiama gli stateless ejb (gli entity sopracitati per capirci, a dire il vero sono session bean per entity, non so come vanno chiamati).
con questi ejb devo farci insert e update.

ora, tt questo metodo deve essere in una transazione (perchè devo completarli tutti), come si fa?

per capirci:

@EJB
Entity1 e1
@EJB
Entity3 e2
@EJB
Entity2 e3

(sono sempre gli session bean che gestiscono gli entity)

public void metodo(){

e1.create(foo1)
e2.create(foo2)
e3.create(foo3)
}

tt e 3 devono essere completati.come si fa?

altra cosa, come posso sapere l'id di un entity?
mettiamo che la mia entity si utente: -id - nome - cognome.

ora faccio

public void crea(String n, String c){
Utente utente = new Utente()
utente.setName(n);
utente.setCognome(c);
(che è sempre un session bean che gestisce l'entity)
entityUtente.create(utente)

qui, come recupero l'id del utente appena creato?
}

spero di essermi spiegato.
grazie.

Giuseppe Morreale said...

http://forum.html.it/forum/forumdisplay.php?s=e8106c34d46f924c988cd2ba8a96544d&forumid=79

http://www.javaranch.com/

http://forums.sun.com/index.jspa

Questi sono alcuni dei forum, in cui sicuramente otterrai un supporto adeguato.

Di solito tendo a dare supporto solo su questioni strettamente inerenti all'articolo.

Non è un non volerti aiutare, ma prendila come una direzione su cui muoverti per risolvere le tue problematiche. Un FORUM è posto nettamente più adatto, lo dico anche nel tuo interesse.

Stefano T said...

Salve
mi è sorto un dubbio, ma l'entityMangaer annotato come @PersistenceContext che tipo di transizioni esegue tra

Required

RequiresNew

Mandatory

NotSupported

Supports

Never

grazie

PS: sui forum non ho ricevuto risposte esurienti
PPS:sarebbe utile magari un post che tratta questa particolarità delle transazioni (se già non c'è)

Giuseppe Morreale said...

Se non ricordo male di default dovrebbe essere "Required".

per chiarire ogni dubbio bisognerebbe spulciare la jsr 220.

stefano said...

grazie mille.
Una guida eccellente che riassume in poche righe cose che ho messo insieme leggendo tantissime fonti incomplete, ambigue, o di complicata comprensione.

Giuseppe Morreale said...

Grazie a te per i complimenti!