Implementing Data Access in Managed Business Objects

Objective

After completing this lesson, you will be able to implement unmanaged save and additional save.

Save Options in the Behavior Definition

Save options for managed behavior implementation

When you define the behavior for a managed business object, you can choose between three different save options. You make this choice for each entity of the business object, independently for each BO entity. The behavior definition in the example uses a different save option for each of the three entities.

The main syntax differences are as follows:

Managed Save

This is the default option. No specific addition is needed to specify that a BO entity uses the save option. For the managed save option, it is mandatory to specify exactly one persistent table to which the framework writes the data of this BO entity and a field mapping for this database table.

Unmanaged Save

You choose this option for a BO entity by adding the unmanaged save addition to the respective define behavior statement. With the unmanaged save option, it is not allowed to specify a persistent table. However, you can add any number of field mappings to support the manual implementation of the database access.

Additional Save

You choose this option for a BO entity by adding the additional save addition to the respective define behavior statement. For the additional save option, it is mandatory to specify a persistent table and a field mapping for this database table. Also, you can add field mappings for other database tables and structure types to support the implementation of the additional database access.

Syntax variants for the with unmanaged save option or the with additional save option for a BO entity

When you choose the with unmanaged save option or the with additional save option for a BO entity, you can add one or both of the following additions:

and cleanup

If this addition is specified, the cleanup method must be redefined in the local saver class.

With full data

By default, only the values of the key fields and changed fields are handed over to the save_modified method of the saver class. The addition with full data can be used to hand over the full instance data to the save_modified method.

The Saver Class in Managed Implementations

Methods of the saver class in managed implementation scenarios

In the managed implementation scenarios, the saver class can only redefine methods that are executed after the point-of-no-return.

The most important method is the save_modified method that becomes mandatory when one of the BO entities is defined with the with unmanaged save addition or the with additional save addition. The purpose of the save_modified method is similar to the save method in the unmanaged implementation scenario. The main difference is that the save_modified method has importing parameters through which the framework provides the data that must be persisted on the database. While the save method does not have any importing parameters.

The other methods have the same purpose as in the unmanaged scenario and they are called at the same position in the save sequence. However, because in the managed implementation scenario usually the BO runtime maintains the application buffer, the cleanup and cleanup_finalize methods are less important than in the unmanaged scenario.

Note

Redefining the cleanup method requires the and cleanup addition for at least one of the BO entities.

The SAVE_MODIFIED Method

Quick fixes to create the saver class or to add missing redefinitions

When you edit the behavior definition and change the save option for a BO entity, you can invoke quick fixes to create the saver class or to add missing redefinitions for the save_modified and cleanup methods to the existing saver class.

For example, if you add the with unmanaged save addition or the with additional save addition to a define behavior statement for the first time, the syntax check issues a warning that the redefinition of the save_modified method is missing. If you add the and cleanup addition for the first time, the syntax check warns you about the missing redefinition of the cleanup method.

To invoke the quick fixes, use the familiar techniques.

Signature of the SAVE_MODIFIED method

Unlike the save method, which only has response parameters, the save_modified method comes with three importing parameters named create, update, and delete. When the method is called at runtime, the framework fills the parameters with the data from its application buffer, that must be persisted on the database.

All three parameters are deep structures with one component for each BO entity that has a with unmanaged save addition or a with additional save addition. The component types are derived from the respective entity. The components of the create and update parameters are based on the TABLE FOR CHANGE type and contain all elements of the respective entity. The components of the delete parameter use the TABLE FOR KEY type and therefore only contain the primary key values, which is sufficient to identify the entity instances that must be deleted.

A typical implementation of the save_modified method uses field mapping to convert the content of the import parameters to the legacy types. Then, they forward this information to reusable code that encapsulates the database access, for example methods of a global class or function modules.

Caution

Although possible, it is not recommended to implement the modifying ABAP SQL statements directly in the save_modified method.

Implement Unmanaged Save

