7. Transaction gérée par l'EJB

7.1. Introduction

Rappelons ici qu'une transaction est une unité de travail, en général liée à des changements de données dans une base. Cette unité est vue comme une et indivisible, soit elle est menée à bien avec succès dans son intégralité, soit elle est entièrement annulée. Le support des transactions est un des points fondamentaux des EJBs. Pour le moment, nous nous sommes placés dans le cas où une méthode métier est exécutée dans le contexte d'une transaction. Dans ce cas, cette transaction est entièrement gérée par le serveur d'applications, sans que le développeur d'EJB n'ait à s'en soucier. Les choses sont en fait plus complexes : il existe deux contextes applicatifs. Dans le premier, appelé bean managed transaction , les transactions sont gérées manuellement, dans les méthodes métier des EJB. Dans le second, appelé container managed transaction , c'est toujours le serveur d'application qui gère la transaction, mais l'application a tout de même la main sur quelques aspects de sa gestion. Dans tous les cas, une transaction est toujours associée à une unité d'exécution ( thread ) particulière dans le modèle EJB.

7.2. Déclaration du mode transactionnel

Un EJB qui choisit de gérer lui-même ses transactions doit être annoté par @TransactionManagement, comme dans l'exemple suivant. Cette annotation ne peut être posée que sur une classe, le comportement d'un EJB vis-à-vis de la gestion des transactions est donc global à un EJB, et ne peut se régler méthode par méthode.

Exemple 68. Annotation sur un EJB en mode bean managed transaction

@Stateless(mappedName="MarinService")
 @Remote(MarinService.class)
 @TransactionManagement(TransactionManagementType.BEAN)
 public  class MarinServiceImpl  implements MarinService {

     // reste de la classe
}

7.3. Gestion de la transaction

Dans le contexte application managed transactions les transactions sont gérées manuellement, comme si l'on était dans une application JSE. La seule différence est que, pour obtenir une transaction, l'on s'adresse tout de même au serveur d'applications. Une transaction est dans ce cas modélisée par l'interface UserTransaction. On peut récupérer cette transaction de deux manières : soit par injection de dépendances, soit en demandant cette transaction à l'annuaire du serveur. L'injection de dépendance est la manière la plus simple, que l'on montre sur l'exemple suivant. Enfin, rappelons que l'injection d'une transaction ne peut se faire que dans un EJB qui gère lui-même ses transactions, donc annoté avec @TransactionManagement(TransactionManagementType.BEAN). Dans le cas où les transactions sont gérées par le conteneur d'EJB, cette injection est illégale, et peut mener à une erreur de démarrage de l'application.

Exemple 69. Injection d'une transaction dans un EJB

@Stateless(mappedName="MarinService")
 @Remote(MarinService.class)
 @TransactionManagement(TransactionManagementType.BEAN)
 public  class MarinServiceImpl  implements MarinService {

     @Resource
     private UserTransaction transaction ;
    
     // reste de la classe
}

L'interface UserTransaction propose les six méthodes suivantes.
  • begin() : marque le début d'une transaction.
  • commit() : marque la fin d'une transaction, et demande sa validation. Cette validation peut échouer en jetant une exception.
  • rollback() : marque la fin d'une transaction en demandant l'annulation de toutes les opérations effectuées dans le contexte de cette transaction. Cette annulation peut également échouer en jetant une exception.
  • setRollbackOnly() : impose que la transaction soit annulée en fin d'exécution. Si un appel à commit() est fait, alors une exception sera générée et la transaction sera annulée.
  • getStatus() : retourne le statut de la transaction. Ce statut est un entier dont les valeurs sont fixées par l'interface javax.transaction.Status. Il y a dix statuts possibles pour une transaction :
    • STATUS_ACTIVE : indique que la transaction est en cours, et qu'elle n'a pas été marquée pour être annulée ;
    • STATUS_COMMITTED : la transaction a été validée ;
    • STATUS_COMMITTING : le processus de validation est en cours, donc un appel à la méthode commit() a été fait, mais il n'est pas terminé ;
    • STATUS_MARKED_ROLLBACK : la transaction a été marquée pour être annulée, en principe par un appel à la méthode setRollbackOnly() ;
    • STATUS_NO_TRANSACTION : la transaction n'existe plus ;
    • STATUS_PREPARED : la transaction est prête à être validée, ce qui indique que tous les éléments qui participent à cette transaction (par exemple les différentes bases de données) sont prêts pour la validation finale ;
    • STATUS_PREPARING : la transaction est en train de consulter les éléments qui participent, qui n'ont pas encore répondu s'ils étaient prêts ou non pour la validation finale ;
    • STATUS_ROLLEDBACK : la transaction a été annulée ;
    • STATUS_ROLLING_BACK : la transaction est en cours d'annulation ;
    • STATUS_UNKNOWN : l'état courant de la transaction n'est pas connu, ce qui indique qu'elle en train de passer d'un état à l'autre.
    Il peut être utile, pour un EJB avec état, d'interroger la transaction pour connaître l'état dans lequel elle se trouve. Effectivement, un tel EJB peut démarrer une transaction lors d'un appel de méthode, et la fermer lors d'un autre, avec encore d'autres appels de méthodes entre ce premier et ce dernier.
  • setTransactionTimeOut(int) : permet de fixer un temps au bout duquel une transaction doit être annulée.

