Skip to content

Lesson 9 — Working with Custom Persistence ​

In Lesson 8, you designed and activated:

text
ZRESV_###

The table exists.

But it contains:

text
0 rows

Today you will manipulate that persistence directly from ABAP.

You will learn:

text
table as ABAP type
UUID generation
INSERT
SELECT
UPDATE
DELETE
sy-dbcnt
safe WHERE conditions
implicit client handling
verification
cleanup

This is intentionally the last lesson where direct writes to this table are presented as the main learning path.

Later, once the table belongs to a RAP business object, consumers should use the RAP contract instead.


Lesson information ​

Teacher-led timeAbout 3 hours
Self-practice timeAbout 3 hours
EnvironmentSAP BTP ABAP Trial
PrerequisitesLesson 8
Existing tableZRESV_###
Guided classZCL_RESV_SQL_###
Main outcomeSafe full DML lifecycle

Learning objectives ​

After the teacher-led part, you should be able to:

  • use a Database Table as an ABAP structured type;
  • generate an X16 UUID with CL_SYSTEM_UUID;
  • handle CX_UUID_ERROR;
  • construct a table-row value with VALUE;
  • create a row with INSERT;
  • verify a write using SELECT SINGLE;
  • interpret sy-dbcnt;
  • update a specific row safely;
  • explain why an unrestricted UPDATE is dangerous;
  • delete only the row owned by the current exercise;
  • verify cleanup;
  • explain implicit client handling during normal ABAP SQL access;
  • explain why direct SQL does not populate RAP administrative fields automatically;
  • distinguish low-level persistence mechanics from the future RAP application API.

Certification alignment ​

This lesson reinforces current ABAP Cloud backend skills around:

text
ABAP SQL
database write access
structured Dictionary types
exception handling
system fields
client-dependent access
safe data modification
transaction awareness
transition to Business Objects / EML

SAP's current core ABAP learning path also connects database work with Business Object and EML-based updates.

The course teaches direct writes here for persistence literacy, then moves to RAP.


1. Architecture today ​

Lesson 8 ended here:

text
ZRESV_###

Today:

text
ZCL_RESV_SQL_###
      ↓
ABAP SQL
      ↓
ZRESV_###

Later:

text
Consumer
   ↓
EML / RAP service
   ↓
RAP behavior
   ↓
ZRESV_###

Understand the lower layer before replacing it with a business-object API.


2. A Database Table is also an ABAP type ​

The table definition can type an ABAP structure:

abap
DATA reservation TYPE zresv_###.

That structure contains the table row fields.

You can also define:

abap
DATA reservations TYPE STANDARD TABLE OF zresv_###
  WITH EMPTY KEY.

So the Dictionary object is both:

text
database schema
+
global ABAP type

3. Create the SQL exercise class ​

Create:

text
ZCL_RESV_SQL_###

Implement:

text
IF_OO_ADT_CLASSRUN

The class will perform this lifecycle:

text
Generate UUID
    ↓
INSERT
    ↓
SELECT / verify
    ↓
UPDATE
    ↓
SELECT / verify
    ↓
DELETE
    ↓
verify cleanup

4. Why generate the UUID ourselves today? ​

The table key contains:

text
uuid : SYSUUID_X16

Today there is no RAP managed numbering yet.

So low-level ABAP code must supply the UUID.

Use:

abap
cl_system_uuid=>create_uuid_x16_static( )

5. UUID generation can fail ​

The method can raise:

text
CX_UUID_ERROR

Use:

abap
TRY.
    DATA(uuid) = cl_system_uuid=>create_uuid_x16_static( ).
  CATCH cx_uuid_error INTO DATA(uuid_error).
    out->write( uuid_error->get_text( ) ).
    RETURN.
ENDTRY.

Do not assume infrastructure calls can never fail.


6. Construct a Reservation row ​

Use the table as a structured type:

abap
DATA(reservation) = VALUE zresv_###(
  uuid             = uuid
  reservation_no   = '00000001'
  customer_name    = 'Ada Martin'
  customer_email   = 'ada@example.com'
  workshop_code    = 'ABAP01'
  reservation_date = sy-datum
  status           = 'N'
  fee              = '125.00'
  currency_code    = 'EUR'
  notes            = 'Lesson 9 SQL exercise'
).

Notice what is not supplied:

text
client
RAP administrative fields

7. Why don't we fill client? ​

The table is client-dependent.

Normal ABAP SQL performs implicit client handling.

So application code does not normally set:

text
client

manually in this exercise.

Likewise, ordinary reads do not need:

abap
WHERE client = @sy-mandt

Client handling is supplied by the ABAP SQL layer.


8. Why are RAP admin fields left initial? ​

These fields exist:

text
local_created_by
local_created_at
local_last_changed_by
local_last_changed_at
last_changed_at

But today's direct SQL code does not populate them.

That is intentional.

Direct table writes do not magically execute future managed RAP behavior.

This difference becomes visible later when RAP manages the persistence.


