목차
Additional Save
Managed 시나리오에서 프레임워크의 자동 저장과 별개로 개발자가 직접 저장로직을 추가하는 기능입니다. 구현위치는 Behavior Definition에 선언 후 Saver Class의 save_modified 메소드 내에 구현합니다.
주요용도
변경이력 로그 기록 - 누가 무엇을 언제 변경했는지 별도 테이블에 이력 남기기
외부 시스템 연동 - 저장 시점에 API 호출이나 메시지 출력
집계 테이블 갱신 - 요약 데이터를 별도 테이블로 관리
우리는 이중에서 변경이력을 남기는 로직을 추가해 보겠습니다. Behavior Definition에서 Travel쪽에 with additional save를 추가합니다. 그리고 Method를 생성합니다.

Handler Class가 아닌 Save Class에 Method가 생성된걸 볼 수 있습니다. Method명에 마우스를 클릭해 파라미터를 살펴보겠습니다. create, update, delete로 나눠져 있는걸 보니 데이터를 생성할때는 create로 수정할때는 update로 삭제할때는 delete로 값이 들어오는 것 같습니다. [derived type..]을 클릭해 보겠습니다.

생성할때 입력한 Travel정보가 zever_m_travel_i 파라미터로 들어올 것 같습니다. 그럼 Booking정보는 어떻게 받아야 할까요?
우리가 Behavior Definition에 with additional save를 Travel 쪽에만 추가를 해줘서 그렇습니다.

Behavior Definition으로 이동해서 Booking쪽에도 with additional save를 추가해 줍니다. 그러고 나서 파라미터를 다시 확인해 보겠습니다.

Travel과 Booking에 대한 파라미터가 둘다 보입니다.

아니면 Travel과 Booking쪽에 추가했던 with additional save를 모두 삭제하고 상단에 추가해 주면 모든 Entity에 additional save가 적용이 됩니다. 이번 실습에서는 아래와 같이 가장 상단에 추가하고 진행하겠습니다.

Log 저장을 위한 테이블 2개를 생성합니다.
Key필드로 sysuuid_x16 타입의 log_id를 추가했고 나머지 필드는 기존 Travel과 Booking 테이블의 필드를 그대로 가져왔습니다.
< ZEVER_TRAVEL_L >
@EndUserText.label : 'Travel Log Table'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zever_travel_l {
key client : abap.clnt not null;
key log_id : sysuuid_x16 not null;
operation : abap.char(20);
travel_id : /dmo/travel_id not null;
agency_id : /dmo/agency_id;
customer_id : /dmo/customer_id;
begin_date : /dmo/begin_date;
end_date : /dmo/end_date;
@Semantics.amount.currencyCode : 'zever_travel_l.currency_code'
booking_fee : /dmo/booking_fee;
@Semantics.amount.currencyCode : 'zever_travel_l.currency_code'
total_price : /dmo/total_price;
currency_code : /dmo/currency_code;
description : /dmo/description;
status : /dmo/travel_status;
createdby : syuname;
createdat : timestampl;
}
< ZEVER_BOOKING_L >
@EndUserText.label : 'Booking Log Table'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zever_booking_l {
key client : abap.clnt not null;
key log_id : sysuuid_x16 not null;
operation : abap.char(20);
travel_id : /dmo/travel_id not null;
booking_id : /dmo/booking_id not null;
booking_date : /dmo/booking_date;
customer_id : /dmo/customer_id;
carrier_id : /dmo/carrier_id;
connection_id : /dmo/connection_id;
flight_date : /dmo/flight_date;
@Semantics.amount.currencyCode : 'zever_booking_l.currency_code'
flight_price : /dmo/flight_price;
currency_code : /dmo/currency_code;
createdby : syuname;
createdat : timestampl;
}
Log 테이블을 생성하고 save_modified 메소드에 로직을 구현해 보겠습니다. 첫번째로 데이터를 생성했을때의 로직입니다.
소스가 길어서 그렇지 어렵지 않습니다.
1. 저장을 위한 Internal Table을 선언합니다.
2. Travel정보가 생성되면 저장시점에 create-zever_m_travel_i로 값이 들어옵니다. 그 값을 Log저장을 위한 내부테이블
lt_travel_log에 값을 넣습니다. Operation필드에는 'CREATE'를 넣어주고 log_id필드에는
cl_system_uuid=>create_uuid_x16_static( ) 메소드를 사용하여 값을 넣어줍니다.
3. Booking정보가 생성되면 저장시점에 create-zever_m_booking_i로 값이 들어옵니다. 그 값을 Log저장을 위한 내부테이블
lt_booking_log에 값을 넣습니다. Booking도 마찬가지로 Operation필드에는 'CREATE'를 넣어주고 log_id필드에는
cl_system_uuid=>create_uuid_x16_static( ) 메소드를 사용하여 값을 넣어줍니다.