7.4. Fonctionnement de la transaction

Dans un scénario JDBC classique, les choses se déroulent de la façon suivante :
  • une application cliente ouvre une connexion sur une base de données ;
  • la transaction est gérée au travers de l'objet Connection, par ses méthodes commit(), rollback() et éventuellement setAutoCommit(boolean).
Un EJB n'ouvre pas une connexion sur une base de données de cette manière. Il le fait par injection de dépendances :
  • d'une source de données s'il a besoin d'émettre des ordres SQL directement sur la base, ou s'il n'utilise pas JPA ;
  • d'une unité de persistance s'il utilise JPA, qui elle-même référence une source de données par son nom.
Un EJB peut bien sûr utiliser autant de connexions sur autant de bases de données qu'il veut, soit directement soit au travers de JPA. Ensuite, il obtient une référence sur une transaction également par injection de dépendances, comme nous l'avons vu. Résumons tout ceci sur l'exemple suivant.

Exemple 70. Injection de sources de données et entity manager

@Stateless(mappedName="MarinService")
 @Remote(MarinService.class)
 @TransactionManagement(TransactionManagementType.BEAN)
 public  class MarinServiceImpl  implements MarinService {

     @Resource
     private UserTransaction transaction ;
    
     @Resource(mappedName="jdbc/CoursEJBDS")
     private DataSource dataSource ;

     @PersistenceContext(unitName="cours-ear-pu")
     private EntityManager em ;
    
     // reste de la classe
}

Cet objet de types DataSource permet de récupérer une connexion JDBC à une base de données en appelant sa méthode getConnection(). Se pose alors la question du lien entre la transaction obtenue, et les différentes opérations menées à bien au travers des différentes connexions JDBC, utilisées directement ou non. La réponse est fournie par la spécification EJB. Toute opération sur une base de données effectuée dans une méthode métier d'un EJB, après un appel à transaction.begin() est faite dans le contexte de cette transaction. Tous les principes de fonctionnement des transactions s'appliquent, en particulier l'atomicité et la cohérence. Le cas où l'on s'adresse à plusieurs bases de données différentes est ici nouveau, et pose le problème de la synchronisation des opérations. Cette synchronisation est résolue en procédant en deux temps, on appelle cette procédure le two phases commit .
  • Lors d'une première étape le serveur d'application interroge les différentes bases de données, qui indiquent qu'elles sont prêtes à valider les éléments qui les concernent ou pas.
  • Une fois que tous les éléments ont répondu par l'affirmative, alors la validation finale est faite, et toutes les bases valident leurs travail. Bien sûr, si l'une des bases n'était pas prête à faire cette validation, c'est l'ensemble du processus qui est annulé, respectant en cela le principe d'atomicité.

7.5. Cas des EJB avec état

