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

Mi è capitato di leggere nei forum: “chi ha più bisogno degli EJB? Ormai è roba vecchia”. Posizione opinabile a mio avviso: in Java EE 5 e soprattutto nella 6, creare un EJB si è ridotto alla scrittura di un POJO annotato, per cui perché fare a meno delle funzionalità che la specifica ci offre? Quali dite? In primis JTA e la gestione automatica delle transazioni, per esempio legate a JPA o JMS. EJB in coppia con JTA sono sufficienti per la maggior parte degli scenari che coinvolgono JPA: grazie all’EntityManager abbiamo il controllo delle operazioni sulle nostre entità, gestite automaticamente in modo ACID all’interno di un EJB. Ma cosa fare quando abbiamo bisogno di più controllo? E se sono in un ambiente dove non ho EJB e JTA ma solo JPA? Approfondiamo tutti gli scenari insieme all’interno di un ambiente EE (JBoss in questo caso).

Non dimentichiamo le basi

Anche se per creare un EJB basta un POJO annotato con @Stateless o @Statefull, è bene avere presente che ciclo di vita ha questo tipo di oggetti, come ci ricorda il post di Fabio.
Una cosa che spesso si dà per scontata in ambiente EE, è la tipologia di Data Source (DS). Ne esistono di due tipi:

Java Transaction API (JTA)
Il DS può essere gestito da JTA: questo significa che le transazioni sono gestite dal container Java EE (ovvero dagli EJB), e possono interessare più risorse. E’ il comportamento di default se non viene specificata la tipologia.
RESOURCE_LOCAL (RL)
le transazioni su questo tipo di DS vanno gestite manualmente: EJB fornisce una api di supporto (che vedremo nel prossimo post) per non arrivare a scomodare JDBC.

La gestione delle entità tramite EntityManager e delle transazioni sulle entità stesse può essere fatta in svariati modi: vediamo di capirci meglio. Tutti gli esempi che seguono presuppongono che si lavori all’interno di un EJB Stateless.

Data Source JTA, CMT e BMT

Aggiungiamo altri acronimi e cominciamo col considerare il caso standard: DS JTA, Entity Manager iniettatato in un EJB Stateless: in questa configurazione, le transazioni possono essere gestite dal container (Container Managed Transactions, CMT) o delegate al programmatore (Bean Managed Transactions, BMT).

EJB, JTA e Container Managed Transactions

Se non specifichiamo niente, la transazione è CMT. Immaginiamo quindi di dover salvare un’entità “Persona”: preoccupandoci solo di dover iniettare l’Entity Manager, lo scenario di default prevede

  • DS JTA
  • EJB con EntityManager
  • transazioni CMT

l’EJB sarà semplicemente:

@Stateless
public class PersonService {

   @PersistenceContext
   private EntityManager entityManager;
   
   public void save(Person person) {
      this.entityManager.persist(person);
   }
}

Sembra banale, ma questo è un caso molto frequente. Gli EJB, se non diversamente specificato, hanno la caratteristica di eseguire il loro metodi in modo transazionale: o arrivano al termine (seguendo un eventuale commit all’uscita del metodo), o eseguono un rollback. Tramite l’annotazione @TransactionAttribute, è possibile dare delle indicazioni al container su come gestire le transazioni, grazie a valori dell’enum TransactionAttributeType:

REQUIRED
Comportamento di default di ogni EJB (se non specificato niente). Ogni metodo viene eseguito in modo transazionale: se esiste una transazione attiva, il container si aggancia ad essa, altrimenti ne apre una nuova.
REQUIRES_NEW
Il metodo annotato viene sempre eseguito in una nuova transazione, indipendentemente dalla transazione (eventuale) del chiamante. Se esiste una transazione attiva, questa viene sospesa e se ne crea una nuova. Al termine, viene riattivata la transazione precedentemente sospesa. Questo perché con JTA un thread può essere associato ad una sola transazione.
SUPPORTS
Se esiste una transazione attiva, l’esecuzione del metodo si aggancia ad essa, altrimenti non viene aperta nessuna transazione
NOT_SUPPORTED
Il metodo non verrà mai eseguito in transazione: se ne esiste una attiva, viene sospesa durante l’esecuzione del metodo annotato.
NEVER
Simile al caso NOT_SUPPORTED, con la differenza che viene lanciata una RemoteException se viene trovata una transazione attiva.
MANDATORY
Per eseguire il metodo annotato deve essere attiva una transazione, altrimenti viene lanciata una TransactionRequiredException.

Per esempio:

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void save(Person person) {
   this.entityManager.persist(person);
}

Benissimo! Rimane solo da capire come gestire il rollback in caso di errori.

EJB Exception Handling: gestiamo le eccezioni EJB

La specifica in merito è molto chiara ed è legata al tipo di eccezioni:

Rollback implicito
Se nel metodo dell’EJB viene lanciata una RuntimeException (Unchecked Exception), il container la interpreta come errore ed esegue automaticamente il rollback della transazione.
Rollback esplicito
E’ possibile indicare al container di eseguire il rollback in modo programmatico in due modi:
  • tramite EJBContext: è necessario iniettare nell’ejb l’EJBContext (semplicemente tramite annotation @Resource), catturare l’eccezione e chiamare il metodo EJBContext.setRollbackOnly(). In questo modo la transazione verrà marcata come “rollbackabile” (scusate la parolaccia 🙂 )
    ...
    @Resource
    private EJBContext context;
    	
    public void saveOrRollback(Person person) {
       try {
          this.entityManager.persist(person);
       } catch (Exception e) {
          e.printStackTrace();
          this.context.setRollbackOnly();
       }
    }
    
  • tramite @ApplicationException: se creiamo una nostra eccezione applicativa che estende Exception (di tipo checked quindi), normalmente non verrà eseguito il rollback. Se però annotiamo l’eccezione con @ApplicationException(rollback=true), allora indicheremo al container di dover eseguire il rollback per noi.

EJB, JTA e Bean Managed Transactions

Cosa succede invece quando abbiamo bisogno di un controllo più fine delle transazioni? Ovviamente JTA non ci nega il permesso ed è così che ci metta disposizione l’interfaccia UserTransaction.
Nel caso quindi di:

  • DS JTA
  • EJB con EntityManager
  • transazioni BMT

il nostro EJB si complica leggermente perché dobbiamo dire al container di non gestire le transazioni, ma il compito spetterà al nostro codice:

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class PersonService {

   @PersistenceContext
   private EntityManager entityManager;
	
   @Resource
   private UserTransaction transaction;
	
   public void save(Person person) {
      try {
         this.transaction.begin();
         this.entityManager.persist(person);
         this.transaction.commit();
      } catch (Exception e) {
         try {
            this.transaction.rollback();
      } catch (IllegalStateException | SecurityException
              | SystemException e1) {
         e1.printStackTrace();
      }
   }
}

Essendo in ambito JTA, la UserTransaction permette di gestire transazioni distribuite proprio come se fosse JTA stesso ad occuparsene. Vedremo prossimamente infatti che esiste un altro tipo di interfaccia che permette di gestire le transazioni, chiamata EntityTransaction. Questo tipo di transazione è incompatibile con JTA, quindi dovremo usare DataSources Resource Local, che vedremo nel prossimo post.

Conclusioni

L’argomento non è dei più entusiasmanti, ma a me è servito per fare chiarezza su tutte le varianti che si possono ottenere con l’utilizzo di JTA e EJB. Con JTA le cose si presentano in modo molto semplice e lineare, basta ricordarsi un po’ di teoria per sapere come il container gestisce le transazioni in base alle annotazioni sui metodi e alle eccezioni che questi possono lanciare. Nel prossimo post abbandoneremo l’EntityManager in “favore” dell’EntityManagerFactory per usare anche i DataSources non gestiti da JTA.

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+

  • Maddalena

    Vorrei aggiungere una cosa che ho appena scoperto riguardo all’uso dell’annotation @ApplicationException(rollback=true).

    La visibilità di questa annotation è limitata al jar che la contiene. Dunque se la stessa eccezione così annotata viene lanciata da un ejb che appartiene ad un jar diverso (ovviamente dipendente dal jar che la contiene), non avverrà il rollback della transazione!

    L’ho scoperto perché nel progetto a cui sto lavorando stiamo cercando di “modularizzare” l’applicazione e quindi abbiamo separato due jar che contengono ejb ed entity relativi a funzionalità diverse, ma per segnalare gli errori usiamo la stessa eccezione che quindi è definita in uno solo dei due jar. Improvvisamente ci siamo accorti che quando veniva lanciata l’eccezione da un ejb appartenente al jar che *non* contiene l’eccezione, la transazione veniva committata nonostante l’annotation!

    In seguito ad alcune ricerche su Google ho scoperto che si può risolvere il problema inserendo nella cartella META-INF del jar che *non* contiene l’eccezione un file ejb-jar.xml così definito:

    com.mypackage.exception.MyException
    true

    • Grazie del contributo Madda!! Su che application server si verifica?

      • Maddalena

        Noi per questo progetto stiamo usando WildFly 8.0!

        • Mi puzza tanto di bug… è uscito WildFly 8.2, chissà se lì funziona 😉

  • matfur92

    Ciao,
    proprio oggi mi sono imbattuto nel problema delle transazioni vedendo un bel “You cannot commit during a managed transaction!”.
    Sto utilizzando Spring all’interno dell’EJB nel quale faccio l’@Autowired di un mio DAO che al suo interno è assente di metodi di commit() in quanto lo utilizzo senza problemi nel WAR nel quale ho una configurazione del genere sul file di configurazione di spring:

    In questo modo li riesco ad utilizzare il transaction-manager liberamente. Ma nell’EJB tutto ciò non funziona.

    Come posso utilizzare @TransactionAttribute per riuscire ad utilizzare il mio DAO?

    Grazie mille in anticipo per eventuali suggerimenti e complimenti ancora per i due articoli molto interessanti 😉