Un petit article sur les transactions en JEE. Il ne s’agit pas de réexpliquer les mécanismes mis en oeuvre, à savoir l’AOP, mais des choses beaucoup plus terre à terre.
Débloque les + belles offres tech en 10 mins
Les transactions dans les applications JEE se définissent sur deux niveaux, à savoir au niveau applicatif et au niveau base de données. Pour rappel les transactions doivent en théorie satisfaire les quatre principes suivants :
- Atomicité : soit la transaction passe entièrement, soit on revient à la situation initiale. Pas d’état intermédiaire.
- Cohérence : la transaction ne doit pas casser la cohérence des données en base.
- Isolation : la transaction ne doit pas « voir » ce que font d’autres transactions qui se dérouleraient en même temps qu’elle.
- Durabilité : une fois la transaction réussie, les données doivent être stockées de manière permanente en base de données.
On désigne habituellement sous le terme ACID les principes ci-dessus. Les mécanismes mis en jeu pour y parvenir sont du genre compliqués surtout au niveau de la base de données, mais on y reviendra un peu plus tard.
Les niveaux transactionnels en Java/JEE
Dans ce paragraphe nous ne faisons pas la distinctions entre les niveaux transactionnels offerts par Spring et ceux offerts par les EJB tant ils sont proches. D’ailleurs on va prendre la notation standard, à savoir celle des EJB. Dans les applications modernes les transactions sont généralement définies par annotations. La liste ci-dessous indique les différents niveaux transactionnels disponibles.
- Required : vérifie que la méthode ainsi annotée a été lancée dans un contexte transactionnel. Si ce n’est pas le cas, crée un contexte transactionnel avant de l’invoquer. Si une exception est levée, fait un rollback sur la transaction.
- RequiresNew : idem que pour Required, sauf qu’un nouveau contexte transactionnel est créé même si on est déjà dans un contexte transactionnel au moment d’invoquer la méthode.
- Supported : si la méthode n’est pas invoquée dans un contexte transactionnel, n’en crée pas de novueau, sinon, reste dans le contexte transactionnel courant.
- NotSupported : si la méthode est invoquée dans un contexte transactionnel, sort de ce contexte le temps de l’exécuter.
- Mandatory : vérifie que la méthode est lancée dans un contexte transactionnel. Si ce n’est pas le cas, lève une exception.
- Never : vérifie que la méthode n’est pas lancée dans un contexte transactionnel. Si c’est le cas, lève une exception.
Dans la pratique Required
est utilisé pour les opérations en écriture, Supported
pour les opérations en lecture. RequiresNew
et NotSupported
sont rarement utilisés, et on se demande pourquoi Mandatory
et Never
existent…
Notez toutefois que si vous utilisez des contextes transactionnels, il est de bon aloi d’annoter toutes les méthodes publiques de vos classes avec des informations de contexte, du moins pour celles qui peuvent être invoquées dans le cadre d’une transaction.
Au niveau de la base
Pour que les transactions fonctionnent, il convient aussi de configurer la base de données correctement. Cette configuration aura un impact sur l’isolation des transactions, mais aussi sur les performances. Et comme vous pouvez vous en douter, plus l’isolation est élevée, moins bonnes sont les performances. Bref il s’agit comme toujours d’une histoire de compromis…
Voici la liste des configurations disponibles :
- Read uncommitted : aucune isolation. Chaque transaction en cours « voit » ce que fond les autres transactions, y compris ce qui n’a pas encore été committé par ces dernières. Ceci peut être très problématiques si les traitements à effectuer sont fonctions de valeurs non encore committées.
- Read committed : le niveau de base de l’isolation. Chaque transaction en cours voit uniquement ce qui a été committé par les autres transactions. Le problème de ce niveau concerne les lectures non répétables, à savoir qu’à un temps t un champ de la base aura une première valeur, et à t+n il aura pris une autre valeur committée par une autre transaction.
- Repeatable reads : garantit que les lectures de valeurs sont répétables. Cependant ce niveau ne protège pas contre les lectures fantômes, à savoir que lancer deux fois le même
SELECT
au cours du temps dans une même transaction peut renvoyer des résultats différents. - Serializable : le niveau d’isolation maximal, en fait le seul qui satisfasse réellement les principes ACID… et de loin le plus lent.
Dans la pratique le niveau Read committed
suffit dans la plupart des cas.
En bref…
Au niveau des transactions comme dans beaucoup de choses tout est affaire de compromis, suivant les besoins fonctionnels de l’application sur laquelle vous travaillez. Veillez toutefois à ne pas mettre toutes les méthodes en Required
ou RequiresNew
au niveau JEE sous peine de deadlocks ou du moins d’un sérieux impact sur les performances…
Débloque les + belles offres tech en 10 mins
Cet article vous a plu ? Vous aimerez sûrement aussi :
- #AstuceDeCode 16 : les annotations, l’AOP et Spring
- #AstuceDeCode 17 : filtrage et validation efficaces
- #AstuceDeCode 18 : la mise à jour de collections en Hibernate
Julien
Moi c’est Julien, ingénieur en informatique avec quelques années d’expérience. Je suis tombé dans la marmite étant petit, mon père avait acheté un Apple – avant même ma naissance (oui ça date !). Et maintenant je me passionne essentiellement pour tout ce qui est du monde Java et du système, les OS open source en particulier.
Au quotidien, je suis devops, bref je fais du dév, je discute avec les opérationnels, et je fais du conseil auprès des clients.