EJB, JPA, eccezioni, JTA e Resource Local: facciamo un po’ di chiarezza – Parte 2

Nel post precedente 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!

Andrea Como

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+