In this exercise, you reuse existing business logic for flight travel items in your business object. The business object as such remains managed. You only enable unmanaged save for the travel items and replace the managed access to your persistent table with calls to the existing code.

Note

In this exercise, replace ## with your group number.

Solution

Repository Object TypeRepository Object ID
CDS Behavior Definition (Model)/LRN/437F_R_TRAVEL
ABAP Class/LRN/BP_437F_R_TRAVEL

Task 1: Analyze Existing Code

Analyze existing ABAP class /LRN/CL_S4D437_TRITEM.

Steps

  1. Open the source code of ABAP class /LRN/CL_S4D437_TRITEM.

    1. Press Ctrl + Shift + A.

    2. Enter /LRN/CL_S4D437_ as the search string.

    3. Select /LRN/CL_S4D437_TRITEM (Class) from the list and choose OK.

  2. Analyze the public section of the definition part.

    1. Analyze the code between PUBLIC SECTION. and PROTECTED SECTION.

Task 2: Enable Data Access Implementation

Adjust the behavior definition on data model level Z##_R_TRAVEL. For the child entity Z##_R_TravelItem, specify that you want to implement extra coding to be performed during the save sequence. Then make sure that the framework does no longer take care of write access to the persistent table Z##_TRITEM.

Steps

  1. Edit your behavior definition on data model level Z##_R_TRAVEL and locate the define behavior statement for the child entity Z##_R_TravelItem.

    1. For example, you can press Ctrl + F to invoke a search dialog where you search for R_TravelItem.

  2. Specify that you want to implement additional coding to be performed during the save sequence.

    1. Adjust the code as follows:

      Code Snippet
      1234
      define behavior for Z##_R_TravelItem alias Item persistent table z##_tritem with additional save draft table z##_tritem_d
  3. Perform a syntax check.

    1. Press Ctrl + F2.

  4. Do not invoke the related quick fix yet. Instead, comment or remove the previous addition and replace it with the addition that specifies that you want to take care of the persistence of flight travels yourself.

    1. Adjust the code as follows:

      Code Snippet
      1234
      define behavior for Z##_R_TravelItem alias Item persistent table z##_tritem with unmanaged save draft table z##_tritem_d
  5. Perform another syntax check.

    1. Press Ctrl + F2.

  6. Remove or comment the persistent table addition.

    1. Adjust the code as follows:

      Code Snippet
      123
      define behavior for Z##_R_TravelItem alias Item with unmanaged save draft table z##_tritem_d
  7. Activate the behavior definition. Then, use a quick fix to generate the required method in the save sequence.

    1. Press Ctrl + F3 to activate the development object.

    2. Place the cursor in the code row that starts with with unmanaged and press Ctrl + 1. Alternatively, choose the warning icon with a light bulb that is displayed on the left-hand side of this code row.

    3. From the list of available quick fixes, choose Add required method save_modified in new local saver class.

      Result

      The quick fix adds a local class lsc_z##_r_travel to your behavior pool class ZBP_##_R_TRAVEL. The local class inherits from global class CL_ABAP_BEHAVIOR_SAVER and redefines the inherited instance method save_modified.
  8. Analyze the signature of the generated method.

    1. For example, place the cursor on save_modified and press F2 to display the ABAP Element Info for the method.

Task 3: Implement the Data Access

Implement the save_modified method of the saver class. For all three importing parameters in turn, implement a loop over the item component and call the related method of the /LRN/CL_S4D437_TRITEM class. To facilitate data mapping between element names in the business object and field names in the signature of the /LRN/CL_S4D437_TRITEM class, define a field mapping for the structure types and use it in CORRESPONDING expressions.

