Lock
In classic SAP GUI, a transaction like VA02 calls ENQUEUE_EVVBAK and the lock lives as long as the dialog session. Web apps are different: each roundtrip is a fresh ABAP session by default, so a lock set in one roundtrip is gone by the next. Locking in abap2UI5 means picking a deliberate strategy for two questions:
| Phase | Question |
|---|---|
| Edit | What happens while the user is thinking and typing? |
| Save | What happens the moment they hit save? |
The patterns below combine in different ways. The examples use the sales order header table VBAK and its standard enqueue object EVVBAK, but the same shapes apply to any table. For patterns 1 to 6 the full source is included as a collapsible block at the end of the section; 7 and 8 point at the RAP draft chapter and the add-on that implement them.
Which Strategy?
Eight ways to keep two users from overwriting each other, from none at all to a framework that does it for you. Pick from the table, then read that one strategy below; every full class is folded under its section.
| # | Strategy | "Locked by X" while editing | Catches external writes | Stateless | Best fit |
|---|---|---|---|---|---|
| 1 | No Locking | no | no | yes | Demos, sandboxes |
| 2 | Lock at Save | no | partial (race window) | yes | Single-app writers, low contention |
| 3 | Optimistic Check | no | yes (timestamp) | yes | Many concurrent users |
| 4 | Combined | no | yes | yes | Production default |
| 5 | Stateful Session | yes (enqueue) | yes | no (pins work process) | GUI-like feel, few users |
| 6 | Soft Lock | yes (Z table) | only via underlying guard | yes | "Locked by Alice" UX |
| 7 | RAP Drafts | yes (RAP-managed) | yes | yes | A released draft-enabled BO exists |
| 8 | Lock-Manager Add-on | yes | yes | yes | Skip the boilerplate |
Start with (4) Combined unless one of these tips the balance:
- The app is read-only → no lock needed
- A released, draft-enabled SAP BO already covers your object → (7) RAP Drafts
- You need a "locked by X since…" message at open → add (6) Soft Lock on top of (4), or use (5) Stateful Session for few users with a GUI-like feel
- A lock-manager add-on exists for your platform → (8) Lock-Manager Add-on
For the underlying concepts and trade-offs of statefulness, see Statefulness.
1. No Locking
The minimal starting point — the user edits and saves, no lock and no conflict check. Last save wins, silently. Fine for personal sandboxes and throwaway demos, but rarely what you want in production.
Full source — ZCL_SAMPLE_LOCK_1
* Scenario 1 — Naive editing (no locking)
*
* The simplest possible starting point. The user can change a sales
* order and save. There is no lock and no conflict check. Last save
* wins, silently.
*
* When to use this:
* - Personal sandboxes, throwaway demos, internal tools where only
* one user ever touches a record.
CLASS zcl_sample_lock_1 DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
DATA auart TYPE vbak-auart.
PROTECTED SECTION.
DATA client TYPE REF TO z2ui5_if_client.
DATA vbeln TYPE vbak-vbeln VALUE `0000004711`.
DATA ernam TYPE vbak-ernam.
DATA erdat TYPE vbak-erdat.
METHODS on_init.
METHODS on_event_save.
METHODS view_display.
METHODS data_read.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_sample_lock_1 IMPLEMENTATION.
METHOD z2ui5_if_app~main.
me->client = client.
IF client->check_on_navigated( ).
on_init( ).
ELSEIF client->check_on_event( `SAVE` ).
on_event_save( ).
ENDIF.
ENDMETHOD.
METHOD on_init.
data_read( ).
view_display( ).
ENDMETHOD.
METHOD data_read.
SELECT SINGLE auart, ernam, erdat
FROM vbak
WHERE vbeln = @vbeln
INTO ( @auart, @ernam, @erdat ).
ENDMETHOD.
METHOD on_event_save.
"don't do that, just demo
"UPDATE vbak SET auart = @auart WHERE vbeln = @vbeln.
"COMMIT WORK.
client->message_toast_display( `Saved.` ).
ENDMETHOD.
METHOD view_display.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:form` v = `sap.ui.layout.form`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `Edit Sales Order — No Locking`
)->a( n = `navButtonPress` v = client->_event_nav_app_leave( )
)->a( n = `showNavButton` b = client->check_app_prev_stack( )
)->ele( n = `SimpleForm` ns = `form`
)->a( n = `title` v = `Header`
)->a( n = `editable` b = abap_true
)->ele( n = `content` ns = `form`
)->tag( `Label`
)->a( n = `text` v = `Sales Order`
)->tag( `Input`
)->a( n = `value` t = CONV string( vbeln )
)->a( n = `enabled` b = abap_false
)->tag( `Label`
)->a( n = `text` v = `Type`
)->tag( `Input`
)->a( n = `value` v = client->_bind( auart )
)->tag( `Label`
)->a( n = `text` v = `Created by`
)->tag( `Input`
)->a( n = `value` t = CONV string( ernam )
)->a( n = `enabled` b = abap_false
)->tag( `Label`
)->a( n = `text` v = `Created on`
)->tag( `Input`
)->a( n = `value` t = CONV string( erdat )
)->a( n = `enabled` b = abap_false
)->tag( `Button`
)->a( n = `text` v = `Save`
)->a( n = `press` v = client->_event( `SAVE` ) ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.2. Lock at Save
Do not hold a lock while the user thinks. Acquire it the moment they save, write, commit, and release — all in one short roundtrip:
METHOD on_event_save.
CALL FUNCTION `ENQUEUE_EVVBAK`
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
client->message_box_display( `Locked by another user.` ).
RETURN.
ENDIF.
UPDATE vbak SET auart = @auart WHERE vbeln = @vbeln.
COMMIT WORK.
CALL FUNCTION `DEQUEUE_EVVBAK`
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln.
ENDMETHOD.The lock exists for milliseconds, so this scales — but it protects only the write, not the edit. The enqueue is taken after the user read the record, so a second user who read the same record, saved first and released the lock again is never noticed: the first user's save then overwrites that change with values based on the old read. That lost update is what the timestamp of pattern 3 catches.
Full source — ZCL_SAMPLE_LOCK_2
* Scenario 2 — Edit + Enqueue at save
*
* We do NOT hold a lock while the user thinks. At the moment they
* press Save, we lock, write, commit, and release in one short
* roundtrip.
*
* When to use this:
* - Quick edits with low chance of two users hitting the same record
* - Default starting point for most stateless editing apps
CLASS zcl_sample_lock_2 DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
DATA auart TYPE vbak-auart.
PROTECTED SECTION.
DATA client TYPE REF TO z2ui5_if_client.
DATA vbeln TYPE vbak-vbeln VALUE `0000004711`.
METHODS on_init.
METHODS on_event_save.
METHODS view_display.
METHODS data_read.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_sample_lock_2 IMPLEMENTATION.
METHOD z2ui5_if_app~main.
me->client = client.
IF client->check_on_navigated( ).
on_init( ).
ELSEIF client->check_on_event( `SAVE` ).
on_event_save( ).
ENDIF.
ENDMETHOD.
METHOD on_init.
data_read( ).
view_display( ).
ENDMETHOD.
METHOD data_read.
SELECT SINGLE auart
FROM vbak
WHERE vbeln = @vbeln
INTO @auart.
ENDMETHOD.
METHOD on_event_save.
CALL FUNCTION 'ENQUEUE_EVVBAK'
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
client->message_box_display( |Cannot lock { vbeln } — already locked by another user| ).
RETURN.
ENDIF.
"don't do that, just demo
"UPDATE vbak SET auart = @auart WHERE vbeln = @vbeln.
"COMMIT WORK.
CALL FUNCTION 'DEQUEUE_EVVBAK'
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln.
client->message_toast_display( `Saved.` ).
ENDMETHOD.
METHOD view_display.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:form` v = `sap.ui.layout.form`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `Edit Sales Order — Enqueue at Save`
)->a( n = `navButtonPress` v = client->_event_nav_app_leave( )
)->a( n = `showNavButton` b = client->check_app_prev_stack( )
)->ele( n = `SimpleForm` ns = `form`
)->a( n = `title` v = `Header`
)->a( n = `editable` b = abap_true
)->ele( n = `content` ns = `form`
)->tag( `Label`
)->a( n = `text` v = `Sales Order`
)->tag( `Input`
)->a( n = `value` t = CONV string( vbeln )
)->a( n = `enabled` b = abap_false
)->tag( `Label`
)->a( n = `text` v = `Type`
)->tag( `Input`
)->a( n = `value` v = client->_bind( auart )
)->tag( `Button`
)->a( n = `text` v = `Save`
)->a( n = `press` v = client->_event( `SAVE` ) ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.3. Optimistic Check
On read, remember the record's change timestamp. On save, re-read and reject if it shifted in the meantime — the same idea as an HTTP ETag:
METHOD data_read.
SELECT SINGLE aedat, UPD_TMSTMP FROM vbak
WHERE vbeln = @vbeln
INTO ( @token_aedat, @token_aezet ).
ENDMETHOD.
METHOD on_event_save.
SELECT SINGLE aedat, UPD_TMSTMP FROM vbak
WHERE vbeln = @vbeln
INTO ( @DATA(now_aedat), @DATA(now_aezet) ).
IF now_aedat <> token_aedat OR now_aezet <> token_aezet.
client->message_box_display( `Record changed — please reload.` ).
RETURN.
ENDIF.
" ...write through...
ENDMETHOD.No lock is held during the edit phase, so this scales to many concurrent users and catches conflicts even from outside your app (SE16, batch jobs, RFC).
Pick a column that always updates on writes. If anyone writes the table bypassing AEDAT / UPD_TMSTMP, the check silently misses real conflicts.
Full source — ZCL_SAMPLE_LOCK_3
* Scenario 3 — Optimistic locking (timestamp check)
*
* On read we remember the record's last-changed timestamp. On save,
* we re-read it and reject if it changed in the meantime. Same idea
* as HTTP ETag or OData's @odata.etag.
*
* When to use this:
* - Any time silent overwrites would be a problem
* - Combine with Scenario 2 — it costs almost nothing and catches
* real bugs
CLASS zcl_sample_lock_3 DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
DATA auart TYPE vbak-auart.
PROTECTED SECTION.
DATA client TYPE REF TO z2ui5_if_client.
DATA vbeln TYPE vbak-vbeln VALUE `0000004711`.
DATA token_aedat TYPE vbak-aedat.
DATA token_aezet TYPE vbak-UPD_TMSTMP.
METHODS on_init.
METHODS on_event_save.
METHODS view_display.
METHODS data_read.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_sample_lock_3 IMPLEMENTATION.
METHOD z2ui5_if_app~main.
me->client = client.
IF client->check_on_navigated( ).
on_init( ).
ELSEIF client->check_on_event( `SAVE` ).
on_event_save( ).
ENDIF.
ENDMETHOD.
METHOD on_init.
data_read( ).
view_display( ).
ENDMETHOD.
METHOD data_read.
SELECT SINGLE auart, aedat, UPD_TMSTMP
FROM vbak
WHERE vbeln = @vbeln
INTO ( @auart, @token_aedat, @token_aezet ).
ENDMETHOD.
METHOD on_event_save.
DATA current_aedat TYPE vbak-aedat.
DATA current_aezet TYPE vbak-UPD_TMSTMP.
SELECT SINGLE aedat, UPD_TMSTMP
FROM vbak
WHERE vbeln = @vbeln
INTO ( @current_aedat, @current_aezet ).
IF current_aedat <> token_aedat OR current_aezet <> token_aezet.
client->message_box_display(
|Sales order { vbeln } has been changed by another user since you opened it. Please refresh.| ).
RETURN.
ENDIF.
"don't do that, just for demo
"GET TIME STAMP FIELD now.
"UPDATE vbak
" SET auart = @auart,
" aedat = @sy-datum,
" UPD_TMSTMP = @now
" WHERE vbeln = @vbeln.
"COMMIT WORK.
data_read( ).
client->message_toast_display( `Saved.` ).
ENDMETHOD.
METHOD view_display.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:form` v = `sap.ui.layout.form`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `Edit Sales Order — Optimistic Locking`
)->a( n = `navButtonPress` v = client->_event_nav_app_leave( )
)->a( n = `showNavButton` b = client->check_app_prev_stack( )
)->ele( n = `SimpleForm` ns = `form`
)->a( n = `title` v = `Header`
)->a( n = `editable` b = abap_true
)->ele( n = `content` ns = `form`
)->tag( `Label`
)->a( n = `text` v = `Sales Order`
)->tag( `Input`
)->a( n = `value` t = CONV string( vbeln )
)->a( n = `enabled` b = abap_false
)->tag( `Label`
)->a( n = `text` v = `Type`
)->tag( `Input`
)->a( n = `value` v = client->_bind( auart )
)->tag( `Button`
)->a( n = `text` v = `Save`
)->a( n = `press` v = client->_event( `SAVE` ) ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.4. Combined (recommended default)
Lock at Save plus the Optimistic Check is the safest stateless pattern and the sensible production default — the enqueue serializes parallel saves of this app, the timestamp check catches everyone else.
Full source — ZCL_SAMPLE_LOCK_4
* Scenario 4 — Combined (the recommended default)
*
* Enqueue at save AND the optimistic check. The safest stateless
* pattern, and the one most production apps should default to.
*
* Why both:
* - The enqueue serializes concurrent writers so two saves cannot
* interleave.
* - The timestamp check catches anyone who slipped in via another
* path (SE16, batch job, classic SAP GUI) between read and write.
CLASS zcl_sample_lock_4 DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
DATA auart TYPE vbak-auart.
PROTECTED SECTION.
DATA client TYPE REF TO z2ui5_if_client.
DATA vbeln TYPE vbak-vbeln VALUE `0000004711`.
DATA token_aedat TYPE vbak-aedat.
DATA token_aezet TYPE vbak-UPD_TMSTMP.
METHODS on_init.
METHODS on_event_save.
METHODS view_display.
METHODS data_read.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_sample_lock_4 IMPLEMENTATION.
METHOD z2ui5_if_app~main.
me->client = client.
IF client->check_on_navigated( ).
on_init( ).
ELSEIF client->check_on_event( `SAVE` ).
on_event_save( ).
ENDIF.
ENDMETHOD.
METHOD on_init.
data_read( ).
view_display( ).
ENDMETHOD.
METHOD data_read.
SELECT SINGLE auart, aedat, UPD_TMSTMP
FROM vbak
WHERE vbeln = @vbeln
INTO ( @auart, @token_aedat, @token_aezet ).
ENDMETHOD.
METHOD on_event_save.
CALL FUNCTION 'ENQUEUE_EVVBAK'
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
client->message_box_display( |Sales order { vbeln } is currently being saved by another user. Try again.| ).
RETURN.
ENDIF.
DATA current_aedat TYPE vbak-aedat.
DATA current_aezet TYPE vbak-UPD_TMSTMP.
SELECT SINGLE aedat, UPD_TMSTMP
FROM vbak
WHERE vbeln = @vbeln
INTO ( @current_aedat, @current_aezet ).
IF current_aedat <> token_aedat OR current_aezet <> token_aezet.
CALL FUNCTION 'DEQUEUE_EVVBAK'
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln.
client->message_box_display(
|Sales order { vbeln } has been changed by another user since you opened it. Please refresh.| ).
RETURN.
ENDIF.
"don't do that, just for demo
"GET TIME STAMP FIELD now.
"UPDATE vbak
" SET auart = @auart,
" aedat = @sy-datum,
" UPD_TMSTMP = @now
" WHERE vbeln = @vbeln.
"COMMIT WORK.
CALL FUNCTION 'DEQUEUE_EVVBAK'
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln.
data_read( ).
client->message_toast_display( `Saved.` ).
ENDMETHOD.
METHOD view_display.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:form` v = `sap.ui.layout.form`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `Edit Sales Order — Enqueue + Optimistic`
)->a( n = `navButtonPress` v = client->_event_nav_app_leave( )
)->a( n = `showNavButton` b = client->check_app_prev_stack( )
)->ele( n = `SimpleForm` ns = `form`
)->a( n = `title` v = `Header`
)->a( n = `editable` b = abap_true
)->ele( n = `content` ns = `form`
)->tag( `Label`
)->a( n = `text` v = `Sales Order`
)->tag( `Input`
)->a( n = `value` t = CONV string( vbeln )
)->a( n = `enabled` b = abap_false
)->tag( `Label`
)->a( n = `text` v = `Type`
)->tag( `Input`
)->a( n = `value` v = client->_bind( auart )
)->tag( `Button`
)->a( n = `text` v = `Save`
)->a( n = `press` v = client->_event( `SAVE` ) ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.5. Stateful Session
For classic SAP GUI-like behavior, switch the session to stateful and call the lock function module on init. The lock survives subsequent roundtrips as long as the session stays alive:
IF client->check_on_navigated( ).
CALL FUNCTION `ENQUEUE_EVVBAK`
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc = 0.
client->set_session_stateful( ).
ELSE.
client->message_box_display( `Locked by another user.` ).
client->nav_app_leave( ).
RETURN.
ENDIF.
ENDIF.While the app is open, an entry for EVVBAK / 0000004711 is visible in SM12. The user can navigate, type, wait — the lock stays. On exit, release it explicitly:
CALL FUNCTION `DEQUEUE_EVVBAK`
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln.
client->set_session_stateful( abap_false ).Each active user pins a work process, and a leaked enqueue blocks the record until the session times out — the cost and the rules for paying it are on Statefulness → Trade-offs.
Full source — ZCL_SAMPLE_LOCK_5
* Scenario 5 — Stateful session with a persistent enqueue
*
* Classic SAP GUI behaviour: the moment the user opens the screen the
* lock is held until they save or leave. With set_session_stateful( ),
* abap2UI5 keeps the session alive between roundtrips so a real
* ENQUEUE_EVVBAK survives.
*
* When to use this:
* - Internal back-office apps with few concurrent users
* - You want users to immediately see "locked by X" when opening
*
* Cost: each active user pins a work process. Do NOT use this for
* high-traffic apps.
CLASS zcl_sample_lock_5 DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
DATA auart TYPE vbak-auart.
PROTECTED SECTION.
DATA client TYPE REF TO z2ui5_if_client.
DATA vbeln TYPE vbak-vbeln VALUE `0000004711`.
METHODS on_init.
METHODS on_event_save.
METHODS on_event_cancel.
METHODS lock_acquire.
METHODS lock_release.
METHODS view_display.
METHODS data_read.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_sample_lock_5 IMPLEMENTATION.
METHOD z2ui5_if_app~main.
me->client = client.
IF client->check_on_navigated( ).
on_init( ).
ELSEIF client->check_on_event( `SAVE` ).
on_event_save( ).
ELSEIF client->check_on_event( `CANCEL` ).
on_event_cancel( ).
ENDIF.
ENDMETHOD.
METHOD on_init.
lock_acquire( ).
ENDMETHOD.
METHOD lock_acquire.
CALL FUNCTION 'ENQUEUE_EVVBAK'
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
client->set_session_stateful( abap_false ).
client->message_box_display( |Sales order { vbeln } is locked by another user.| ).
RETURN.
ENDIF.
client->set_session_stateful( ).
data_read( ).
view_display( ).
ENDMETHOD.
METHOD lock_release.
CALL FUNCTION 'DEQUEUE_EVVBAK'
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln.
client->set_session_stateful( abap_false ).
ENDMETHOD.
METHOD data_read.
SELECT SINGLE auart
FROM vbak
WHERE vbeln = @vbeln
INTO @auart.
ENDMETHOD.
METHOD on_event_save.
"don't do that, just demo
"GET TIME STAMP FIELD now.
"UPDATE vbak
" SET auart = @auart,
" aedat = @sy-datum,
" UPD_TMSTMP = @now
" WHERE vbeln = @vbeln.
"COMMIT WORK.
lock_release( ).
client->message_toast_display( `Saved.` ).
client->nav_app_leave( ).
ENDMETHOD.
METHOD on_event_cancel.
lock_release( ).
client->nav_app_leave( ).
ENDMETHOD.
METHOD view_display.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:form` v = `sap.ui.layout.form`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `Edit Sales Order — Stateful Lock`
)->a( n = `navButtonPress` v = client->_event( `CANCEL` )
)->a( n = `showNavButton` b = client->check_app_prev_stack( )
)->ele( n = `SimpleForm` ns = `form`
)->a( n = `title` v = `Header`
)->a( n = `editable` b = abap_true
)->ele( n = `content` ns = `form`
)->tag( `Label`
)->a( n = `text` v = `Sales Order`
)->tag( `Input`
)->a( n = `value` t = CONV string( vbeln )
)->a( n = `enabled` b = abap_false
)->tag( `Label`
)->a( n = `text` v = `Type`
)->tag( `Input`
)->a( n = `value` v = client->_bind( auart )
)->tag( `Button`
)->a( n = `text` v = `Save`
)->a( n = `press` v = client->_event( `SAVE` )
)->tag( `Button`
)->a( n = `text` v = `Cancel`
)->a( n = `press` v = client->_event( `CANCEL` ) ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.6. Soft Lock
A soft lock is a row in a custom Z table marking "user X is editing object Y". It is not enforced by the SAP kernel — only your app code respects it — so use it for UX feedback ("locked by Alice since 09:32") and always layer it on top of a real save-time guard. A minimal schema (table Z2UI5_SAMPLE_01 in the example below):
| Field | Type | Description |
|---|---|---|
| MANDT | MANDT | Client (key) |
| VBELN | VBELN_VA | Object (key) |
| USERNAME | SYUNAME | Editing user |
| LOCKED_AT | TIMESTAMPL | When the lock started |
A user closing the browser without pressing Release leaves the row behind, so add a cleanup job that deletes entries older than, say, 30 minutes. The example below uses a matching Z2UI5_SAMPLE_01 table.
Full source — ZCL_SAMPLE_LOCK_6
* Scenario 6 — Soft lock (advisory only)
*
* A soft lock is a row in a custom Z table marking
* "user X is editing sales order Y." It is NOT enforced by the SAP
* kernel — only your app code respects it. Use it for UX feedback
* ("locked by Alice since 09:32"), always layered on top of a real
* save-time guard.
*
* Required Z table Z2UI5_SAMPLE_01 (create via SE11):
* MANDT MANDT Client (key)
* VBELN VBELN_VA Sales order (key)
* USERNAME SYUNAME Editing user
* LOCKED_AT TIMESTAMPL When the lock started
* Delivery class A.
CLASS zcl_sample_lock_6 DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
DATA auart TYPE vbak-auart.
PROTECTED SECTION.
DATA client TYPE REF TO z2ui5_if_client.
DATA vbeln TYPE vbak-vbeln VALUE `0000004711`.
DATA locked_by TYPE string.
DATA token_aedat TYPE vbak-aedat.
DATA token_aezet TYPE vbak-UPD_TMSTMP.
METHODS on_init.
METHODS on_event_save.
METHODS on_event_release.
METHODS soft_lock_acquire
RETURNING
VALUE(ok) TYPE abap_bool.
METHODS soft_lock_release.
METHODS view_display.
METHODS data_read.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_sample_lock_6 IMPLEMENTATION.
METHOD z2ui5_if_app~main.
me->client = client.
IF client->check_on_navigated( ).
on_init( ).
ELSEIF client->check_on_event( `SAVE` ).
on_event_save( ).
ELSEIF client->check_on_event( `RELEASE` ).
on_event_release( ).
ENDIF.
ENDMETHOD.
METHOD on_init.
" soft_lock_acquire fills locked_by when someone else holds the lock;
" the view renders read-only in that case
soft_lock_acquire( ).
data_read( ).
view_display( ).
ENDMETHOD.
METHOD soft_lock_acquire.
DATA s_existing TYPE z2ui5_sample_01.
SELECT SINGLE *
FROM z2ui5_sample_01
WHERE vbeln = @vbeln
INTO @s_existing.
IF sy-subrc = 0 AND s_existing-username <> sy-uname.
locked_by = |Locked by { s_existing-username } since { s_existing-locked_at TIMESTAMP = USER }|.
ok = abap_false.
RETURN.
ENDIF.
DATA s_new TYPE z2ui5_sample_01.
s_new-vbeln = vbeln.
s_new-username = sy-uname.
GET TIME STAMP FIELD s_new-locked_at.
MODIFY z2ui5_sample_01 FROM @s_new.
COMMIT WORK.
locked_by = ``.
ok = abap_true.
ENDMETHOD.
METHOD soft_lock_release.
DELETE FROM z2ui5_sample_01
WHERE vbeln = @vbeln
AND username = @sy-uname.
COMMIT WORK.
ENDMETHOD.
METHOD data_read.
SELECT SINGLE auart, aedat, UPD_TMSTMP
FROM vbak
WHERE vbeln = @vbeln
INTO ( @auart, @token_aedat, @token_aezet ).
ENDMETHOD.
METHOD on_event_save.
CALL FUNCTION 'ENQUEUE_EVVBAK'
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln
EXCEPTIONS
foreign_lock = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
client->message_box_display( `Could not acquire enqueue` ).
RETURN.
ENDIF.
DATA current_aedat TYPE vbak-aedat.
DATA current_aezet TYPE vbak-UPD_TMSTMP.
SELECT SINGLE aedat, UPD_TMSTMP
FROM vbak
WHERE vbeln = @vbeln
INTO ( @current_aedat, @current_aezet ).
IF current_aedat <> token_aedat OR current_aezet <> token_aezet.
CALL FUNCTION 'DEQUEUE_EVVBAK'
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln.
client->message_box_display( `Record changed by another user. Please refresh.` ).
RETURN.
ENDIF.
"don't do that, just demo
"GET TIME STAMP FIELD now.
"UPDATE vbak
" SET auart = @auart,
" aedat = @sy-datum,
" UPD_TMSTMP = @now
" WHERE vbeln = @vbeln.
"COMMIT WORK.
CALL FUNCTION 'DEQUEUE_EVVBAK'
EXPORTING
mode_vbak = `E`
mandt = sy-mandt
vbeln = vbeln.
soft_lock_release( ).
client->message_toast_display( `Saved.` ).
client->nav_app_leave( ).
ENDMETHOD.
METHOD on_event_release.
soft_lock_release( ).
client->nav_app_leave( ).
ENDMETHOD.
METHOD view_display.
DATA(editable) = COND abap_bool( WHEN locked_by IS INITIAL THEN abap_true ELSE abap_false ).
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:form` v = `sap.ui.layout.form`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `Edit Sales Order — Soft Lock + Save Guard`
)->a( n = `navButtonPress` v = client->_event_nav_app_leave( )
)->a( n = `showNavButton` b = client->check_app_prev_stack( )
)->ele( n = `SimpleForm` ns = `form`
)->a( n = `title` v = `Header`
)->a( n = `editable` b = editable
)->ele( n = `content` ns = `form`
)->tag( `Label`
)->a( n = `text` v = `Sales Order`
)->tag( `Input`
)->a( n = `value` t = CONV string( vbeln )
)->a( n = `enabled` b = abap_false
)->tag( `Label`
)->a( n = `text` v = `Type`
)->tag( `Input`
)->a( n = `value` v = client->_bind( auart )
)->tag( `Label`
)->a( n = `text` v = `Status`
)->tag( `Input`
)->a( n = `value` t = locked_by
)->a( n = `enabled` b = abap_false
)->tag( `Button`
)->a( n = `text` v = `Save`
)->a( n = `press` v = client->_event( `SAVE` )
)->a( n = `enabled` b = editable
)->tag( `Button`
)->a( n = `text` v = `Release & Exit`
)->a( n = `press` v = client->_event( `RELEASE` ) ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.7. RAP Drafts
On modern releases, RAP draft-enabled business objects manage locking for you: the draft holds an exclusive lock for its owner while the user keeps editing — no stateful session, no ENQUEUE_* call. If a released SAP BO already covers your object, this is usually the simplest path. See Draft Handling.
8. Lock-Manager Add-on
The community add-on lock-manager wraps the lock logic in a reusable class — including stale-lock cleanup and a "locked by X since…" message for the user. Install it like any other add-on and call it instead of writing the boilerplate yourself.
Working Samples
Complete apps from the sample catalog that use what this page describes. Each is a single class in abap2UI5/samples unless its row names another of the three sample repositories — pull that repository with abapGit and start the class with ?app_start=<class>.
| Sample | Class |
|---|---|
| Stateful Sessions — Locks — ENQUEUE_E_TABLE and ENQUEUE_READ, end and restart the session | Z2UI5_CL_SMPS_APP_485 · samples-stack, branch 06-stateful-locks |
| Stateful Sessions — Navigation and Locks — every Next Lock View takes the next VARKEY, going back releases it | Z2UI5_CL_SMPS_APP_490 · samples-stack, branch 06-stateful-locks |
