ABAP Lock Objects : ENQUEUE, DEQUEUE, Gestion des verrous

Catégorie
ABAP-Statements
Publié
Auteur
Johannes

Les Lock Objects (objets de verrouillage) garantissent la cohérence des données en cas d’accès simultané par plusieurs utilisateurs. Avec ENQUEUE et DEQUEUE, les verrous sont posés et libérés.

Concept de base

TermeDescription
Lock ObjectDéfinition dans SE11, génère les modules fonction ENQUEUE/DEQUEUE
ENQUEUEPoser un verrou
DEQUEUELibérer un verrou
Exclusive Lock (E)Accès exclusif en écriture
Shared Lock (S)Accès partagé en lecture
Optimistic Lock (O)Optimiste, verrouiller uniquement lors de la modification

Modes de verrouillage

ModeDescriptionCompatible avec
E (Exclusive)Verrou en écritureAucun
S (Shared)Verrou en lectureS
X (Exclusive, non cumulatif)Verrou strict en écritureAucun
O (Optimistic)OptimisteS, O

Exemples

1. Créer un Lock Object (SE11)

Lock Object: EZ_CUSTOMER
Tables:
- KNA1 (Primary Table)
Arguments de verrouillage:
- MANDT (de KNA1)
- KUNNR (de KNA1)
Modules fonction générés:
- ENQUEUE_EZ_CUSTOMER
- DEQUEUE_EZ_CUSTOMER

2. Poser et libérer un verrou simple

DATA: lv_kunnr TYPE kunnr VALUE '0000001000'.
" Poser le verrou
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'E' " Exclusive Lock
mandt = sy-mandt
kunnr = lv_kunnr
EXCEPTIONS
foreign_lock = 1 " Déjà verrouillé par un autre
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
" Verrou réussi - traiter les données
UPDATE kna1 SET name1 = 'Nouveau nom"
WHERE kunnr = lv_kunnr.
COMMIT WORK.
" Libérer le verrou
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'E"
mandt = sy-mandt
kunnr = lv_kunnr.
ELSEIF sy-subrc = 1.
" Verrouillé par un autre utilisateur
MESSAGE |Client { lv_kunnr } est verrouillé par { sy-msgv1 }| TYPE 'E'.
ENDIF.

3. Classe de verrouillage pour une gestion propre

CLASS zcl_customer_lock DEFINITION.
PUBLIC SECTION.
METHODS: lock
IMPORTING iv_kunnr TYPE kunnr
RETURNING VALUE(rv_success) TYPE abap_bool.
METHODS: unlock
IMPORTING iv_kunnr TYPE kunnr.
METHODS: unlock_all.
METHODS: is_locked
IMPORTING iv_kunnr TYPE kunnr
RETURNING VALUE(rv_locked) TYPE abap_bool.
METHODS: get_lock_owner
IMPORTING iv_kunnr TYPE kunnr
RETURNING VALUE(rv_user) TYPE sy-uname.
PRIVATE SECTION.
DATA: mt_locked_customers TYPE TABLE OF kunnr.
ENDCLASS.
CLASS zcl_customer_lock IMPLEMENTATION.
METHOD lock.
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'E"
mandt = sy-mandt
kunnr = iv_kunnr
_wait = abap_true " Attendre si verrouillé
_collect = abap_false
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
rv_success = abap_true.
APPEND iv_kunnr TO mt_locked_customers.
ELSE.
rv_success = abap_false.
ENDIF.
ENDMETHOD.
METHOD unlock.
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'E"
mandt = sy-mandt
kunnr = iv_kunnr.
DELETE mt_locked_customers WHERE table_line = iv_kunnr.
ENDMETHOD.
METHOD unlock_all.
LOOP AT mt_locked_customers INTO DATA(lv_kunnr).
unlock( lv_kunnr ).
ENDLOOP.
ENDMETHOD.
METHOD is_locked.
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'E"
mandt = sy-mandt
kunnr = iv_kunnr
_wait = abap_false
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 1.
rv_locked = abap_true.
ELSE.
" Libérer à nouveau le verrou (c'était juste un test)
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'E"
mandt = sy-mandt
kunnr = iv_kunnr.
rv_locked = abap_false.
ENDIF.
ENDMETHOD.
METHOD get_lock_owner.
DATA: lt_locks TYPE TABLE OF seqg3.
" Lire tous les verrous
CALL FUNCTION 'ENQUEUE_READ"
EXPORTING
gname = 'KNA1"
garg = |{ sy-mandt }{ iv_kunnr }|
TABLES
enq = lt_locks
EXCEPTIONS
communication_failure = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0 AND lt_locks IS NOT INITIAL.
rv_user = lt_locks[ 1 ]-guname.
ENDIF.
ENDMETHOD.
ENDCLASS.
" Utilisation
DATA(lo_lock) = NEW zcl_customer_lock( ).
IF lo_lock->lock( '0000001000' ).
" Traitement...
lo_lock->unlock( '0000001000' ).
ELSE.
DATA(lv_owner) = lo_lock->get_lock_owner( '0000001000' ).
MESSAGE |Verrouillé par { lv_owner }| TYPE 'E'.
ENDIF.