다음은 데이터를 수정했을때의 로직입니다.
4. Travel정보가 변경되면 저장시점에 update-zever_m_travel_i로 값이 들어옵니다. Operation 필드에 'UPDATE'라고
입력하는것 외에 생성때와 로직은 동일합니다.
5. Booking정보가 변경되면 저장시점에 update-zever_m_booking_i로 값이 들어옵니다. Booking도 마찬가지로 Operation
필드에 'UPDATE'라고 입력하는것 외에 생성때와 로직은 동일합니다.

마지막으로 데이터를 삭제했을때의 로직입니다. 삭제때는 우리가 받는 파라미터에 모든필드의 값이 넘어오지 않고 오직 Key필드만 넘어옵니다. 그래서 Log테이블에도 Entity의 Key필드만 저장되도록 했습니다.
6. Travel정보가 삭제되면 저장시점에 delete-zever_m_travel_i로 값이 들어옵니다. Operation 필드에 'DELETE'라고
입력하는것 외에 생성때와 로직은 동일합니다.
7. Booking정보가 삭제되면 저장시점에 delete-zever_m_booking_i로 값이 들어옵니다. Booking도 마찬가지로 Operation
필드에 'DELETE'라고 입력하는것 외에 생성때와 로직은 동일합니다.
8. 생성 or 수정 or 삭제가 되어 내부테이블에 값이 존재하면 실제 Log테이블에 데이터를 저장합니다.

이제 테스트를 해보겠습니다. App을 실행한 후 신규 Travel ID를 생성해 보겠습니다. Booking정보는 10번, 20번 두개를 추가하고 저장버튼을 클릭합니다.

ZEVER_TRAVEL_L 테이블입니다. 한건이 저장되었습니다.

ZEVER_BOOKING_L 테이블입니다. 두건이 저장되었습니다.
수정 및 삭제도 한번씩 해보면서 Log가 잘 저장되는지 테스트 해보시기 바랍니다.

Unmanaged Save
Unmanaged Save는 RAP 객체의 비즈니스 로직(Create, Update, Delete)은 RAP 프레임워크가 표준관리(Managed) 방식으로 처리하도록 두되 최종 DB저장(Save Phase) 작업만 개발자가 직접 ABAP 코드로 제어하는 방식입니다.
Managed 프레임워크의 편리함을 누리면서도 저장 시점에는 표준 BAPI를 사용해야 하거나 데이터를 여러 DB에 나눠서 저장해야 하는 상황에서 사용할 수 있습니다.
그리고 만약 특정 Entity는 CUD기능이 필요없다면 Unmanaged save를 추가하고 DB저장로직을 구현하지 않아도 됩니다.
이번 실습에서는 Booking쪽만 Unmanaged save를 이용해서 DB에 직접 Create, Update, Delete하는 로직을 구현해 보겠습니다. Behavior Definition에서 Booking쪽에 with unmanaged save를 추가해 줍니다. unmanaged save를 사용하면 persistent table을 지정할 수 없기때문에 persistent table 부분은 주석처리 해줍니다.

로직을 구현해 보겠습니다. Unmanaged Save도 Additional Save때와 마찬가지로 save_modified 메소드에 로직을 구현합니다.
Additional Save때 구현했던 로직 사이에 Unmanaged Save 로직을 추가해도 되지만 이번 실습에서는 Unmanaged Save로직은 메소드 하단에 따로 추가하는걸로 하겠습니다.
1. Booking정보 Create, Update, Delete를 위한 내부테이블을 선언합니다.
2. Booking정보 생성시 넘어온 정보를 내부테이블에 저장합니다.

3. Booking정보 수정시에 넘어온 정보를 내부테이블에 저장합니다. Update시에는 수정된 필드만 값이 들어오고 나머지 필드는
빈값으로 넘어오기 때문에 %control의 정보로 수정된 필드를 파악한 후 저장해줘야 합니다. %contorl값이 "01"이면 수정된
필드라고 판단하시면 됩니다.
4. Booking정보 삭제시에 넘어온 정보를 내부테이블에 저장합니다.
5. 각 내부테이블별로 값이 존재할때 MODIFY, UPDATE, DELETE 구문을 수행해 줍니다.

