Créateur de solutions Intranet et Internet pour les Associations et les Fédérations
Développeurs
Les transactions PDF Imprimer Envoyer

Comment protéger des ensembles d'opérations par des transactions


Introduction
1. Les transactions avec InterBaseExpress (IBX)
Les transactions à plusieurs niveaux
2. Les transactions avec dbExpress
3. Les transactions avec ADO (dbGo)


Introduction


Vous avez certainement entendu parler des transactions : il y a les immobilières, les mobilières et ... les autres. La plupart des SGBD (systèmes de gestion de base de données) dignes de ce nom mettent en œuvre des transactions. Du sens général du mot, on retiendra qu'il y a un échange entre (au moins) deux acteurs qui se conclue par un accord. L'échange a pour but d'établir l'accord. Si l'accord est conclu, l'objet de l'échange est validé (la base est mise à jour), sinon, chacun revient aux conditions initiales (les données sont rétablies à leurs valeurs initiales).

Le mécanisme de transaction est d'abord employé par la base elle-même, en interne, pour maintenir sa cohérence : par exemple lorsque vous avez demandé qu'un index soit maintenu sur une colonne d'une table, à chaque mise à jour, un SGBD correctement construit :

 

  1. Met à jour la donnée
  2. Met à jour l'index.
Si un problème se pose lors de la mise à jour de l'index, la mise à jour de la donnée est annulée : ou bien toutes les opérations sont effectuées, ou bien aucune.

Ce mécanisme est intéressant bien entendu pour des opérations effectuées au niveau de l'application. Par exemple, dans un schéma volontairement simplifié, on ajoutera une ligne à la facture si l'article est retiré du stock. Supposons que l'article manque en stock, la ligne doit être retirée.

Voyons ceci en détail :

 

  1. Réservation du stock
  2. Inscription de la ligne de la facture
  3. Acceptation par le client de la ligne au vu de la quantité et du prix
  4. Sortie effective du stock
En mono-poste, pas de problèmes particuliers. En revanche, si l'on travaille en réseau, un autre opérateur peut être en train de vendre le même article. Ce qui peut donner la succession suivante
 

1° client Etape 2° client
Réservation du stock pour le 1° client 1  
  2 Réservation du stock pour le 2° client
Inscription de la ligne de la facture 3 Inscription de la ligne de la facture
Acceptation par le client de la ligne au vu de la quantité et du prix 4 Acceptation par le client de la ligne au vu de la quantité et du prix
Sortie effective du stock : le stock est décrémenté 5  
  6 Tentative de sortie effective du stock mais le stock s'avère insuffisant
 
Le stock a été définitivement affecté au 1° client à l'étape 5, et, à l'étape 6, le 2° client ne trouve plus ce qui était pourtant disponible à l'étape 2.

Bien sûr on aurait pu procéder à l'affectation dès l'étape 1. Mais, si le 1° client, au vu du prix, refuse la commande, la vente est alors perdue pour le client 2 puisque le stock affecté dès l'étape 1 ne lui est pas disponible à l'étape 2.

Pour éviter cela, on regroupe les séquences d'opération ainsi :

1° client Etape 2° client
Début de transaction 1 Début de transaction
Réservation du stock pour le 1° client 2  
Inscription de la ligne de la facture 3  
Acceptation par le client de la ligne au vu de la quantité et du prix 4  
Sortie effective du stock : le stock est décrémenté 5  
Fin de transaction 6 Fin de transaction
Début de transaction 7 Début de transaction
  8 Réservation du stock pour le 2° client
  9 Inscription de la ligne de la facture
  10 Acceptation par le client de la ligne au vu de la quantité et du prix
  11 Sortie effective du stock : le stock est décrémenté
Fin de transaction 12 Fin de transaction
Chacun des deux groupes sera isolé au sein de ce que l'on appellera une transaction.
La transaction est visible pour tous les clients, c'est ce qu'indiquent les marques de début et de fin de transaction dans chaque colonne.

Nous remarquons que c'est au niveau de l'application que doivent être délimitées les transactions : c'est le développeur qui définit les opérations qui constituent une transaction.

Dans la pratique, on évitera de mettre dans la transaction des actions d'attente de la décision des clients.
De plus, les SGBD utilisent des méthodes plus sophistiquées que la sérialisation des transactions telle qu'elle est induite du tableau : attendre d'avoir fini le 1° client pour traiter le 2° client introduit des goulots d'étranglement très pénalisant. Mais pour la compréhension logique de ce qui se passe, la présentation ci-dessus est suffisamment simple et complète.