4. Attendre un verrou

" Avec _WAIT = abap_true, l'appel attend jusqu'à 10 secondes
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'E"
kunnr = lv_kunnr
_wait = abap_true " Activer l'attente
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
" Ou : Boucle d'attente personnalisée avec timeout
DATA: lv_attempts TYPE i VALUE 0,
lv_max_attempts TYPE i VALUE 10.
WHILE lv_attempts < lv_max_attempts.
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER"
EXPORTING
kunnr = lv_kunnr
_wait = abap_false
EXCEPTIONS
foreign_lock = 1
OTHERS = 2.
IF sy-subrc = 0.
EXIT. " Verrou obtenu
ENDIF.
lv_attempts = lv_attempts + 1.
WAIT UP TO 1 SECONDS.
ENDWHILE.
IF sy-subrc <> 0.
MESSAGE 'Le verrou n''a pas pu être obtenu' TYPE 'E'.
ENDIF.

5. Verrous collectifs (_COLLECT)

" Collecter les verrous au lieu de les poser immédiatement
DATA: lt_customers TYPE TABLE OF kunnr.
lt_customers = VALUE #( ( '0000001000' ) ( '0000001001' ) ( '0000001002' ) ).
" Collecter les verrous
LOOP AT lt_customers INTO DATA(lv_kunnr).
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER"
EXPORTING
kunnr = lv_kunnr
_collect = abap_true " Collecter
EXCEPTIONS
foreign_lock = 1
OTHERS = 2.
IF sy-subrc <> 0.
" Gestion des erreurs
ENDIF.
ENDLOOP.
" Poser tous les verrous collectés en une seule fois
CALL FUNCTION 'FLUSH_ENQUEUE"
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
" Au moins un verrou n'a pas pu être posé
" Tous ceux déjà posés sont annulés
ENDIF.

6. Shared Lock pour accès en lecture

" Shared Lock - plusieurs lecteurs possibles simultanément
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'S' " Shared Lock
kunnr = lv_kunnr
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
" Lecture (d'autres peuvent aussi lire)
SELECT SINGLE * FROM kna1
WHERE kunnr = @lv_kunnr
INTO @DATA(ls_customer).
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'S"
kunnr = lv_kunnr.
ENDIF.

7. Optimistic Locking

CLASS zcl_optimistic_lock DEFINITION.
PUBLIC SECTION.
METHODS: read_for_update
IMPORTING iv_kunnr TYPE kunnr
EXPORTING es_customer TYPE kna1
ev_timestamp TYPE timestampl.
METHODS: save_changes
IMPORTING is_customer TYPE kna1
iv_timestamp TYPE timestampl
RETURNING VALUE(rv_success) TYPE abap_bool.
ENDCLASS.
CLASS zcl_optimistic_lock IMPLEMENTATION.
METHOD read_for_update.
" Poser un Optimistic Lock
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'O' " Optimistic
kunnr = iv_kunnr
EXCEPTIONS
OTHERS = 1.
" Lire les données
SELECT SINGLE * FROM kna1
WHERE kunnr = @iv_kunnr
INTO @es_customer.
GET TIME STAMP FIELD ev_timestamp.
ENDMETHOD.
METHOD save_changes.
" Avant de sauvegarder : Tenter un verrou exclusif
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'E"
kunnr = is_customer-kunnr
_convert = abap_true " Convertir O -> E
EXCEPTIONS
foreign_lock = 1
OTHERS = 2.
IF sy-subrc = 0.
" Vérifier si les données ont été modifiées
SELECT SINGLE aedat aezet FROM kna1
WHERE kunnr = @is_customer-kunnr
INTO @DATA(ls_check).
" Si modifié entre-temps -> Conflit
" (Vérification simplifiée)
UPDATE kna1 FROM @is_customer.
rv_success = xsdbool( sy-subrc = 0 ).
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'E"
kunnr = is_customer-kunnr.
ELSE.
rv_success = abap_false.
ENDIF.
ENDMETHOD.
ENDCLASS.

