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 .
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 metodoEJBContext.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 estendeException
(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.
- tramite
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. abbandoneremo l’EntityManager
in “favore” dell’EntityManagerFactory
per usare anche i DataSources non gestiti da JTA.