Dans le langage SQL, on dispose d'un ordre pour démarrer et de deux ordres pour terminer une transaction.
Pour le début de transaction, on utilise SET TRANSACTION, tandis que pour la fin de transaction, on utilise COMMIT (ou ROLLBACK) selon que l'on valide (ou invalide) les modifications de la base effectuées au cours de la transaction.
Ces ordres seront mis en œuvre directement par le programmeur ou par l'intermédiaire de composants qui le feront pour lui.

Nous allons en voir l'application dans 3 cas :


1. Les transactions avec InterBaseExpress (IBX)


Avant de commencer à parler de nos transactions, il faut souligner que toutes les opérations effectuées avec les IBX sont d'ores et déjà inscrite dans une transaction fondamentale. Cette transaction fondamentale a pour effet de faire apparaître la base de données à chacun, comme s'il en était le seul utilisateur.
Dans ces conditions, on voit mal comment on pourrait mettre en œuvre des transactions fines, puisque tout ce que fait un utilisateur en lecture et en écriture est isolé dans la transaction fondamentale.
Eh bien cela est possible si l'on paramètre correctement la transaction fondamentale en donnant à la propriété Params d'IBTransaction1 les valeurs

IBTransaction1  
Params ... read_committed
rec_version
nowait
 
 read_committed permet à la transaction de voir les valeurs validées par les autres transactions (notamment d'autres utilisateurs) et même de mettre à jour ces valeurs à son tour.

TIBTransaction propose des méthodes qui exécutent les ordres SQL vus ci-dessus. Ce sont

StartTransaction 
Commit
Rollback
Cependant l'inconvénient de Commit et de Rollback c'est qu'ils obligent à réactiver la transaction. Cela est coûteux, pour une simple mise à jour.
Heureusement, nous avons à disposition une variation de ces commandes qui maintient la transaction ouverte. Ce sont

CommitRetaining
RollbackRetaining
Voyons maintenant comment programmer notre transaction

Plaçons sur un Data Module un composant TIBDatabase et TIBTransaction

Nous relions les deux composants en indiquant les propriétés

IBDatabase1.DefaultTransaction = IBTransaction1 IBTransaction1.DefaultDatabase = IBDatabase1
et mettons IBTransaction1.Params comme indiqué plus haut à read_committed, rec_version et nowait

Note : Nous ne détaillons ici que ce qui est en rapport avec les transactions.
.

Voyons la séquence de validation de la ligne de commande (qui peut se trouver dans le gestionnaire d'événement d'un bouton de la Form).

procedure ReservationDeLigne;
begin
    try

        IBTransaction1.CommitRetaining; //Début de la transaction
        if StockSuffisant then
            InsererLaLigne
        else
            Abort;
        if AcceptationDeLaLigne then
            SortieEffectiveDuStock
        else
            Abort;
        IBTransaction1.CommitRetaining; //Fin normale de la transaction
    except
        IBTransaction1.RollbackRetaining; //Fin anormale de la transaction
    end
end;
Cette méthode est en fait une succession de validations de la transaction fondamentale. Le premier CommitRetaining a pour but de marquer le point de départ auquel on reviendrait en cas de RollbackRetaining.


Les transactions à plusieurs niveaux


Lorsque l'on écrit des transactions, celles-ci peuvent se trouver incluses ultérieurement dans un transaction plus globale.
Par exemple, nous avons une transaction pour une ligne de la facture.
Mais il est tout à fait possible d'avoir également une transaction globale au niveau de la facture au sein de laquelle se produisent les transactions au niveau de chaque ligne.
Nous allons mettre en place une méthode qui prend cela en compte d'une manière très intéressante.
En écrivant différemment la procédure ci-dessus, nous n'aurons même pas besoin de la modifier le jour où elle se trouve prise dans une transaction globale (et ceci, quel que soit le nombre de transaction globale que nous serions amenés à emboîter par la suite).

Tout d'abord nous ajoutons la propriété NiveauDeTransaction en lecture seule dans le DataModule :

private
FNiveauDeTransaction: Integer;
public
property NiveauDeTransaction : Integer read FNiveauDeTransaction;
Ceci nous permettra de gérer en interne du DataModule le niveau de transaction et de savoir à l'extérieur du DataModule si nous sommes ou non dans une transaction.

Maintenant, pour gérer nos transactions, en tout point de l'application, nous allons créer 3 procédures publiques dans le DataModule.

public procedure TransactionStart; procedure TransactionCommit; procedure TransactionRollback; implementation procedure TransactionStart; begin if FNiveauDeTransaction = 0 then IBTransaction1.CommitRetaining; Inc(FNiveauDeTransaction); end procedure TransactionCommit; begin if FNiveauDeTransaction > 0 then Dec(FNiveauDeTransaction); if FNiveauDeTransaction = 0 then IBTransaction1.CommitRetaining; end procedure TransactionRollback; begin if FNiveauDeTransaction > 0 then IBTransaction1.RollbackRetaining; FNiveauDeTransaction := 0; end
Bien entendu, nous nous interdisons désormais de faire appel à IBTransaction1. Pour gérer nos transactions, nous ferons désormais appel exclusivement aux 3 procédures ci-dessus.

Note : Comme vous pouvez le constater, la procédure TransactionStart ne met pas en œuvre IBTransaction1.StartTransaction comme on pourrait s'y attendre. En effet, nous ne créons pas de nouvelles transactions. Mais nous nous appuyons sur la transaction fondamentale (voir plus haut). Chaque Commit intervient comme un point de validation de cette transaction. C'est pourquoi on le trouve dans procedure TransactionStart; et dans procedure TransactionCommit;

Par exemple, la réservation de la ligne de facture s'écrit maintenant :

function ReservationDeLigne : boolean; begin Result := True; try DataModule1.TransactionStart; //Début de la transaction if StockSuffisant then InsererLaLigne else Abort; if AcceptationDeLaLigne then SortieEffectiveDuStock else Abort; DataModule1.TransactionCommit; //Fin normale de la transaction except DataModule1.TransactionRollback; //Fin anormale de la transaction Result := False; end end;
Supposons maintenant que la ligne de facture ne sera définitivement validée que lorsque la facture sera validée dans son ensemble.
Par exemple, si nous achetons un parquet à poser avec de la colle, ou nous prenons l'ensemble, ou rien.
Le client peut également décider d'annuler, au vu du prix.

Donc nos transactions élémentaires se trouvent maintenant incluses dans une transaction globale au niveau de la facturation. (Nous avons dit au début que nous simplifions en ayant une seule opération pour la commande et la facture).

function Facturation : boolean; begin Result := True; try DataModule1.TransactionStart; //Début de la transaction repeat if not ReservationDeLigne then Abort; until DerniereLigne; DataModule1.TransactionCommit; //Fin normale de la transaction except DataModule1.TransactionRollback; //Fin anormale de la transaction Result := False; end end;
Nous remarquons que nos transactions au niveau des lignes n'ont pas été modifiées lorsque nous les avons englobées dans la transaction au niveau de la facture.
De même, si nous devons englober un jour la transaction du niveau de la facture dans une transaction d'ensemble, nos procédures ne seront pas à modifier.

Un dernier mot sur l'incidence des commandes Retaining utilisées de manière répétée : elles ne laissent pas la base de données InterBase dans un état optimum. C'est pourquoi, il est souhaitable de faire tourner les utilitaires d'InterBase de manière régulière


2. Les transactions avec dbExpress


(en préparation)


3. Les transactions avec ADO (dbGo)


Avec ADO, nous disposons de la gestion explicite des transactions dans TADOConnection ainsi que nous le voyons dans l'exemple ci-dessous.

procedure ReservationDeLigne; begin try ADOConnection1.BeginTrans; //Début de la transaction if StockSuffisant then InsererLaLigne else Abort; if AcceptationDeLaLigne then SortieEffectiveDuStock else Abort; ADOConnection1.CommitTrans; //Fin normale de la transaction except ADOConnection1.RollbackTrans; //Fin anormale de la transaction end end;
Dans ce tutoriel, nous avons vu comment protéger des ensembles d'opérations par des transactions.
 


Plan
Contact
Société Descartes
151 rue Montmartre
75002 PARIS
tél. : 01 42 36 92 17
© 2014 Site officiel de la Société Descartes - réalisé par descartes.fr et mis à jour le 18/07/2014
Déclaration CNIL n° 1216720