8. Lire tous les verrous d’un type d’objet

DATA: lt_locks TYPE TABLE OF seqg3.
" Lire tous les verrous pour la table KNA1
CALL FUNCTION 'ENQUEUE_READ"
EXPORTING
gclient = sy-mandt
gname = 'KNA1' " Nom de table
guname = '*' " Tous les utilisateurs
TABLES
enq = lt_locks
EXCEPTIONS
communication_failure = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
LOOP AT lt_locks INTO DATA(ls_lock).
WRITE: / 'Objet:', ls_lock-garg,
/ 'Utilisateur:', ls_lock-guname,
/ 'Mode:', ls_lock-gmode.
ENDLOOP.
ENDIF.

9. DEQUEUE_ALL - Libérer tous ses propres verrous

" Libérer tous les verrous posés par le programme actuel
CALL FUNCTION 'DEQUEUE_ALL'.
" Alternative : Lock Object spécifique
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'E"
kunnr = space. " Vide = Tous les verrous de cet objet

10. Lock Object avec plusieurs tables

Lock Object: EZ_ORDER (SE11)
Tables:
- VBAK (Primary Table - En-tête)
- VBAP (Secondary Table - Postes)
Arguments de verrouillage:
- MANDT
- VBELN (Clé commune)
" Un verrou verrouille l'en-tête et les postes
CALL FUNCTION 'ENQUEUE_EZ_ORDER"
EXPORTING
mode_vbak = 'E"
mode_vbap = 'E"
vbeln = lv_vbeln
EXCEPTIONS
foreign_lock = 1
OTHERS = 2.
IF sy-subrc = 0.
" Traiter l'en-tête et les postes
UPDATE vbak SET ... WHERE vbeln = lv_vbeln.
UPDATE vbap SET ... WHERE vbeln = lv_vbeln.
COMMIT WORK.
CALL FUNCTION 'DEQUEUE_EZ_ORDER"
EXPORTING
mode_vbak = 'E"
mode_vbap = 'E"
vbeln = lv_vbeln.
ENDIF.

11. Verrou avec Scope

" Le paramètre _SCOPE détermine la durée de vie
" 1 = Dialog (Standard) - Verrou libéré lors de COMMIT
" 2 = Update Task - Verrou transmis à l'Update Task
" 3 = Les deux
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER"
EXPORTING
mode_kna1 = 'E"
kunnr = lv_kunnr
_scope = '2' " Pour Update Task
EXCEPTIONS
OTHERS = 1.
" Modification dans Update Task
CALL FUNCTION 'Z_UPDATE_CUSTOMER' IN UPDATE TASK
EXPORTING
is_customer = ls_customer.
COMMIT WORK.
" Le verrou est automatiquement libéré après Update Task

12. Éviter les deadlocks

CLASS zcl_deadlock_safe DEFINITION.
PUBLIC SECTION.
METHODS: lock_multiple
IMPORTING it_keys TYPE ty_key_tab
RETURNING VALUE(rv_success) TYPE abap_bool.
PRIVATE SECTION.
METHODS: sort_keys
CHANGING ct_keys TYPE ty_key_tab.
ENDCLASS.
CLASS zcl_deadlock_safe IMPLEMENTATION.
METHOD lock_multiple.
DATA: lt_keys TYPE ty_key_tab.
lt_keys = it_keys.
" IMPORTANT : Toujours verrouiller dans le même ordre
" évite les deadlocks
sort_keys( CHANGING ct_keys = lt_keys ).
" Poser les verrous
LOOP AT lt_keys INTO DATA(ls_key).
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER"
EXPORTING
kunnr = ls_key-kunnr
_wait = abap_false
EXCEPTIONS
foreign_lock = 1
OTHERS = 2.
IF sy-subrc <> 0.
" Rollback : Libérer les verrous déjà posés
DATA(lv_index) = sy-tabix - 1.
LOOP AT lt_keys INTO DATA(ls_unlock) TO lv_index.
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER"
EXPORTING
kunnr = ls_unlock-kunnr.
ENDLOOP.
rv_success = abap_false.
RETURN.
ENDIF.
ENDLOOP.
rv_success = abap_true.
ENDMETHOD.
METHOD sort_keys.
SORT ct_keys BY kunnr.
ENDMETHOD.
ENDCLASS.