9. INSERT ​

Create the row:

abap
INSERT zresv_### FROM @reservation.

Read it as:

Insert this ABAP structure as a row in the current client's ZRESV_### table.


10. Capture sy-dbcnt immediately ​

After a database-changing statement:

abap
sy-dbcnt

contains the number of rows processed.

Copy it immediately:

abap
DATA(inserted_rows) = sy-dbcnt.

Then output:

abap
out->write(
  |Inserted rows: { inserted_rows }|
).

Expected:

text
1

11. Why immediately? ​

System fields are reused by later statements.

If you care about the result of:

text
INSERT
UPDATE
DELETE

capture it before executing unrelated operations.

This is the same discipline you learned earlier with sy-subrc.


12. Verify the INSERT ​

Never assume:

text
statement executed
=
data is exactly what I intended

Read the row back:

abap
SELECT SINGLE FROM zresv_###
  FIELDS uuid,
         reservation_no,
         customer_name,
         workshop_code,
         status,
         fee,
         currency_code
  WHERE uuid = @uuid
  INTO @DATA(created_reservation).

Then:

abap
out->write( 'After INSERT' ).
out->write( created_reservation ).

13. Why use the UUID in the WHERE condition? ​

The UUID identifies the row created by this run.

That gives a safe, precise condition:

abap
WHERE uuid = @uuid

It avoids depending on names or other non-unique business data.


14. UPDATE ​

Change only the status:

abap
UPDATE zresv_###
  SET status = 'C'
  WHERE uuid = @uuid.

Then immediately capture:

abap
DATA(updated_rows) = sy-dbcnt.

Expected:

text
1

15. The WHERE clause is not optional thinking ​

This is dangerous:

abap
UPDATE zresv_###
  SET status = 'C'.

It means:

update every row visible to the statement.

In a shared training environment, that is unacceptable unless intentionally required.

Before executing DML, ask:

text
Which exact rows can this statement change?

16. Verify the UPDATE ​

Read the row again:

abap
SELECT SINGLE FROM zresv_###
  FIELDS uuid,
         reservation_no,
         customer_name,
         workshop_code,
         status
  WHERE uuid = @uuid
  INTO @DATA(updated_reservation).

Expected:

text
Status = C

17. DELETE ​

Delete only the row created by the current run:

abap
DELETE FROM zresv_###
  WHERE uuid = @uuid.

Capture:

abap
DATA(deleted_rows) = sy-dbcnt.

Expected:

text
1

18. Why clean up? ​

A training program should be:

text
repeatable
safe
isolated

If every run leaves random sample data, the table becomes difficult to inspect and later exercises become less deterministic.

So the class cleans up its own row.


19. Verify the DELETE ​

Try to read the UUID again:

abap
SELECT SINGLE FROM zresv_###
  FIELDS uuid
  WHERE uuid = @uuid
  INTO @DATA(deleted_check).

Immediately inspect:

abap
sy-subrc

Expected:

text
not found

The exact numeric value is less important than understanding that the row no longer exists.


20. Complete guided class ​

abap
CLASS zcl_resv_sql_### DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

ENDCLASS.


CLASS zcl_resv_sql_### IMPLEMENTATION.

  METHOD if_oo_adt_classrun~main.

    DATA uuid TYPE sysuuid_x16.

    TRY.
        uuid = cl_system_uuid=>create_uuid_x16_static( ).
      CATCH cx_uuid_error INTO DATA(uuid_error).
        out->write( uuid_error->get_text( ) ).
        RETURN.
    ENDTRY.

    DATA(reservation) = VALUE zresv_###(
      uuid             = uuid
      reservation_no   = '00000001'
      customer_name    = 'Ada Martin'
      customer_email   = 'ada@example.com'
      workshop_code    = 'ABAP01'
      reservation_date = sy-datum
      status           = 'N'
      fee              = '125.00'
      currency_code    = 'EUR'
      notes            = 'Lesson 9 SQL exercise'
    ).

    INSERT zresv_### FROM @reservation.

    DATA(inserted_rows) = sy-dbcnt.

    out->write(
      |Inserted rows: { inserted_rows }|
    ).

    SELECT SINGLE FROM zresv_###
      FIELDS uuid,
             reservation_no,
             customer_name,
             workshop_code,
             status,
             fee,
             currency_code
      WHERE uuid = @uuid
      INTO @DATA(created_reservation).

    out->write( 'After INSERT' ).
    out->write( created_reservation ).


    UPDATE zresv_###
      SET status = 'C'
      WHERE uuid = @uuid.

    DATA(updated_rows) = sy-dbcnt.

    out->write(
      |Updated rows: { updated_rows }|
    ).

    SELECT SINGLE FROM zresv_###
      FIELDS uuid,
             reservation_no,
             customer_name,
             workshop_code,
             status
      WHERE uuid = @uuid
      INTO @DATA(updated_reservation).

    out->write( 'After UPDATE' ).
    out->write( updated_reservation ).


    DELETE FROM zresv_###
      WHERE uuid = @uuid.

    DATA(deleted_rows) = sy-dbcnt.

    out->write(
      |Deleted rows: { deleted_rows }|
    ).

    SELECT SINGLE FROM zresv_###
      FIELDS uuid
      WHERE uuid = @uuid
      INTO @DATA(deleted_check).

    IF sy-subrc <> 0.
      out->write( 'Cleanup verified: row no longer exists.' ).
    ELSE.
      out->write( 'Cleanup check failed.' ).
    ENDIF.

  ENDMETHOD.