Dans ce cas, une transaction peut être ouverte lors d'un premier appel de méthode métier, puis maintenue ouverte à la fin de cet appel. Un deuxième appel d'une autre méthode peut alors avoir lieu, dans la même transaction, toujours ouverte. Enfin un troisième appel peut valider (ou non) la transaction. Cette validation, ou cette invalidation, portera alors sur les opérations menées lors des trois appels successifs de méthode. Dans ce contexte, l'appel à la méthode getStatus() de l'interface UserTransaction renseigne sur l'état courant de cette transaction.
JPA & EJB
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
Introduction
1. Objet du mapping objet / relationnel
2. Un peu d'histoire
Un premier exemple
1. Introduction
2. Création de l'environnement technique
2.1. Introduction
2.2. Création de la base Derby
2.3. Création du projet NetBeans et d'une première entité
2.4. Structure d'un projet persistant
2.5. Une première classe persistante
2.6. Un premier fichier persistence.xml
3. Utilisation de ce premier exemple
3.1. Écriture du code d'utilisation
3.2. Exécution de notre premier exemple
3.3. Modification de la class Marin
3.4. Opérations CRUD
Mettre un jeu de classes en base
1. Introduction
2. Définition d'une entité JPA
2.1. Écriture de l'entité
2.2. Annotation de l'entité
2.3. Annotations des champs
2.4. Exemple d'utilisation
3. Opérations sur les entités
3.1. Introduction
3.2. Opération PERSIST
3.3. Opération REMOVE
3.4. Opération REFRESH
3.5. Opération DETACH
3.6. Opération MERGE
4. Mise en relation d'entités
4.1. Introduction
4.2. Relations unidirectionnelles et bidirectionnelles
4.3. Relation 1:1
4.4. Relation 1:p
4.5. Relation p:1
4.6. Relation n:p
4.7. Comportement cascade
4.8. Effacement des entités orphelines
5. Charger des entités et leurs relations
6. Objets inclus
6.1. Introduction
6.2. Déclaration d'un objet inclus
6.3. Utilisation d'objets inclus
6.4. Cas où l'objet inclus est nul
6.5. Renommer les colonnes incluses
6.6. Collections d'objets inclus
L'API Collection en base
1. Introduction
2. Enregistrer une collection d'entités
2.1. Enregistrement d'une collection simple
2.2. Enregistrement d'un Set
2.3. Enregistrement d'une List
3. Enregistrer une collection de types de base
4. Enregistrement d'une Map
4.1. Table de hachage de type (type de base, entité)
4.2. Cas où la clé est un champ de la valeur
4.3. Cas d'une table (entité, entité)
Héritage
1. Introduction
2. Enregistrement d'une hiérarchie de classes
2.1. Entité et super-classe non enregistrée
2.2. Position du problème
2.3. Trois façons de faire
3. Stratégie SINGLE_TABLE
3.1. Fonctionnement
3.2. Mise en place
3.3. Limitations
4. Stratégie JOINED
4.1. Fonctionnement
4.2. Mise en place
4.3. Limitations
5. Stratégie TABLE_PER_CLASS
5.1. Fonctionnement
5.2. Mise en place
5.3. Limitations
Requêtes
1. Introduction
2. Un premier exemple
2.1. Écriture d'une première requête
2.2. Exécution d'une première requête
2.3. Exécution d'une première requête d'agrégation
3. Définition de requêtes
3.1. Requêtes dynamiques
3.2. Requêtes paramétrées
3.3. Requêtes nommées
3.4. Requêtes natives
4. Exécution, analyse du résultat
4.1. Exécution d'une requête dynamique
4.2. Exécution d'une requête nommée
4.3. Analyse du résultat
4.4. Cas des résultats de grande taille
4.5. Remarques
5. Clause From
5.1. Définition des entités
5.2. Jointures dans la clause From
5.3. Remarque finale sur les jointures en JPQL
6. Clause Where
6.1. Variables et chemins dans une clause where
6.2. Expressions conditionnelles et opérateurs
6.3. Requêtes imbriquées
6.4. Opérateurs any, all et some
6.5. Expressions fonctionnelles
7. Clauses Group By et Having
8. Opérations Update et Delete
EJB
1. Introduction
2. Un premier exemple
2.1. Introduction
2.2. Installation dans Glassfish à l'aide de Netbeans
2.3. Création d'un premier EJB
2.4. Déploiement de notre premier EJB
2.5. Création d'un client
3. Mise en oeuvre du pattern session facade
3.1. Introduction
3.2. Modèle objet
3.3. Définition de l'unité de persistance
3.4. Assemblage de notre application
3.5. Assemblage et déploiement
3.6. Utilisation du client
4. Opération de persistance en façade
4.1. Introduction
4.2. Enrichissement du service
4.3. Création de la méthode findMarinById(long)
4.4. Ajout de la méthode findAllMarins()
4.5. Utilisation dans un code client
5. Types d'EJB
5.1. Introduction
5.2. Qu'est-ce qu'un EJB ?
5.3. Écriture d'un EJB session
5.4. Qu'est-ce qu'une méthode métier ?
5.5. EJB avec ou sans état
5.6. Gestion des transactions
5.7. Restrictions
6. Cycle de vie d'un EJB
6.1. Cas des EJB sans état
6.2. Cas des EJB avec état
6.3. Injection de dépendances
7. Transaction gérée par l'EJB
7.1. Introduction
7.2. Déclaration du mode transactionnel
7.3. Gestion de la transaction
7.4. Fonctionnement de la transaction
7.5. Cas des EJB avec état
8. Transaction gérée par le serveur
8.1. Introduction
8.2. Déclaration du mode transactionnel
8.3. Gestion de la transaction
8.4. Fonctionnement de la transaction
8.5. Remarques
9. Intercepteurs
9.1. Introduction
9.2. Aperçu général
9.3. Cycle de vie d'un intercepteur
9.4. Object InvocationContext
9.5. Interception d'un EJB ou d'une méthode métier
9.6. Exemple de mise en œuvre d'un intercepteur