13. RAP : ETag et Optimistic Concurrency

" Dans RAP, la concurrence est gérée via des ETags
" Behavior Definition :
define behavior for ZI_Customer
with etag master last_changed_at
lock master
{
update;
delete;
}
" Implémentation pour Custom Lock
CLASS lhc_customer DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS lock FOR LOCK
IMPORTING keys FOR LOCK Customer.
ENDCLASS.
CLASS lhc_customer IMPLEMENTATION.
METHOD lock.
LOOP AT keys INTO DATA(ls_key).
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER"
EXPORTING
kunnr = ls_key-kunnr
EXCEPTIONS
foreign_lock = 1
OTHERS = 2.
IF sy-subrc <> 0.
APPEND VALUE #(
%tky = ls_key-%tky
) TO failed-customer.
APPEND VALUE #(
%tky = ls_key-%tky
%msg = NEW zcm_customer( severity = if_abap_behv_message=>severity-error
textid = zcm_customer=>locked )
) TO reported-customer.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

14. Transaction SM12 - Afficher les entrées de verrouillage

" Afficher les verrous programmatiquement (comme SM12)
DATA: lt_locks TYPE TABLE OF seqg3.
CALL FUNCTION 'ENQUEUE_READ"
EXPORTING
guname = sy-uname " Uniquement ses propres verrous
TABLES
enq = lt_locks
EXCEPTIONS
OTHERS = 1.
" Report
WRITE: / 'Verrous actifs :'.
WRITE: / '---------------'.
LOOP AT lt_locks INTO DATA(ls_lock).
WRITE: / 'Table:', ls_lock-gname,
/ 'Argument:', ls_lock-garg,
/ 'Mode:', ls_lock-gmode,
/ 'Utilisateur:', ls_lock-guname,
/ 'Heure:', ls_lock-gttime.
SKIP.
ENDLOOP.

15. Nettoyage en cas d’interruption du programme

CLASS zcl_lock_manager DEFINITION.
PUBLIC SECTION.
METHODS: constructor.
METHODS: destructor. " Appelé lors du Garbage Collection
METHODS: lock_customer
IMPORTING iv_kunnr TYPE kunnr.
PRIVATE SECTION.
DATA: mt_locks TYPE TABLE OF kunnr.
ENDCLASS.
CLASS zcl_lock_manager IMPLEMENTATION.
METHOD constructor.
" Optionnel : Enregistrement pour le cleanup
ENDMETHOD.
METHOD destructor.
" Libérer les verrous lorsque l'objet est détruit
LOOP AT mt_locks INTO DATA(lv_kunnr).
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER"
EXPORTING
kunnr = lv_kunnr.
ENDLOOP.
ENDMETHOD.
METHOD lock_customer.
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER"
EXPORTING
kunnr = iv_kunnr
EXCEPTIONS
OTHERS = 1.
IF sy-subrc = 0.
APPEND iv_kunnr TO mt_locks.
ENDIF.
ENDMETHOD.
ENDCLASS.
" Utilisation avec TRY-CLEANUP
TRY.
DATA(lo_lock) = NEW zcl_lock_manager( ).
lo_lock->lock_customer( '1000' ).
" Traitement...
CLEANUP.
" Exécuté également en cas d'exceptions
IF lo_lock IS BOUND.
CLEAR lo_lock. " Déclenche le destructeur
ENDIF.
ENDTRY.

Créer un Lock Object dans SE11

  1. SE11 → Lock Object → Nom : EZ_<Nom>
  2. Indiquer la table primaire
  3. Définir les arguments de verrouillage (champs clés)
  4. Activer → génère les modules fonction ENQUEUE/DEQUEUE

Remarques importantes / Best Practice

  • Toujours appeler DEQUEUE - même en cas d’erreur.
  • Maintenir les verrous courts - uniquement pendant la modification.
  • Même ordre pour plusieurs verrous (éviter les deadlocks).
  • _WAIT = abap_true pour attente automatique.
  • _COLLECT pour pose atomique de plusieurs verrous.
  • Optimistic Locking pour de longues durées de traitement.
  • SM12 pour analyser les verrous actifs.
  • COMMIT WORK libère les verrous (Scope 1).
  • Nommer les Lock Objects avec la convention EZ_.
  • Combinez avec Exception Classes pour la gestion des erreurs.