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
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:
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
30 | |
19 | |
10 | |
10 | |
8 | |
7 | |
7 | |
7 | |
6 | |
6 |