여기까지 해놓고 App을 실행해서 실제로 Booking정보 생성,수정,삭제가 잘 되는지 테스트 해보시면 됩니다.
아래는 save_modified의 전체소스 입니다.
METHOD save_modified.
DATA : lt_travel_log TYPE TABLE OF zever_travel_l,
lt_booking_log TYPE TABLE OF zever_booking_l.
*- CREATE
IF create-zever_m_travel_i IS NOT INITIAL.
LOOP AT create-zever_m_travel_i INTO DATA(ls_create_travel).
APPEND INITIAL LINE TO lt_travel_log ASSIGNING FIELD-SYMBOL(<fs_travel_log>).
<fs_travel_log>-operation = 'CREATE'.
<fs_travel_log>-travel_id = ls_create_travel-TravelId.
<fs_travel_log>-agency_id = ls_create_travel-AgencyId.
<fs_travel_log>-customer_id = ls_create_travel-CustomerId.
<fs_travel_log>-begin_date = ls_create_travel-BeginDate.
<fs_travel_log>-end_date = ls_create_travel-EndDate.
<fs_travel_log>-booking_fee = ls_create_travel-BookingFee.
<fs_travel_log>-total_price = ls_create_travel-TotalPrice.
<fs_travel_log>-currency_code = ls_create_travel-CurrencyCode.
<fs_travel_log>-description = ls_create_travel-Description.
<fs_travel_log>-status = ls_create_travel-Status.
<fs_travel_log>-createdby = cl_abap_context_info=>get_user_technical_name( ).
GET TIME STAMP FIELD <fs_travel_log>-createdat.
TRY.
<fs_travel_log>-log_id = cl_system_uuid=>create_uuid_x16_static( ).
CATCH cx_uuid_error.
ENDTRY.
ENDLOOP.
ENDIF.
IF create-zever_m_booking_i IS NOT INITIAL.
LOOP AT create-zever_m_booking_i INTO DATA(ls_create_booking).
APPEND INITIAL LINE TO lt_booking_log ASSIGNING FIELD-SYMBOL(<fs_booking_log>).
<fs_booking_log>-operation = 'CREATE'.
<fs_booking_log>-travel_id = ls_create_booking-TravelId.
<fs_booking_log>-booking_id = ls_create_booking-BookingId.
<fs_booking_log>-booking_date = ls_create_booking-BookingDate.
<fs_booking_log>-customer_id = ls_create_booking-CustomerId.
<fs_booking_log>-carrier_id = ls_create_booking-CarrierId.
<fs_booking_log>-connection_id = ls_create_booking-ConnectionId.
<fs_booking_log>-flight_date = ls_create_booking-FlightDate.
<fs_booking_log>-flight_price = ls_create_booking-FlightPrice.
<fs_booking_log>-currency_code = ls_create_booking-CurrencyCode.
<fs_booking_log>-createdby = cl_abap_context_info=>get_user_technical_name( ).
GET TIME STAMP FIELD <fs_booking_log>-createdat.
TRY.
<fs_booking_log>-log_id = cl_system_uuid=>create_uuid_x16_static( ).
CATCH cx_uuid_error.
ENDTRY.
ENDLOOP.
ENDIF.
*- UPDATE
IF update-zever_m_travel_i IS NOT INITIAL.
LOOP AT update-zever_m_travel_i INTO DATA(ls_update_travel).
APPEND INITIAL LINE TO lt_travel_log ASSIGNING <fs_travel_log>.
<fs_travel_log>-operation = 'UPDATE'.
<fs_travel_log>-travel_id = ls_update_travel-TravelId.
<fs_travel_log>-agency_id = ls_update_travel-AgencyId.
<fs_travel_log>-customer_id = ls_update_travel-CustomerId.
<fs_travel_log>-begin_date = ls_update_travel-BeginDate.
<fs_travel_log>-end_date = ls_update_travel-EndDate.
<fs_travel_log>-booking_fee = ls_update_travel-BookingFee.
<fs_travel_log>-total_price = ls_update_travel-TotalPrice.
<fs_travel_log>-currency_code = ls_update_travel-CurrencyCode.
<fs_travel_log>-description = ls_update_travel-Description.
<fs_travel_log>-status = ls_update_travel-Status.
<fs_travel_log>-createdby = cl_abap_context_info=>get_user_technical_name( ).
GET TIME STAMP FIELD <fs_travel_log>-createdat.
TRY.
<fs_travel_log>-log_id = cl_system_uuid=>create_uuid_x16_static( ).
CATCH cx_uuid_error.
ENDTRY.
ENDLOOP.
ENDIF.
IF update-zever_m_booking_i IS NOT INITIAL.
LOOP AT update-zever_m_booking_i INTO DATA(ls_update_booking).
APPEND INITIAL LINE TO lt_booking_log ASSIGNING <fs_booking_log>.
<fs_booking_log>-operation = 'UPDATE'.
<fs_booking_log>-travel_id = ls_update_booking-TravelId.
<fs_booking_log>-booking_id = ls_update_booking-BookingId.
<fs_booking_log>-booking_date = ls_update_booking-BookingDate.
<fs_booking_log>-customer_id = ls_update_booking-CustomerId.
<fs_booking_log>-carrier_id = ls_update_booking-CarrierId.
<fs_booking_log>-connection_id = ls_update_booking-ConnectionId.
<fs_booking_log>-flight_date = ls_update_booking-FlightDate.
<fs_booking_log>-flight_price = ls_update_booking-FlightPrice.
<fs_booking_log>-currency_code = ls_update_booking-CurrencyCode.
<fs_booking_log>-createdby = cl_abap_context_info=>get_user_technical_name( ).
GET TIME STAMP FIELD <fs_booking_log>-createdat.
TRY.
<fs_booking_log>-log_id = cl_system_uuid=>create_uuid_x16_static( ).
CATCH cx_uuid_error.
ENDTRY.
ENDLOOP.
ENDIF.
*- DELETE
IF delete-zever_m_travel_i IS NOT INITIAL.
LOOP AT delete-zever_m_travel_i INTO DATA(ls_delete_travel).
APPEND INITIAL LINE TO lt_travel_log ASSIGNING <fs_travel_log>.
<fs_travel_log>-operation = 'DELETE'.
<fs_travel_log>-travel_id = ls_delete_travel-TravelId.
<fs_travel_log>-createdby = cl_abap_context_info=>get_user_technical_name( ).
GET TIME STAMP FIELD <fs_travel_log>-createdat.
TRY.
<fs_travel_log>-log_id = cl_system_uuid=>create_uuid_x16_static( ).
CATCH cx_uuid_error.
ENDTRY.
ENDLOOP.
ENDIF.
IF delete-zever_m_booking_i IS NOT INITIAL.
LOOP AT delete-zever_m_booking_i INTO DATA(ls_delete_booking).
APPEND INITIAL LINE TO lt_booking_log ASSIGNING <fs_booking_log>.
<fs_booking_log>-operation = 'DELETE'.
<fs_booking_log>-travel_id = ls_delete_booking-TravelId.
<fs_booking_log>-booking_id = ls_delete_booking-BookingId.
<fs_booking_log>-createdby = cl_abap_context_info=>get_user_technical_name( ).
GET TIME STAMP FIELD <fs_booking_log>-createdat.
TRY.
<fs_booking_log>-log_id = cl_system_uuid=>create_uuid_x16_static( ).
CATCH cx_uuid_error.
ENDTRY.
ENDLOOP.
ENDIF.
IF lt_travel_log IS NOT INITIAL.
MODIFY zever_travel_l FROM TABLE @lt_travel_log.
ENDIF.
IF lt_booking_log IS NOT INITIAL.
MODIFY zever_booking_l FROM TABLE @lt_booking_log.
ENDIF.
*- UNMANAGED SAVE
TYPES: ty_booking_create TYPE /dmo/booking,
ty_booking_update TYPE /dmo/booking WITH INDICATORS control TYPE abap_bool,
ty_booking_delete TYPE /dmo/booking.
DATA : lt_booking_create TYPE TABLE OF ty_booking_create,
lt_booking_update TYPE TABLE OF ty_booking_update,
lt_booking_delete TYPE TABLE OF ty_booking_delete.
*- CREATE
IF create-zever_m_booking_i IS NOT INITIAL.
LOOP AT create-zever_m_booking_i INTO ls_create_booking.
APPEND INITIAL LINE TO lt_booking_create ASSIGNING FIELD-SYMBOL(<fs_booking_create>).
<fs_booking_create>-travel_id = ls_create_booking-TravelId.
<fs_booking_create>-booking_id = ls_create_booking-BookingId.
<fs_booking_create>-booking_date = ls_create_booking-BookingDate.
<fs_booking_create>-customer_id = ls_create_booking-CustomerId.
<fs_booking_create>-carrier_id = ls_create_booking-CarrierId.
<fs_booking_create>-connection_id = ls_create_booking-ConnectionId.
<fs_booking_create>-flight_date = ls_create_booking-FlightDate.
<fs_booking_create>-flight_price = ls_create_booking-FlightPrice.
<fs_booking_create>-currency_code = ls_create_booking-CurrencyCode.
ENDLOOP.
ENDIF.
*- UPDATE
IF update-zever_m_booking_i IS NOT INITIAL.
LOOP AT update-zever_m_booking_i INTO ls_update_booking.
APPEND INITIAL LINE TO lt_booking_update ASSIGNING FIELD-SYMBOL(<fs_booking_update>).
<fs_booking_update>-travel_id = ls_update_booking-TravelId.
<fs_booking_update>-booking_id = ls_update_booking-BookingId.
<fs_booking_update>-booking_date = ls_update_booking-BookingDate.
<fs_booking_update>-customer_id = ls_update_booking-CustomerId.
<fs_booking_update>-carrier_id = ls_update_booking-CarrierId.
<fs_booking_update>-connection_id = ls_update_booking-ConnectionId.
<fs_booking_update>-flight_date = ls_update_booking-FlightDate.
<fs_booking_update>-flight_price = ls_update_booking-FlightPrice.
<fs_booking_update>-currency_code = ls_update_booking-CurrencyCode.
<fs_booking_update>-control-booking_date = COND #( WHEN ls_update_booking-%control-BookingDate = '01' THEN 'X' ELSE space ).
<fs_booking_update>-control-customer_id = COND #( WHEN ls_update_booking-%control-CustomerId = '01' THEN 'X' ELSE space ).
<fs_booking_update>-control-carrier_id = COND #( WHEN ls_update_booking-%control-CarrierId = '01' THEN 'X' ELSE space ).
<fs_booking_update>-control-connection_id = COND #( WHEN ls_update_booking-%control-ConnectionId = '01' THEN 'X' ELSE space ).
<fs_booking_update>-control-flight_date = COND #( WHEN ls_update_booking-%control-FlightDate = '01' THEN 'X' ELSE space ).
<fs_booking_update>-control-flight_price = COND #( WHEN ls_update_booking-%control-FlightPrice = '01' THEN 'X' ELSE space ).
<fs_booking_update>-control-currency_code = COND #( WHEN ls_update_booking-%control-CurrencyCode = '01' THEN 'X' ELSE space ).
ENDLOOP.
ENDIF.
*- DELETE
IF delete-zever_m_booking_i IS NOT INITIAL.
LOOP AT delete-zever_m_booking_i INTO ls_delete_booking.
APPEND INITIAL LINE TO lt_booking_delete ASSIGNING FIELD-SYMBOL(<fs_booking_delete>).
<fs_booking_delete>-travel_id = ls_delete_booking-TravelId.
<fs_booking_delete>-booking_id = ls_delete_booking-BookingId.
ENDLOOP.
ENDIF.
IF lt_booking_create IS NOT INITIAL.
MODIFY /dmo/booking FROM TABLE @lt_booking_create.
ENDIF.
IF lt_booking_update IS NOT INITIAL.
UPDATE /dmo/booking FROM TABLE @lt_booking_update INDICATORS SET STRUCTURE control.
ENDIF.
IF lt_booking_delete IS NOT INITIAL.
DELETE /dmo/booking FROM TABLE @lt_booking_delete.
ENDIF.
ENDMETHOD.'RAP' 카테고리의 다른 글
| RAP 18 - MANAGED REPORT : CUSTOM ENTITY (0) | 2026.09.12 |
|---|---|
| RAP 16 - MANAGED REPORT : DETERMINATION (0) | 2026.08.31 |
| RAP 15 - MANAGED REPORT : FEATURE CONTROL (0) | 2026.08.24 |
| RAP 14 - MANAGED REPORT : ACTION (0) | 2026.08.04 |
| RAP 13 - MANAGED REPORT : VALIDATION (0) | 2026.08.03 |