ENDCLASS.

Replace all ###.


21. Run the class ​

Activate:

text
Ctrl + F3

Run:

text
F9

Expected output conceptually:

text
Inserted rows: 1

After INSERT
...

Updated rows: 1

After UPDATE
status = C

Deleted rows: 1

Cleanup verified: row no longer exists.

The UUID changes every run.


22. Inspect Data Preview ​

Open:

text
ZRESV_###

Use:

text
Alt + F8

Because the class deletes its own row, the table should normally return to:

text
0 rows

That is intentional.


23. Why no explicit COMMIT WORK exercise? ​

This lesson is about:

text
DML semantics
safe targeting
verification
cleanup

not about teaching manual transaction control as an application architecture.

The class performs the full lifecycle in one run and deletes its own data.

Later RAP lessons introduce the business-object transaction model and its save boundary through:

text
COMMIT ENTITIES

Do not build a habit of scattering manual commits through application logic.


24. Why not teach MODIFY today? ​

ABAP SQL also has other write forms.

The guided lesson deliberately uses:

text
INSERT
UPDATE
DELETE

because each operation has one clear purpose.

That makes the persistence lifecycle explicit:

text
create
change
remove

The Companion explains why MODIFY is not needed for the learning goal.


25. Direct SQL is not the future consumer API ​

Today you can write:

abap
UPDATE zresv_###

Later the Reservation table will belong to a RAP business object.

Then direct writes could bypass:

text
validation
determination
authorization
feature control
locking / ETag expectations
RAP messages
transactional buffer
save sequence

So this lesson teaches:

text
how persistence works

not:

text
how a finished RAP application should expose writes

26. Architectural transition ​

Today:

text
ZCL_RESV_SQL_###
      ↓
ABAP SQL
      ↓
ZRESV_###

Soon:

text
Consumer
   ↓
EML
   ↓
RAP BO
   ↓
ZRESV_###

That transition should now feel concrete rather than magical.


27. Common mistakes ​

UUID left initial ​

The UUID is part of the primary key.

Generate it before the INSERT.


Unsafe UPDATE ​

Never omit the WHERE condition accidentally.


Unsafe DELETE ​

Never run:

abap
DELETE FROM zresv_###.

as a training cleanup shortcut.


Reading sy-dbcnt too late ​

Copy it immediately after the DML statement you are measuring.


Manually filling client ​

Normal ABAP SQL client handling supplies the current client behavior.


Expecting RAP admin fields to fill themselves ​

There is no managed RAP behavior yet.


Confusing SQL write success with business validity ​

Today's table has little business behavior.

Later RAP is where business rules become part of the approved write path.


28. Knowledge check ​

1. Why can ZRESV_### be used as an ABAP type? ​

Show answer

A Dictionary Database Table also provides the row structure as a globally usable ABAP type.

2. Why do we generate a UUID before INSERT? ​

Show answer

The UUID is part of the technical primary key and no RAP managed numbering exists yet.

3. Why copy sy-dbcnt immediately? ​

Show answer

Later statements can overwrite system fields. Capturing it immediately preserves the row count for the operation being checked.

4. Why is WHERE uuid = @uuid important? ​

Show answer

It targets exactly the row created by this class run and prevents broad accidental changes.

5. Why are admin fields initial in this exercise? ​

Show answer

Direct SQL is not managed RAP behavior and does not automatically perform the future RAP lifecycle handling.

6. Why does the class delete its own row? ​

Show answer

To make the exercise safe and repeatable without leaving random training data.

7. Is direct SQL the normal consumer API once RAP owns this table? ​

Show answer

No. Future consumers should use the RAP business-object contract, such as EML, rather than bypassing behavior with direct table writes.


29. Lesson summary ​

You performed a complete low-level persistence lifecycle:

text
Generate UUID
    ↓
INSERT
    ↓
SELECT
    ↓
UPDATE
    ↓
SELECT
    ↓
DELETE
    ↓
verify cleanup

You also learned the architectural boundary:

Understanding direct DML is useful. Building future application behavior around direct table writes is not the RAP architecture.


30. What comes next? ​

Lesson 10 starts the RAP sequence.

The first question is:

How do we turn a database table into the data model of a RAP Business Object?

You will build:

text
ZRESV_###
    ↓
ZR_RESERVATION_###

No OData/UI is needed.

The focus moves from:

text
database row

to:

text
business object