ABAP Lock Objects: ENQUEUE, DEQUEUE, gestión de bloqueos

Kategorie
ABAP-Statements
Veröffentlicht
Autor
Johannes

Los Lock Objects (objetos de bloqueo) garantizan la consistencia de datos cuando múltiples usuarios acceden simultáneamente. Con ENQUEUE y DEQUEUE se establecen y liberan bloqueos.

Concepto básico

TérminoDescripción
Lock ObjectDefinición en SE11, genera funciones ENQUEUE/DEQUEUE
ENQUEUEEstablecer bloqueo
DEQUEUELiberar bloqueo
Exclusive Lock (E)Acceso de escritura exclusivo
Shared Lock (S)Acceso de lectura compartido
Optimistic Lock (O)Optimista, bloquear solo al modificar

Modos de bloqueo

ModoDescripciónCompatible con
E (Exclusive)Bloqueo de escrituraNinguno
S (Shared)Bloqueo de lecturaS
X (Exclusive, no acumulativo)Bloqueo de escritura estrictoNinguno
O (Optimistic)OptimistaS, O

Ejemplos

1. Crear Lock Object (SE11)

Lock Object: EZ_CUSTOMER
Tablas:
- KNA1 (Primary Table)
Argumentos de bloqueo:
- MANDT (de KNA1)
- KUNNR (de KNA1)
Funciones generadas:
- ENQUEUE_EZ_CUSTOMER
- DEQUEUE_EZ_CUSTOMER

2. Establecer y liberar bloqueo simple

DATA: lv_kunnr TYPE kunnr VALUE '0000001000'.
" Establecer bloqueo
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E' " Exclusive Lock
mandt = sy-mandt
kunnr = lv_kunnr
EXCEPTIONS
foreign_lock = 1 " Ya bloqueado por otro
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
" Bloqueo exitoso - editar datos
UPDATE kna1 SET name1 = 'Nuevo nombre'
WHERE kunnr = lv_kunnr.
COMMIT WORK.
" Liberar bloqueo
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
mandt = sy-mandt
kunnr = lv_kunnr.
ELSEIF sy-subrc = 1.
" Bloqueado por otro usuario
MESSAGE |Cliente { lv_kunnr } está bloqueado por { sy-msgv1 }| TYPE 'E'.
ENDIF.

3. Clase de bloqueo para manejo limpio

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 " Esperar si está bloqueado
_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.
" Liberar bloqueo (era solo prueba)
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.
" Leer todos los bloqueos
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.
" Uso
DATA(lo_lock) = NEW zcl_customer_lock( ).
IF lo_lock->lock( '0000001000' ).
" Edición...
lo_lock->unlock( '0000001000' ).
ELSE.
DATA(lv_owner) = lo_lock->get_lock_owner( '0000001000' ).
MESSAGE |Bloqueado por { lv_owner }| TYPE 'E'.
ENDIF.

4. Esperar por bloqueo

" Con _WAIT = abap_true la llamada espera hasta 10 segundos
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
kunnr = lv_kunnr
_wait = abap_true " Activar espera
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
" O: Bucle de espera propio con 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. " Bloqueo obtenido
ENDIF.
lv_attempts = lv_attempts + 1.
WAIT UP TO 1 SECONDS.
ENDWHILE.
IF sy-subrc <> 0.
MESSAGE 'No se pudo obtener el bloqueo' TYPE 'E'.
ENDIF.

5. Bloqueos colectivos (_COLLECT)

" Recopilar bloqueos en lugar de establecerlos inmediatamente
DATA: lt_customers TYPE TABLE OF kunnr.
lt_customers = VALUE #( ( '0000001000' ) ( '0000001001' ) ( '0000001002' ) ).
" Recopilar bloqueos
LOOP AT lt_customers INTO DATA(lv_kunnr).
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
kunnr = lv_kunnr
_collect = abap_true " Recopilar
EXCEPTIONS
foreign_lock = 1
OTHERS = 2.
IF sy-subrc <> 0.
" Manejo de errores
ENDIF.
ENDLOOP.
" Establecer todos los bloqueos recopilados de una vez
CALL FUNCTION 'FLUSH_ENQUEUE'
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
" Al menos un bloqueo no se pudo establecer
" Todos los ya establecidos se revocan
ENDIF.

6. Shared Lock para acceso de lectura

" Shared Lock - múltiples lectores simultáneos posibles
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.
" Leer (otros también pueden leer)
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. Bloqueo optimista

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.
" Establecer bloqueo optimista
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'O' " Optimistic
kunnr = iv_kunnr
EXCEPTIONS
OTHERS = 1.
" Leer datos
SELECT SINGLE * FROM kna1
WHERE kunnr = @iv_kunnr
INTO @es_customer.
GET TIME STAMP FIELD ev_timestamp.
ENDMETHOD.
METHOD save_changes.
" Antes de guardar: Intentar bloqueo exclusivo
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.
" Verificar si los datos fueron modificados
SELECT SINGLE aedat aezet FROM kna1
WHERE kunnr = @is_customer-kunnr
INTO @DATA(ls_check).
" Si se modificó entretanto -> Conflicto
" (Verificación simplificada)
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. Leer todos los bloqueos de un tipo de objeto

