Nel abbiamo affrontato EJB, JPA e JTA nel loro utilizzo più frequente (e più semplice). Vediamo adesso di capire cosa possiamo fare quando rinunciamo all’EntityManager
a favore dell’EntityManagerFactory
. Perché scendere di un livello e avere più controllo? In un contesto Java EE magari non è molto utile perché complica il codice, ma è bene sapere cosa si può fare a partire da questa factory.
La fabbrica di EntityManager
In un contesto Java SE, per lavorare con JPA bisogna necessiamente passare dall’EntityManagerFactory
(a meno che non si usi Spring…), e comunque il tipo di DataSource sarà quasi sicuramente di tipo Resource Local (RL). In Java EE invece le combinazioni possono essere più disparate…
Prima cosa da ricordare:
una volta creato un EntityManager dalla factory, ricordiamoci di chiuderlo per liberare le risorse.
Seconda cosa: rinfreschiamo gli acronimi introdotti nel post precedente.
- CMT: Container Managed Transaction
- BMT: Bean Managed Transaction
- Datasource JTA: Datasource gestito da Java Transaction API
- Datasource RIL: Datasource di tipo “Resource Local”
Le mille varianti
Riprendiamo quindi il contesto Java EE: prendiamo un EJB stateless, teniamo fisso l’EntityManagerFactory
e proviamo a variare:
- tipo di gestione delle transazioni (CMT o BMT)
- tipo di datasource (JTA o RL)
Vediamo cosa succede!
CMT e JTA
Lasciamo quindi gestire le transazioni al container: il DataSource può essere tranquillamente JTA:
@PersistenceUnit private EntityManagerFactory emf; public void save(Person person) { EntityManager em = emf.createEntityManager(); try { em.persist(person); } finally { em.close(); } }
Per ottenere la factory basta usare l’annotazione @PersistenceUnit
(occhio che per l’EntityManager invece era @PersistenceContext
!). Il codice è molto semplice: viene creato l’EntityManager e nella clausola finally
viene chiuso, mentre la transazione per eseguire il il salvataggio della persona è gestita dal Container.
CMT e RL
Il titolo in questo caso è barrato perché sostanzialmente non si può fare: non possiamo chiedere al container di gestire transazioni su datasource Resource Local perché non sono di sua competenza: dovranno essere gestite per forza manualmente. Il codice dell’esempio precedente quindi se eseguito su un datasource RL non darà errore, ma non verrà eseguito nessun commit!
BMT e RL
Quello che possiamo fare quindi in questo momento è gestire la transazione: essendo fuori JTA, non possiamo usare l’interfaccia UserTransaction
, ma in questo caso abbiamo a disposizione l’interfaccia EntityTransaction
.
@Stateless @TransactionManagement(TransactionManagementType.BEAN) public class PersonEmfBmtRlService { @PersistenceUnit private EntityManagerFactory emf; public void save(Person person) { EntityManager em = emf.createEntityManager(); EntityTransaction transaction = em.getTransaction(); try { transaction.begin(); em.persist(person); transaction.commit(); } catch (Exception e) { e.printStackTrace(); if (transaction.isActive()) { transaction.rollback(); } throw e; } finally { em.close(); } } }
Avevate già notato il metodo EntityManager#getTransaction()? Non era permesso chiamarlo in contesti JTA, ma adesso possiamo (anche perché è l’unico modo di gestire le transazioni!!). Il codice perde molta dell’eleganza vista negli esempi precedenti (diventa un po’ procedurale – crea entity manager, apri transazione, persisti, committa), concetti che stanno molto bene relegati ad un contesto AOP, ed è proprio quello che fa normalmente EJB in un contesto CMT.
In due parole quindi:
-
UserTransaction
: gestisce transazioni in contesti JTA. Si inietta direttamente nell’EJB tramite@Resource
. -
EntityTransaction
: gestisce transazioni fuori da contesti JTA. Si ottiene dal’EntityManager.
BMT e JTA
Gli ultimi due esempi forse sono un po’ estremi, non così frequenti, almeno in un contesto EE.
Più facile invece è potersi imbattere in un caso in cui il DataSource sia JTA ma la transazione vada gestita manualmente. Il codice è semplice, ma fate molta attenzione all’ordine con cui prima viene aperta la transazione e poi creato l’EntityManager:
@Stateless @TransactionManagement(TransactionManagementType.BEAN) public class PersonEmfBmtJtaService { @PersistenceUnit private EntityManagerFactory emf; @Resource private UserTransaction transaction; public void save(Person person) { EntityManager em = null; try { transaction.begin(); em = emf.createEntityManager(); em.persist(person); transaction.commit(); } catch (Exception e) { try { e.printStackTrace(); transaction.rollback(); throw new RuntimeException(e); } catch (IllegalStateException | SecurityException | SystemException e1) { e1.printStackTrace(); } } finally { em.close(); } }
Se invertiamo le due righe evidenziate, ovvero creiamo l’EntityManager prima dell’apertura della transazione, in pratica l’EntityManager non si accorge che c’è una transazione attiva a meno che non venga chiamato esplicitamente il metodo l’
EntityManager#joinTransaction()
(non proprio comodo…).
Riassumendo
Cerchiamo di tirare le fila di tutte le permutazioni fatte in queste prove con la seguente tabella:
CMT | BMT | |
---|---|---|
JTA |
EntityManager EntityManagerFactory
|
EntityManager + UserTransaction EntityManagerFactory + UserTransaction
|
RL | — |
EntityManagerFactory + EntityTransaction
|
In questo modo, le chiacchiere fatte fino adesso assumono una connotazione più chiara: a partire da questa tabella, la prossima volta che avrò a che fare con EntityManagerFactory
, EntityManager
, UserTransaction
o EntityManagerFactory
saprò come incastrarli!