Steps

  1. In the following, you will create an instance of class /LRN/CL_S4D437_TRITEM that encapsulates access to your database table Z##_TRITEM. However, class /LRN/CL_S4D437_TRITEM and database table Z##_TRITEM belong to different software components, and an object of one software component cannot be used in another software component by default, as software components provide their functionality for other software components via explicitly released APIs.

    Therefore, you must first C1 release your database table Z##_TRITEM so that class /LRN/CL_S4D437_TRITEM can access it.

    1. In the Project Explorer, open the context menu for database table Z##_TRITEM.

    2. Choose API StateAdd Use System-Internally (Contract C1)....

    3. On the Add Release Contract dialog, choose Next.

    4. In the Validate Changes step, choose Next.

    5. Select a transport request and choose Finish.

  2. In the save_modified method of the saver class, now create an instance of the /LRN/CL_S4D437_TRITEM class and store a reference to the new instance in an inline declared variable (suggested name: model). Supply the importing parameter of the constructor with the name of your database table for travel items Z##_TRITEM.

    1. Adjust the code as follows:

      Code Snippet
      123456
      METHOD save_modified. DATA(model) = NEW /lrn/cl_s4d437_tritem( i_table_name = 'Z##_TRITEM' ). ENDMETHOD.
  3. Implement a loop over the item component of the importing parameter delete, using an inline declared field symbol (suggested name: <item_d>).

    1. Adjust the code as follows:

      Code Snippet
      12345678910
      METHOD save_modified. DATA(model) = NEW /lrn/cl_s4d437_tritem( i_table_name = 'Z##_TRITEM' ). LOOP AT delete-item ASSIGNING FIELD-SYMBOL(<item_d>). ENDLOOP. ENDMETHOD.
  4. For each row, call the delete_item method of your /LRN/CL_S4D437_TRITEM instance. Supply its importing parameter with the uuid of the current row of delete-item.

    1. Adjust the code as follows:

      Code Snippet
      12345678910
      METHOD save_modified. DATA(model) = NEW /lrn/cl_s4d437_tritem( i_table_name = 'Z##_TRITEM' ). LOOP AT delete-item ASSIGNING FIELD-SYMBOL(<item_d>). model->delete_item( i_uuid = <item_d>-itemuuid ). ENDLOOP. ENDMETHOD.
  5. Similarly, implement a loop over the item component of the importing parameter create, using an inline declared field symbol (suggested name: <item_c>).

    1. At the end of the method, add the following code:

      Code Snippet
      123
      LOOP AT create-item ASSIGNING FIELD-SYMBOL(<item_c>). ENDLOOP.
  6. For each row, call the create_item method of your /LRN/CL_S4D437_TRITEM instance. Supply its importing parameter with the data from the current row of create-item. Use a CORRESPONDING expression to convert the row of create-item to the type of input parameter i_item, that is, to structure type /LRN/437_S_TRITEM.

    Note

    At the moment, the CORRESPONDING expression will only assign the components with identical names in /LRN/437_S_TRITEM and in the row type of create-item. That is not the case for most of the components. We use an explicit field mapping in the next step.
    1. Adjust the code as follows:

      Code Snippet
      1234
      LOOP AT create-item ASSIGNING FIELD-SYMBOL(<item_c>). model->create_item( i_item = CORRESPONDING #( <item_c> ) ). ENDLOOP.
  7. Edit the behavior definition on data model level Z##_R_TRAVEL. Define a field mapping for structure type /LRN/437_S_TRITEM.

    Hint

    Structure type /LRN/437_S_TRITEM and persistent table Z##_TRITEM use the same field names. To save time, you can copy from the field mapping for persistent table Z##_TRITEM.
    1. Add the following code to the behavior definition for the child entity Z##_R_TravelItem:

      Code Snippet
      123456789101112131415
      mapping for /lrn/437_s_tritem corresponding { ItemUuid = item_uuid; AgencyId = agency_id; TravelId = travel_id; CarrierId = carrier_id; ConnectionId = connection_id; FlightDate = flight_date; BookingId = booking_id; PassengerFirstName = passenger_first_name; PassengerLastName = passenger_last_name; ChangedAt = changed_at; ChangedBy = changed_by; LocChangedAt = loc_changed_at; }
  8. Return to the implementation of the save_modified method. Adjust the CORRESPONDING expression so that it uses the field mapping for structure type /LRN/437_S_TRITEM from the behavior definition.

    1. Adjust the code as follows:

      Code Snippet
      12
      model->create_item( i_item = CORRESPONDING #( <item_c> MAPPING FROM ENTITY ) ).
  9. Finally, implement a loop over the item component of the importing parameter update, using an inline declared field symbol (suggested name: <item_u>). For each row, call the update_item method of your /LRN/CL_S4D437_TRITEM instance.

    1. At the end of the method, add the following code:

      Code Snippet
      12345
      LOOP AT update-item ASSIGNING FIELD-SYMBOL(<item_u>). model->update_item( i_item = i_itemx = ). ENDLOOP.
  10. Supply the importing parameter i_item with the data from the current row of update-item. Use the field mapping from the behavior definition as before. Supply importing parameter i_itemx in the same way. But this time, use the addition USING CONTROL to make sure the information is extracted from sub component %control.

    1. Adjust the code as follows:

      Code Snippet
      123456
      LOOP AT update-item ASSIGNING FIELD-SYMBOL(<item_u>). model->update_item( i_item = CORRESPONDING #( <item_u> MAPPING FROM ENTITY ) i_itemx = CORRESPONDING #( <item_u> MAPPING FROM ENTITY USING CONTROL ) ). ENDLOOP.
  11. Return to the behavior definition and extend the field mapping for structure type /LRN/437_S_TRITEM. Specify that the structure type /LRN/437_S_TRITEMX is the related control structure, then activate the behavior definition and the behavior implementation.

    1. Adjust the code as follows:

      Code Snippet
      123
      mapping for /lrn/437_s_tritem control /lrn/437_s_tritemx corresponding {
    2. In the behavior definition, press Ctrl + F3 to activate the development object.

    3. In the behavior implementation, press Ctrl + F3 to activate the development object.

  12. Test the preview for the OData UI service. Make sure that you can successfully create, update and delete flight travel items.

    1. Open the service binding, place the cursor on the root entity and choose Preview....

    2. In the preview, display the details for one of the travels.

    3. Choose Edit to change to edit mode.

    4. Ensure that you can successfully create new travel items via the UI. Also make sure that you can update and delete existing travel items.

    5. Use the preview tool in Eclipse to check whether the contents of database table Z##_TRITEM have been updated accordingly.

Task 4: Optional: Handle the Messages

Handle the returning parameters of the delete_item, create_item, and update_item methods of class /LRN/CL_S4D437_TRITEM. Store the returned messages in the reported parameter of the save_modified method.

Note

Because all returning parameters have the same type, it is a good idea to do the required mapping in a private helper method of the saver class.

Steps

  1. In the saver class, define a new private instance method (suggested name: map_message). Define an importing parameter of structure type SYMSG (suggested name: i_msg) and a returning parameter of type REF TO if_abap_behv_message (suggested name: r_msg).

    1. Adjust the code as follows:

      Code Snippet
      1234567891011121314
      CLASS lsc_z##_r_travel DEFINITION INHERITING FROM cl_abap_behavior_saver. PROTECTED SECTION. METHODS save_modified REDEFINITION. PRIVATE SECTION. METHODS map_message IMPORTING i_msg TYPE symsg RETURNING VALUE(r_msg) TYPE REF TO if_abap_behv_message. ENDCLASS.
  2. Use a quick fix to generate the implementation for the method.

    1. Choose the error icon with a light bulb next to the code row with the method name to display the quick assist proposals.

    2. From the list of available quick fixes, choose Add implementation for map_message.

  3. Implement the method. First, map the message types in component msgty of parameter i_msg to a local variable of type if_abap_behv_message=>t_severity (suggested name: severity).

    Hint

    As values for the local variable, use the components of the constant structure if_abap_behv_message=>severity.

    You can either use a SWITCH expression or a more old-fashioned CASE … ENDCASE structure.

    1. In the implementation of method map_message, add the following code:

      Code Snippet
      123456
      DATA(severity) = SWITCH #( i_msg-msgty WHEN 'S' THEN if_abap_behv_message=>severity-success WHEN 'I' THEN if_abap_behv_message=>severity-information WHEN 'W' THEN if_abap_behv_message=>severity-warning WHEN 'E' THEN if_abap_behv_message=>severity-error ELSE if_abap_behv_message=>severity-none ).

      Alternative solution:

      Code Snippet
      12345678910111213
      DATA severity TYPE if_abap_behv_message=>t_severity. CASE i_msg-msgty. WHEN 'S'. severity = if_abap_behv_message=>severity-success. WHEN 'I'. severity = if_abap_behv_message=>severity-information. WHEN 'W'. severity = if_abap_behv_message=>severity-warning. WHEN 'E'. severity = if_abap_behv_message=>severity-error. WHEN OTHERS. severity = if_abap_behv_message=>severity-none. ENDCASE.

  4. Call the inherited functional method new_message and assign the result to the returning parameter r_msg. Supply the importing parameters with the related components of parameter i_msg, except for parameter severity which you supply with your local variable severity.

    1. At the end of the method map_message, add the following code:

      Code Snippet
      12345678
      r_msg = new_message( id = i_msg-msgid number = i_msg-msgno severity = severity v1 = i_msg-msgv1 v2 = i_msg-msgv2 v3 = i_msg-msgv3 v4 = i_msg-msgv4 ).
  5. Return to the implementation of the save_modified method. Find the call of the delete_item method and store the result in an inline declared variable (suggested name: msg_d).

    1. Adjust the code as follows:

      Code Snippet
      1
      DATA(msg_d) = model->delete_item( i_uuid = <item_d>-itemuuid ).
  6. If the result is not initial, append a new row to the item component of the reported parameter. Fill the %tky-itemuuid component with the uuid of the current flight travel item and component %msg via a call of the private method that you just implemented.

    1. Adjust the code as follows:

      Code Snippet
      123456
      DATA(msg_d) = model->delete_item( i_uuid = <item_d>-itemuuid ). IF msg_d IS NOT INITIAL. APPEND VALUE #( %tky-itemuuid = <item_d>-itemuuid %msg = map_message( msg_d ) ) TO reported-item. ENDIF.
  7. Adjust the call of create_item accordingly.

    1. Adjust the code as follows:

      Code Snippet
      1234567
      DATA(msg_c) = model->create_item( i_item = CORRESPONDING #( <item_c> MAPPING FROM ENTITY ) ). IF msg_c IS NOT INITIAL. APPEND VALUE #( %tky-itemuuid = <item_c>-itemuuid %msg = map_message( msg_c ) ) TO reported-item. ENDIF.
  8. Finally, adjust the call of update_item.

    1. Adjust the code as follows:

      Code Snippet
      123456789
      DATA(msg_u) = model->update_item( i_item = CORRESPONDING #( <item_u> MAPPING FROM ENTITY ) i_itemx = CORRESPONDING #( <item_u> MAPPING FROM ENTITY USING CONTROL ) ). IF msg_u IS NOT INITIAL. APPEND VALUE #( %tky-itemuuid = <item_u>-itemuuid %msg = map_message( msg_u ) ) TO reported-item. ENDIF.
  9. Activate the ABAP class.

    1. Press Ctrl + F3 to activate the development object.

  10. Retest the preview for the OData UI service. Ensure that the messages returned by the delete_item, create_item, and update_item methods of class /LRN/CL_S4D437_TRITEM are displayed on the UI.

    1. Open the service binding, place the cursor on the root entity and choose Preview....

    2. In the preview, display the details for one of the travels.

    3. Choose Edit to change to edit mode.

    4. Make sure that a corresponding success message is displayed when you have successfully created a new travel item.

    5. Also, make sure that appropriate messages are displayed when updating and deleting existing travel items.