Exceptions dans les EJB
Par Cédric Tabin le mercredi 10.03.2010, 08:00 - Java - Lien permanent
La gestion des exceptions en Java EE peut s'avérer être un sacré casse tête, surtout lorsqu'une exception est lancée d'un Bean à un autre.
J'ai déjà parlé des exceptions dans différents billets (ici, et la). Toutefois, le problème de fond n'ayant pas été expliqué (du moins sur ce blog), voici un billet qui détaille un peu mieux leur fonctionnement.
La première chose à comprendre est que lorsqu'un appel est fait à un (Session) Bean, une transaction est créée puis "committée" lorsque l'exécution de la méthode appelée est terminée. Si une exception imprévue remonte, alors cette transaction sera annulée puis "rollbackée".
Il existe différentes manières de gérer les transactions au sein des Bean via des annotations mais cela fera l'objet d'un autre billet. Ce qui est important de savoir est qu'une exception qui remonte à l'Application Server va par défaut invalider la transaction en cours.
Afin de bien comprendre de quoi il en retourne, je vais traiter ici de trois types d'exception :
- les Checked Exception
- les Unchecked Exception
- les Unchecked Exception en tant qu'Application Exception
Voici le code des différentes classes que je vais utiliser :
// Checked Exception public class MyException extends Exception { }
// Unchecked Exception public class MyRuntimeException extends RuntimeException {}
// Unchecked Exception (Application Exception) @ApplicationException public class MyRuntimeApplicationException extends RuntimeException {}
Et voici le Session Bean qui permet de lancer celles-ci :
@Stateless @LocalBean public class ExceptionBean { public void throwException() throws MyException { throw new MyException(); } public void throwRuntimeException(boolean applicationException) { if (applicationException) throw new MyRuntimeApplicationException(); throw new MyRuntimeException(); } }
L'idée est donc de voir ce qui se passe lorsque ces méthodes sont appelées depuis un autre Session Bean !
@Stateless public class FacadeBean implements FacadeBeanLocal { @EJB private ExceptionBean exceptionBean; @Override public void testException() { try { exceptionBean.throwException(); } catch(Exception exception) { e.printStackTrace(); } } }
Concernant la sortie, rien de spécial, la Checked Exception MyException a bien été récupérée (toutefois, la transaction n'est pas invalidée) !
ch.capi.exception.MyException: null at ch.capi.bean.ExceptionBean.throwException
Maintenant qu'en est-il lors de l'appel à la méthode exceptionBean.throwRuntimeException(false) ? Et bien, voici ce que donne la sortie :
javax.ejb.EJBTransactionRolledbackException at com.sun.ejb.containers.BaseContainer.mapLocal3xException(BaseContainer.java:2204)
Rien à voir avec la classe MyRuntimeException et pour cause, celle-ci a été encapsulée dans une EJBException. Cela peut paraître surprenant, mais c'est clairement écrit dans la spécification Java EE : toutes les Unchecked Exception standardes doivent l'être !
Pour que cette encapsulation n'ait pas lieu, il faut utiliser l'annotation @ApplicationException et ainsi obtenir la sortie escomptée :
ch.capi.exception.MyRuntimeApplicationException: null at ch.capi.bean.ExceptionBean.throwRuntimeException
Qu'en est-il des Unchecked Exception autres que celles créées pour le projet (du style java.lang.NullPointerException) ? Et bien il est possible de les définir comme Application Exception dans le fichier de configuration ejb-jar.xml :
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar xmlns = "http://java.sun.com/xml/ns/javaee" version = "3.1" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"> <assembly-descriptor> <application-exception> <exception-class>java.lang.NullPointerException</exception-class> <rollback>true</rollback> </application-exception> </assembly-descriptor> </ejb-jar>
Dans tous les cas ci-dessus, la transaction est invalidée. Cela signifie que si par la suite un appel est fait à un autre Bean durant la même transaction, une exception va être lancée. J'ai décrit ce comportement plus en détail dans ce billet.
Il est possible de spécifier qu'une exception ne provoque pas l'invalidation de la transaction (rollback=false), mais cela peut conduire à des effets de bord très problématiques (par exemple lorsqu'on modifie différentes tables au sein d'une base de données).
J'ai mis en annexe un projet NetBeans 6.8 - GlassFish v3 qui permet de tester exactement les comportements décrit ci-dessus !
Commentaires
Euh, t'es bien sûr que la Checked Exception rollback la transaction, il me semble que non.
Cependant il est possible de provoquer le rollback en annotant l'exception avec @ApplicationException(rollback=true) (ou dans ejb-jar.xml). Ou d'explicitement appeler setRollbackOnly() lors du catch de l'exception.
Oui merci j'ai corrigé
Dans ton dernier exemple, tu defini la NullPointerException comme étant une erreur applicative qui fait faire un rollback, qu'elle est la difference entre ce cas et le cas par defaut, car la NullPOinterException est une unchecked exception qui fait faire un rollback également non?
Salut,
Par défaut, une unechecked exception provoque l'annulation de transaction. En l'ayant définie comme Application exception dans le xml, je fais en sorte qu'elle ne soit pas encapsulée dans une EJBException, ce qui fait que je peux faire un try{...}catch(NullPointerException e){...} !
@++
Ah, je comprend maintenant, c'est que tu peux faire un try/catch du coté client tout en spécifiant si le container doit annuler ou pas la transaction lorsque qu'il traite l'exception.
Merci
Attention, coté client (au sens, application appelant le bean, style une web-app) tu ne pourras rien faire.
Si une Application exception (rollback=false) ou une Checked exception est lancée, la transaction n'est pas invalidée (attention aux effets de bord !!!), mais par contre tu pourras faire faire un setRollbackOnly() dans le try/catch du bean appelant (FacadeBean dans le cas présent).
A l'opposé, lorsqu'une Unchecked exception remonte, la transaction est invalidée automatiquement et, de plus, l'exception sera encapsulée (ce qui empêche de faire un try/catch spécifique à une RuntimeException).