Please read this introductory page explaining why we need to know how to implement OData $batch processing with Content ID.
In this H2G, we're going through step-by-step implementation of building OData $batch services with a a set of simple header & item ABAP database tables.
Table of Contents (Estimated Time)
- Define simple header & item tables (10 mins.)
- Creating association (= navigation resource path) (5 mins.)
- Build CRUD services for each entity (20 mins.)
- Enable $batch (changeset_begin/changeset_end) (10 mins.)
- The $batch implementation to handle Content ID (changeset_process) (20 mins.)
- Test it
- Appendix: HeaderSet & ItemSet code
- Appendix: changeset_process code
Define simple header & item tables
Let's create a set of header & item database tables in SE11. For the sake of the simplicity, here's a very simple design to demonstrate the idea.
(Note: In a real use case, add "MANDT" field for both zheader and zitem)
ZHEADER - ID is a key and TEXT is a small text value.
ZITEM - PARENT_ID (= ID of ZHEADER) and ID are the keys and TEXT is a small text value.
Once you created the ZHEADER and ZITEM database tables, go to the tx SEGW and create a project (this case the name is Z_CONTENTID). Right click on the Data Model node and select Import > DDIC Structure.
Type in the Name field - "Header" - this is the exact name of the OData entity. And ABAP Structure is zheader.
Select all the field to expose as OData.
Tick in ID row as "Is Key".
You created Header entity, the next one is Item. Right click on the Data Model node and select Import > DDIC Structure.
Type in the Name field - "Item" - And ABAP Structure is zitem.
Select all the field to expose as OData.
Tick in PARENT_ID and ID row as "Is Key".
You have created HeaderSet and ItemSet entities!
Creating association (= navigation resource path)
We're going to create a navigation resource path from Header to Item. More precisely - once we configure it, a Header entity (parent) can follow its Item entities (children) by using the navigation path link such as HeaderSet('0000000001')/ToItems .
Right click on Associations > Create.
Name it as "HeaderItemAssociation" and select Entity Type Name for Header and Item. Be sure to set the Cardinality value as 1:n. You set the Navigation Property value as "ToItems" that will be displayed in the OData payload.
Select Dependent Property value as "ParentId" - now Header and Item is linked together with the key fields.
Confirm the default value and save it.
Now you have done with the Navigation Properties that navigates from Header to Item(s). The relationship is going to be calculated by the GW framework and rendered in an OData payload. (ex. HeaderSet('0000000001')/ToItems will return relevant ItemSet entities)
Build CRUD services for each entity
Time to build CRUD operations for each entity. First off, let's generate the base code by selecting Generate Runtime.
And - register the OData services by clicking on Service Maintenance > (Your GW) > Register.
Now your OData services became callable from HTTP client.
As described in the prerequisite section, you should have a skillset of building basic CRUD OData services with SAP Gateway Workbench (tx code: SEGW) as explained here: #1 - OData CRUD Crash Course - Getting ready with offline store .
At least we need Query & Read operation for header and item respectively - we will use them during the test step later. The code is written in the appendix.
Enable $batch (changeset_begin/changeset_end)
Note: - the official SAP Gateway documentation about $batch processing about changeset_begin/end/process.
Let's implement $batch specific methods. Redefine CHANGESET_BEGIN of "/IWBEP/IF_MGW_APPL_SRV_RUNTIME".
01 METHOD /iwbep/if_mgw_appl_srv_runtime~changeset_begin. 02 03 DATA ls_operation_info LIKE LINE OF it_operation_info. 04 05* set cv_defer_mode as TRUE to call changeset_process method 06 LOOP AT it_operation_info INTO ls_operation_info. 07 IF ls_operation_info-content_id IS NOT INITIAL OR 08 ls_operation_info-content_id_ref IS NOT INITIAL. 09 cv_defer_mode = abap_true. 10 ENDIF. 11 ENDLOOP. 12 13 ENDMETHOD.
The very important thing we need to understand here is once you redefine the changeset_begin, it will start the Logical Unit of Work (LUW) in ABAP that essentially means a transaction scope. And by setting the cv_defer_mode as TRUE, it will call the changeset_process method that will be explained in the next section.
The transaction scope means that in case of any failure happens during the changeset_process, all the database update will be rolled back.
Redefine CHANGESET_END of "/IWBEP/IF_MGW_APPL_SRV_RUNTIME".
01 METHOD /iwbep/if_mgw_appl_srv_runtime~changeset_end. 02 03 ENDMETHOD.
Once the logic reaches the changeset_end, the COMMIT WORK will be automatically issued by the SAP Gateway framework.
The $batch implementation to handle Content ID (changeset_process)
Note: - the official SAP Gateway documentation about $batch processing about changeset_begin/end/process.
Redefine CHANGESET_PROCESS of "/IWBEP/IF_MGW_APPL_SRV_RUNTIME".
What the implementation needs to do is as follows:
- Obtain a changeset as an internal table "it_changeset_request".
- Loop it - and pick up each change request (= create request of each entity).
- When it is Header entity - create a header entity.
- When it is Item entity - create an item entity.
That's all. It is fairly simple - and I need to add the detailed explanation during the step 4. As we know the parent has Content ID, so the child needs to look up the Content ID and replace it with the finalized parent key. Here's the code to implement the step 4:
Note: - the entire code is in the appendix.
Note: - this is demonstrating the parent-child relationship, however you can implement any level of relationship (such as parent-child-grandchild-..) by using Content ID.
056 WHEN 'Item'. 057* Item entity 058 ls_changeset_request-entry_provider->read_entry_data( IMPORTING es_data = ls_item ). 059 060 IF ls_changeset_request-content_id_ref IS NOT INITIAL. 061* look up the parent by content id 062 READ TABLE it_changeset_request INTO ls_changeset_req_parent WITH KEY content_id = ls_changeset_request-content_id_ref. 063 064 IF ( sy-subrc = 0 ). 065 READ TABLE ct_changeset_response INTO ls_changeset_resp_parent WITH TABLE KEY operation_no = ls_changeset_req_parent-operation_no. 066 IF ( sy-subrc = 0 ). 067 ASSIGN ls_changeset_resp_parent-entity_data->* TO . 068 IF <ls_header>-id IS NOT INITIAL. 069* header key value is obtained for items 070 ls_item-parent_id = <ls_header>-id. 071 072 INSERT INTO zitem VALUES ls_item. 073 074 IF ( sy-subrc = 0 ). 075* entity inserted 076 copy_data_to_ref( 077 EXPORTING 078 is_data = ls_item 079 CHANGING 080 cr_data = ls_changeset_response-entity_data ). 081 ELSE. 082* entity alredy exists - $batch will be rolled back 083 CONCATENATE lv_entity_type 084 '(''' 085 ls_item-parent_id 086 ',''' 087 ls_item-id 088 ''')' 089 INTO lv_error_entity. 090 RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception 091 EXPORTING 092 textid = /iwbep/cx_mgw_busi_exception=>resource_duplicate 093 entity_type = lv_error_entity. 094 ENDIF. 095 096 ls_changeset_response-operation_no = ls_changeset_request-operation_no. 097 INSERT ls_changeset_response INTO TABLE ct_changeset_response. 098 099 ENDIF. 100 ENDIF. 101 ENDIF. 102 ENDIF. 103 ENDCASE. "end of lv_entity_type
#056 - case block if the entity is "Item".
#058 - read the payload data of Item entity.
#060 - check if the Item entity has "Content ID Reference" value (= the value should exist if the POST request is done with the $ ID)
#062 - if the Item has the reference value - the parent should have the value too.
#064 - should be TRUE if a related parent is found.
#065 - ct_changeset_response should contain a parent. Pick up the parent value - it should contain the finalized key.
#067 - load the parent data onto the ls_header structure.
#068 - the Header entity should contain the finalized key named "id".
#070 - set the parent's id to the child's "parent_id" field. This links both the parent and the child together.
#072 - insert the value in the zitem ABAP database table.
#074 to 080 - insert goes fine - returning the OData entity for "HTTP 201 Created".
#081 to 094 - insert failed - throw the Duplicate exception. The entire $batch request will roll back.
#096 to 097 - prepare the changeset response.
I believe you got a very clear idea how Content ID and Content ID reference value work in the $batch processing to glue the parent and children together.
Test it
Are you ready for testing the $batch service? That's the next step.
Appendix: HeaderSet & ItemSet code
HeaderSet entity CRUD implementation:
Naming convention: l - local scope t - table s - structure v - variable o - object 01 METHOD headerset_get_entityset. 02 03 DATA: lt_entityset TYPE TABLE OF zheader. 04 05 SELECT * FROM zheader INTO TABLE lt_entityset. 06 07 et_entityset = lt_entityset. 08 09 ENDMETHOD.
01 METHOD headerset_get_entity. 02 03 DATA: ls_key_tab TYPE /iwbep/s_mgw_name_value_pair, 04 lv_id TYPE vbeln, 05 ls_entityset TYPE zheader. 06 07 READ TABLE it_key_tab INTO ls_key_tab INDEX 1. 08 UNPACK ls_key_tab-value TO lv_id. "Converts 10 digits value 09 10 SELECT SINGLE * FROM zheader INTO ls_entityset WHERE id = lv_id. 11 12 er_entity = ls_entityset. 13 14 ENDMETHOD.
01 METHOD headerset_create_entity. 02 03 DATA: ls_entityset TYPE zheader, 04 lv_error_entity TYPE string. 05 06 io_data_provider->read_entry_data( IMPORTING es_data = ls_entityset ). 07 08 INSERT INTO zheader VALUES ls_entityset. 09 10 IF ( sy-subrc = 0 ). 11* entity inserted 12 er_entity = ls_entityset. 13 ELSE. 14* entity alredy exists 15 CONCATENATE iv_entity_name 16 '(''' 17 ls_entityset-id 18 ''')' 19 INTO lv_error_entity. 20 RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception 21 EXPORTING 22 textid = /iwbep/cx_mgw_busi_exception=>resource_duplicate 23 entity_type = lv_error_entity. 24 ENDIF. 25 26 ENDMETHOD.
01 METHOD headerset_update_entity. 02 03 DATA: ls_entityset TYPE zheader, 04 ls_key_tab TYPE /iwbep/s_mgw_name_value_pair, 05 lv_id TYPE vbeln, 06 lv_error_entity TYPE string. 07 08 io_data_provider->read_entry_data( IMPORTING es_data = ls_entityset ). 09 10* key is id 11 READ TABLE it_key_tab INTO ls_key_tab INDEX 1. 12 UNPACK ls_key_tab-value TO lv_id. 13 14* make sure the value matches with the one in OData payload 15 IF lv_id EQ ls_entityset-id. 16 17* new data 18 UPDATE zheader FROM ls_entityset. 19 20 IF ( sy-subrc = 0 ). 21* entity found and updated 22 er_entity = ls_entityset. 23 ELSE. 24* entity not found 25 CONCATENATE iv_entity_name 26 '(''' 27 ls_key_tab-value 28 ''')' 29 INTO lv_error_entity. 30 RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception 31 EXPORTING 32 textid = /iwbep/cx_mgw_busi_exception=>resource_not_found 33 entity_type = lv_error_entity. 34 ENDIF. 35 ENDIF. 36 37 ENDMETHOD.
01 METHOD headerset_delete_entity. 02 03 DATA: ls_entityset TYPE zheader, 04 ls_key_tab TYPE /iwbep/s_mgw_name_value_pair, 05 lv_id TYPE vbeln, 06 lv_error_entity TYPE string. 07 08* key is id 09 READ TABLE it_key_tab INTO ls_key_tab INDEX 1. 10 UNPACK ls_key_tab-value TO lv_id. 11 12 DELETE FROM zheader WHERE id = lv_id. 13 14 IF ( sy-subrc = 0 ). 15* delete completed 16 ELSE. 17* entity not found 18 CONCATENATE iv_entity_name 19 '(''' 20 ls_key_tab-value 21 ''')' 22 INTO lv_error_entity. 23 RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception 24 EXPORTING 25 textid = /iwbep/cx_mgw_busi_exception=>resource_not_found 26 entity_type = lv_error_entity. 27 ENDIF. 28 29 ENDMETHOD.
ItemSet entity CRUD implementation:
01 METHOD itemset_get_entityset. 02 03 DATA: lt_entityset TYPE TABLE OF zitem. 04 05 SELECT * FROM zitem INTO TABLE lt_entityset. 06 07 et_entityset = lt_entityset. 08 09 ENDMETHOD.
01 METHOD itemset_get_entity. 02 03 DATA: ls_key_tab TYPE /iwbep/s_mgw_name_value_pair, 04 lv_parent_id TYPE vbeln, 05 lv_id TYPE posnr, 06 ls_entityset TYPE zitem. 07 08 READ TABLE it_key_tab INTO ls_key_tab INDEX 1. 09 UNPACK ls_key_tab-value TO lv_parent_id. 10 READ TABLE it_key_tab INTO ls_key_tab INDEX 2. 11 UNPACK ls_key_tab-value TO lv_id. 12 13 SELECT SINGLE * FROM zitem INTO ls_entityset WHERE parent_id = lv_parent_id AND id = lv_id. 14 15 er_entity = ls_entityset. 16 17 ENDMETHOD.
01 METHOD itemset_create_entity. 02 03 DATA: ls_entityset TYPE zitem, 04 lv_error_entity TYPE string. 05 06 io_data_provider->read_entry_data( IMPORTING es_data = ls_entityset ). 07 08 INSERT INTO zitem VALUES ls_entityset. 09 10 IF ( sy-subrc = 0 ). 11* entity inserted 12 er_entity = ls_entityset. 13 ELSE. 14* entity alredy exists 15 CONCATENATE iv_entity_name 16 '(''' 17 ls_entityset-parent_id 18 ',''' 19 ls_entityset-id 20 ''')' 21 INTO lv_error_entity. 22 RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception 23 EXPORTING 24 textid = /iwbep/cx_mgw_busi_exception=>resource_duplicate 25 entity_type = lv_error_entity. 26 ENDIF. 27 28 ENDMETHOD.
01 METHOD itemset_update_entity. 02 03 DATA: ls_entityset TYPE zitem, 04 ls_key_tab TYPE /iwbep/s_mgw_name_value_pair, 05 lv_parent_id TYPE vbeln, 06 lv_id TYPE posnr, 07 lv_error_entity TYPE string. 08 09 io_data_provider->read_entry_data( IMPORTING es_data = ls_entityset ). 10 11* key is parent_id and id 12 READ TABLE it_key_tab INTO ls_key_tab INDEX 1. 13 UNPACK ls_key_tab-value TO lv_parent_id. 14 READ TABLE it_key_tab INTO ls_key_tab INDEX 2. 15 UNPACK ls_key_tab-value TO lv_id. 16 17* make sure the value matches with the one in OData payload 18 IF lv_parent_id EQ ls_entityset-parent_id AND lv_id EQ ls_entityset-id. 19 20* new data 21 UPDATE zitem FROM ls_entityset. 22 23 IF ( sy-subrc = 0 ). 24* entity found and updated 25 er_entity = ls_entityset. 26 ELSE. 27* entity not found 28 CONCATENATE iv_entity_name 29 '(''' 30 lv_parent_id 31 ',''' 32 lv_id 33 ''')' 34 INTO lv_error_entity. 35 RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception 36 EXPORTING 37 textid = /iwbep/cx_mgw_busi_exception=>resource_not_found 38 entity_type = lv_error_entity. 39 ENDIF. 40 ENDIF. 41 42 ENDMETHOD.
01 METHOD itemset_delete_entity. 02 03 DATA: ls_entityset TYPE zitem, 04 ls_key_tab TYPE /iwbep/s_mgw_name_value_pair, 05 lv_parent_id TYPE vbeln, 06 lv_id TYPE posnr, 07 lv_error_entity TYPE string. 08 09* key is parent_id and id 10 READ TABLE it_key_tab INTO ls_key_tab INDEX 1. 11 UNPACK ls_key_tab-value TO lv_parent_id. 12 READ TABLE it_key_tab INTO ls_key_tab INDEX 2. 13 UNPACK ls_key_tab-value TO lv_id. 14 15 DELETE FROM zitem WHERE parent_id = lv_parent_id AND id = lv_id. 16 17 IF ( sy-subrc = 0 ). 18* delete completed 19 ELSE. 20* entity not found 21 CONCATENATE iv_entity_name 22 '(''' 23 lv_parent_id 24 ',''' 25 lv_id 26 ''')' 27 INTO lv_error_entity. 28 RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception 29 EXPORTING 30 textid = /iwbep/cx_mgw_busi_exception=>resource_not_found 31 entity_type = lv_error_entity. 32 ENDIF. 33 34 ENDMETHOD.
Appendix: changeset_process code
001 METHOD /iwbep/if_mgw_appl_srv_runtime~changeset_process. 002 003 DATA: 004 ls_changeset_request TYPE /iwbep/if_mgw_appl_types=>ty_s_changeset_request, 005 ls_changeset_req_parent TYPE /iwbep/if_mgw_appl_types=>ty_s_changeset_request, 006 lo_create_context TYPE REF TO /iwbep/if_mgw_req_entity_c, 007 lv_entity_type TYPE string, 008 ls_changeset_response TYPE /iwbep/if_mgw_appl_types=>ty_s_changeset_response, 009 ls_changeset_resp_parent TYPE /iwbep/if_mgw_appl_types=>ty_s_changeset_response, 010 ls_header TYPE zcl_zcontentid_mpc=>ts_header, "replace the mcp class name for your own 011 ls_item TYPE zcl_zcontentid_mpc=>ts_item, "replace the mcp class name for your own 012 lv_error_entity TYPE string. 013 014 FIELD-SYMBOLS: 015 <ls_header> TYPE zcl_zcontentid_mpc=>ts_header. "replace the mcp class name for your own 016 017 LOOP AT it_changeset_request INTO ls_changeset_request. 018 019 lo_create_context ?= ls_changeset_request-request_context. 020 lv_entity_type = lo_create_context->get_entity_type_name( ). 021 022 CASE ls_changeset_request-operation_type. 023 WHEN /iwbep/if_mgw_appl_types=>gcs_operation_type-create_entity. 024* create (HTTP POST) 025 CASE lv_entity_type. 026* which entity? The name is case sensitive 027 WHEN 'Header'. 028* Header entity 029 ls_changeset_request-entry_provider->read_entry_data( IMPORTING es_data = ls_header ). 030 031 INSERT INTO zheader VALUES ls_header. 032 033 IF ( sy-subrc = 0 ). 034* entity inserted 035 copy_data_to_ref( 036 EXPORTING 037 is_data = ls_header 038 CHANGING 039 cr_data = ls_changeset_response-entity_data ). 040 ELSE. 041* entity alredy exists - $batch will be rolled back 042 CONCATENATE lv_entity_type 043 '(''' 044 ls_header-id 045 ''')' 046 INTO lv_error_entity. 047 RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception 048 EXPORTING 049 textid = /iwbep/cx_mgw_busi_exception=>resource_duplicate 050 entity_type = lv_error_entity. 051 ENDIF. 052 053 ls_changeset_response-operation_no = ls_changeset_request-operation_no. 054 INSERT ls_changeset_response INTO TABLE ct_changeset_response. 055 056 WHEN 'Item'. 057* Item entity 058 ls_changeset_request-entry_provider->read_entry_data( IMPORTING es_data = ls_item ). 059 060 IF ls_changeset_request-content_id_ref IS NOT INITIAL. 061* look up the parent by content id 062 READ TABLE it_changeset_request INTO ls_changeset_req_parent WITH KEY content_id = ls_changeset_request-content_id_ref. 063 064 IF ( sy-subrc = 0 ). 065 READ TABLE ct_changeset_response INTO ls_changeset_resp_parent WITH TABLE KEY operation_no = ls_changeset_req_parent-operation_no. 066 IF ( sy-subrc = 0 ). 067 ASSIGN ls_changeset_resp_parent-entity_data->* TO <ls_header>. 068 IF <ls_header>-id IS NOT INITIAL. 069* header key value is obtained for items 070 ls_item-parent_id = <ls_header>-id. 071 072 INSERT INTO zitem VALUES ls_item. 073 074 IF ( sy-subrc = 0 ). 075* entity inserted 076 copy_data_to_ref( 077 EXPORTING 078 is_data = ls_item 079 CHANGING 080 cr_data = ls_changeset_response-entity_data ). 081 ELSE. 082* entity alredy exists - $batch will be rolled back 083 CONCATENATE lv_entity_type 084 '(''' 085 ls_item-parent_id 086 ',''' 087 ls_item-id 088 ''')' 089 INTO lv_error_entity. 090 RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception 091 EXPORTING 092 textid = /iwbep/cx_mgw_busi_exception=>resource_duplicate 093 entity_type = lv_error_entity. 094 ENDIF. 095 096 ls_changeset_response-operation_no = ls_changeset_request-operation_no. 097 INSERT ls_changeset_response INTO TABLE ct_changeset_response. 098 099 ENDIF. 100 ENDIF. 101 ENDIF. 102 ENDIF. 103 ENDCASE. "end of lv_entity_type 104 ENDCASE."end of create_entity 105 ENDLOOP. 106 ENDMETHOD.