DATA: lt_locks TYPE TABLE OF seqg3.
" Leer todos los bloqueos para tabla KNA1
CALL FUNCTION 'ENQUEUE_READ'
EXPORTING
gclient = sy-mandt
gname = 'KNA1' " Nombre de tabla
guname = '*' " Todos los usuarios
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: / 'Objeto:', ls_lock-garg,
/ 'Usuario:', ls_lock-guname,
/ 'Modo:', ls_lock-gmode.
ENDLOOP.
ENDIF.

9. DEQUEUE_ALL - Liberar todos los bloqueos propios

" Liberar todos los bloqueos establecidos por el programa actual
CALL FUNCTION 'DEQUEUE_ALL'.
" Alternativa: Lock Object específico
CALL FUNCTION 'DEQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
kunnr = space. " Vacío = Todos los bloqueos de este objeto

10. Lock Object con múltiples tablas

Lock Object: EZ_ORDER (SE11)
Tablas:
- VBAK (Primary Table - Cabecera)
- VBAP (Secondary Table - Posiciones)
Argumentos de bloqueo:
- MANDT
- VBELN (Clave común)
" Un bloqueo bloquea cabecera y posiciones
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.
" Editar cabecera y posiciones
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. Bloqueo con Scope

" El parámetro _SCOPE determina la duración
" 1 = Dialog (Estándar) - Bloqueo liberado en COMMIT
" 2 = Update Task - Bloqueo se pasa a Update Task
" 3 = Ambos
CALL FUNCTION 'ENQUEUE_EZ_CUSTOMER'
EXPORTING
mode_kna1 = 'E'
kunnr = lv_kunnr
_scope = '2' " Para Update Task
EXCEPTIONS
OTHERS = 1.
" Modificación en Update Task
CALL FUNCTION 'Z_UPDATE_CUSTOMER' IN UPDATE TASK
EXPORTING
is_customer = ls_customer.
COMMIT WORK.
" El bloqueo se libera automáticamente después de Update Task

12. Evitar 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.
" IMPORTANTE: Siempre bloquear en el mismo orden
" evita deadlocks
sort_keys( CHANGING ct_keys = lt_keys ).
" Establecer bloqueos
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: Liberar bloqueos ya establecidos
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 y Optimistic Concurrency

" En RAP la concurrencia se controla mediante ETags
" Behavior Definition:
define behavior for ZI_Customer
with etag master last_changed_at
lock master
{
update;
delete;
}
" Implementación para 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. Transacción SM12 - Mostrar entradas de bloqueo

" Mostrar bloqueos programáticamente (como SM12)
DATA: lt_locks TYPE TABLE OF seqg3.
CALL FUNCTION 'ENQUEUE_READ'
EXPORTING
guname = sy-uname " Solo bloqueos propios
TABLES
enq = lt_locks
EXCEPTIONS
OTHERS = 1.
" Report
WRITE: / 'Bloqueos activos:'.
WRITE: / '---------------'.
LOOP AT lt_locks INTO DATA(ls_lock).
WRITE: / 'Tabla:', ls_lock-gname,
/ 'Argumento:', ls_lock-garg,
/ 'Modo:', ls_lock-gmode,
/ 'Usuario:', ls_lock-guname,
/ 'Hora:', ls_lock-gttime.
SKIP.
ENDLOOP.

15. Limpieza en caso de terminación del programa

CLASS zcl_lock_manager DEFINITION.
PUBLIC SECTION.
METHODS: constructor.
METHODS: destructor. " Se llama en 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.
" Opcional: Registro para limpieza
ENDMETHOD.
METHOD destructor.
" Liberar bloqueos cuando el objeto es destruido
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.
" Uso con TRY-CLEANUP
TRY.
DATA(lo_lock) = NEW zcl_lock_manager( ).
lo_lock->lock_customer( '1000' ).
" Procesamiento...
CLEANUP.
" Se ejecuta automáticamente en Exceptions
IF lo_lock IS BOUND.
CLEAR lo_lock. " Dispara destructor
ENDIF.
ENDTRY.

Crear Lock Object en SE11

  1. SE11 → Lock Object → Nombre: EZ_<Nombre>
  2. Indicar tabla primaria
  3. Definir argumentos de bloqueo (campos clave)
  4. Activar → genera funciones ENQUEUE/DEQUEUE

Notas importantes / Mejores prácticas

  • Siempre llamar DEQUEUE – también en caso de error.
  • Mantener bloqueos cortos – solo durante la modificación.
  • Mismo orden para múltiples bloqueos (evitar deadlocks).
  • _WAIT = abap_true para espera automática.
  • _COLLECT para establecer múltiples bloqueos atómicamente.
  • Bloqueo optimista para tiempos de edición largos.
  • SM12 para análisis de bloqueos activos.
  • COMMIT WORK libera bloqueos (Scope 1).
  • Nombrar Lock Objects con convención EZ_.
  • Combinar con Exception Classes para manejo de errores.