Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
thomas_jung
Developer Advocate
Developer Advocate


Introduction
This weblog was really sparked by a conversation that I had the other day. I was talking with a Java developer who was fairly new to the SAP world, but claimed to already be an ABAP expert. He was commenting on the ABAP programming language. Several of his comments revolved around the fact that ABAP Objects didn't have persistent objects or an OO transactional system. Now I found this rather interesting considering that ABAP Objects does have both of those things.

On the same day that I had the opportunity to set this poor misguided soul straight, I ran into another instance of persistence ignorance. There was a posting in SDN on the ABAP forum asking ABAP Developers to fill out /thread/21300 [original link is broken]. One of the questions is "What are the weak points for ABAP Objects". The possible answer that struck me was - "No persistent objects or serializeable objects". ABAP objects actually has both persistent and serializeable objects. 5 people out of 45 so far had given this option as a weak point for ABAP Objects. Therefore I dedicate this weblog to those 5 people and my Java friend.

Serializeable Objects
In defense of those who answered that the lack of persistence and serializeable objects was a weakness, it is true that this functionality is not present before the 6.X releases of the WebAS. All the examples that I am going to share today were developed on a 620 WebAS. Before I jump into the heart of the weblog on Persistent Objects, I thought I might address serializeable objects once again real quick. If you want an ABAP Object class to be serializeable, all you have to do is add the IF_SERIALIZABLE_OBJECT interface. That's it - pretty simple!. The following is some sample ABAP code that I have to serialize a data object class and write it out to an XML file on the presentation server:
TABLES: zesa_issue_hdr.
PARAMETER: id LIKE zesa_issue_hdr-id OBLIGATORY DEFAULT 1.
PARAMETER: ifile TYPE file_table-filename OBLIGATORY
DEFAULT 'c:issue.xml'.
*———————————————————————-*
* VARIABLES *
*———————————————————————-*
****List of possible filenames.
DATA: ifile_tab TYPE filetable.
****Filetable work area
DATA: ifile_tab_line LIKE LINE OF ifile_tab.
****File Open Return Code
DATA: rc TYPE i.
****Temp File name for function module call.
DATA: ifilename TYPE string.
DATA: issue TYPE REF TO zcl_es_asap_issue_object.
*———————————————————————-*
* SELECTION SCREEN – VALUE REQUES FOR FILENAME *
*———————————————————————-*
AT SELECTION-SCREEN ON VALUE-REQUEST FOR ifile.
DATA: window_title TYPE string.
DATA: path TYPE string.
DATA: filename TYPE string.
DATA: fullpath TYPE string.
MOVE 'Download XML File Location'(001) TO window_title.
CALL METHOD cl_gui_frontend_services=>file_save_dialog
EXPORTING
window_title = window_title
default_extension = 'xml'
CHANGING
filename = filename
path = path
fullpath = fullpath
EXCEPTIONS
cntl_error = 1
error_no_gui = 2
OTHERS = 3.
IF sy-subrc <> 0.
ELSE.
MOVE fullpath TO ifile.
ENDIF.

START-OF-SELECTION.
MOVE ifile TO ifilename.
CREATE OBJECT issue
EXPORTING
id = id
create_mode = abap_false.
DATA: g_ixml TYPE REF TO if_ixml,
g_stream_factory TYPE REF TO if_ixml_stream_factory,
xslt_err TYPE REF TO cx_xslt_exception,
g_encoding TYPE REF TO if_ixml_encoding,
ostream TYPE REF TO if_ixml_ostream.
CONSTANTS: line_length TYPE i VALUE 4096.
TYPES: line_t(line_length) TYPE x,
table_t TYPE STANDARD TABLE OF line_t.
DATA: restab TYPE table_t.
CONSTANTS:
* encoding for download of XML files
encoding TYPE string VALUE 'utf-8'.
DATA: ressize TYPE i.

TRY.
g_ixml = cl_ixml=>create( ).
g_stream_factory = g_ixml->create_stream_factory( ).
g_encoding = g_ixml->create_encoding(
character_set = encoding
byte_order = 0 ).
REFRESH restab.
ostream = g_stream_factory->create_ostream_itable( table = restab ).
ostream->set_encoding( encoding = g_encoding ).
CALL TRANSFORMATION id_indent
SOURCE
asap_issue = issue
RESULT XML restab.
ressize = ostream->get_num_written_raw( ).
CATCH cx_xslt_exception INTO xslt_err.
DATA: s TYPE string.
s = xslt_err->get_text( ).
ENDTRY.

CALL FUNCTION 'GUI_DOWNLOAD'
EXPORTING
bin_filesize = ressize
filename = ifilename
filetype = 'BIN'
TABLES
data_tab = restab
EXCEPTIONS
OTHERS = 1.
CALL METHOD cl_gui_frontend_services=>execute
EXPORTING
document = ifilename
* APPLICATION =
* PARAMETER =
* DEFAULT_DIRECTORY =
* MAXIMIZED =
* MINIMIZED =
* SYNCHRONOUS =
EXCEPTIONS
cntl_error = 1
error_no_gui = 2
bad_parameter = 3
file_not_found = 4
path_not_found = 5
file_extension_unknown = 6
error_execute_failed = 7
OTHERS = 8.

The resulting XML looks like this:



Persistent Objects
For this section on Persistent Objects I am going to take a chapter from a delta training course I wrote for in house development group. We are going to start off by creating a new persistent object for the SFLIGHT table. Then we will look at the Persistent Data Mapper. Finally I have three example exercise programs that I will share.

Creating the Persistent Object Class
We start off with the task of creating a Persistent Object Class. The process is very similar to creating any other type of global class in ABAP Objects. From the create class dialog, you simply choose Persistent Class for the Class Type.



After the class creation, if you look at the Properties tab you should see something like the following:



You will notice that even though I choose Public as my instantiation type in the create dialog, SAP has forced this to Protected and locked the field against update. That is because more than just this one class was created. You also have generated two additional classes. A ZCB_ class that is your Base Class and a ZCA_ class that is your Agent Class.



You will also see that your main class you just created now also has a Friends relationship to the Base class. The Base Class in turn is the Superclass for the Agent Class. Later when we get to the coding part of this exercise you will see that we always use the Agent class to create instances of our persistent class.



Right now our persistent class has just 5 methods that it has inherited from the IF_OS_STATE interface. You are not allowed to alter the GET or SET methods because the internal coding is generated. However according to the documentation you can change the default code in the HANDLE_EXCEPTION, INIT, and INVALIDATE methods. For the purposes of this exercise, we will just leave the default coding.



Persistent Data Mapper
Well we have a persistent object, but it doesn't do much yet because we haven't mapped any data objects to the class. This is where the Persistent Data Mapper comes into play. On the Main menu bar in the class builder, we have an extra button when we are working with our persistent class. This button will navigate us to the Persistent Data Mapper.



When we first enter the Data Mapper we are asked to add a Table or Structure. For this example we will enter SFLIGHT (Please note that to get all of my screen shots I had to create my Persistent class multiple times. That is why in some of the screen shots you might see a slightly different class name).



Now at the top of the screen you see an area with your persistent class and all of its attributes (there are none yet). At the bottom of the screen you see all the possible fields exposed by the data object we selected.



If you double click on a field from the Table/Field View, it will be loaded into the middle part of the editor. From this area you can change the attribute name, description, visibility (Public, Protected, Private), and read only flag. Once you are complete you can press the arrow button to the left of the attribute name to add or update this attribute in our persistent class.



You can then repeat this task for each field we want to expose as an attribute in our Persistent Class. You also have a context menu that can be used to alter the attribute once it has been added to the persistent class.



When we save and exit from the Data Mapper, you should see many new SET_ and GET_ methods that have been generated in your Persistent Class.



Coding Examples using our Persistent Class
We have our Persistent Class and all the data fields mapped and exposed. All that is left now is to start writing some code to use our persistent class. For our first example, we will start simple and just read some data from out persistent class. You can see that we use the Agent class to create the instance of our persistent class. You can also see how you can use the get methods in calculations instead of the actual attributes. Finally we will book an additional person and increase our seats occupied. We then refresh our persistent object to prove that we have updated the database.
REPORT  zesu_00_persistance.
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME.
PARAMETER: p_carrid TYPE sbook-carrid,
p_connid TYPE sbook-connid,
p_fldate TYPE sbook-fldate.
SELECTION-SCREEN END OF BLOCK b1.

DATA: my_flight TYPE REF TO zcl_bc620_00_persist,
seatsocc TYPE sflight-seatsocc,
seatsmax TYPE sflight-seatsmax,
seatsfree TYPE sflight-seatsocc,
ierr TYPE REF TO cx_os_object_not_found,
ierr2 TYPE REF TO cx_os_system_error,
ierr3 TYPE REF TO cx_os_error.
DATA: text TYPE string.
CLASS: zca_bc620_00_persist DEFINITION LOAD.

TRY.
****Use the Agent Class to create an instance of our Persistent Class
my_flight = zca_bc620_00_persist=>agent->get_persistent(
i_carrid = p_carrid
i_connid = p_connid
i_fldate = p_fldate ).
****Perform Calculations using our Get Methods instead of the attributes directly
seatsfree = my_flight->get_seatsmax( ) - my_flight->get_seatsocc( ).
seatsmax = my_flight->get_seatsmax( ).
seatsocc = my_flight->get_seatsocc( ).
WRITE: / 'Seats Free: '(001), seatsfree.
WRITE: / 'Seats Max: '(002), seatsmax.
WRITE: / 'Seats Occupied: '(003), seatsocc.
IF seatsfree > 0.
WRITE: / 'Now Booking a new Person'(004).
seatsocc = my_flight->get_seatsocc( ) + 1.
my_flight->set_seatsocc( seatsocc ).
COMMIT WORK AND WAIT.
CLEAR my_flight.
my_flight = zca_bc620_00_persist=>agent->get_persistent(
i_carrid = p_carrid
i_connid = p_connid
i_fldate = p_fldate ).
seatsfree = my_flight->get_seatsmax( ) - my_flight->get_seatsocc( ).
seatsmax = my_flight->get_seatsmax( ).
seatsocc = my_flight->get_seatsocc( ).
WRITE: / 'Seats Free: '(001), seatsfree.
WRITE: / 'Seats Max: '(002), seatsmax.
WRITE: / 'Seats Occupied: '(003), seatsocc.
ENDIF.
CATCH cx_os_object_not_found INTO ierr.
text = ierr->get_text( ).
MESSAGE text TYPE 'I' DISPLAY LIKE 'E'.
CATCH cx_os_system_error INTO ierr2.
text = ierr2->get_text( ).
MESSAGE text TYPE 'I' DISPLAY LIKE 'E'.
CATCH cx_os_error INTO ierr3.
text = ierr3->get_text( ).
MESSAGE text TYPE 'I' DISPLAY LIKE 'E'.
ENDTRY.

For the second example, we are going to get a little fancier. We are going to get an internal table full of instances of our persistent class. My internal table isflight contains just our data keys, along with the persistent class instance (my_flight) which contains the rest of our data. We then write out a simple report:
REPORT  zesu_00_persistance.
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME.
PARAMETER: p_carrid TYPE sbook-carrid,
p_connid TYPE sbook-connid.
* p_fldate TYPE sbook-fldate.
SELECTION-SCREEN END OF BLOCK b1.
DATA: BEGIN OF isflight1 OCCURS 0,
carrid TYPE sbook-carrid,
connid TYPE sbook-connid,
fldate TYPE sbook-fldate,
END OF isflight1.
DATA: BEGIN OF isflight OCCURS 0,
carrid TYPE sbook-carrid,
connid TYPE sbook-connid,
fldate TYPE sbook-fldate,
my_flight TYPE REF TO zcl_bc620_00_persist,
END OF isflight.
DATA: isflight_line LIKE LINE OF isflight.
DATA: seatsocc TYPE sflight-seatsocc,
seatsmax TYPE sflight-seatsmax,
seatsfree TYPE sflight-seatsocc,
ierr TYPE REF TO cx_os_object_not_found,
ierr2 TYPE REF TO cx_os_system_error,
ierr3 TYPE REF TO cx_os_error.
DATA: text TYPE string.
CLASS: zca_bc620_00_persist DEFINITION LOAD.
TRY.
SELECT carrid connid fldate FROM sflight
INTO TABLE isflight1
WHERE carrid = p_carrid
AND connid = p_connid.
LOOP AT isflight1.
CLEAR isflight_line.
MOVE-CORRESPONDING isflight1 TO isflight_line.
APPEND isflight_line TO isflight.
ENDLOOP.
LOOP AT isflight INTO isflight_line.
isflight_line-my_flight = zca_bc620_00_persist=>agent->get_persistent(
i_carrid = isflight_line-carrid
i_connid = isflight_line-connid
i_fldate = isflight_line-fldate ).
MODIFY isflight FROM isflight_line.
ENDLOOP.
LOOP AT isflight INTO isflight_line.
seatsfree = isflight_line-my_flight->get_seatsmax( )
- isflight_line-my_flight->get_seatsocc( ).
seatsmax = isflight_line-my_flight->get_seatsmax( ).
seatsocc = isflight_line-my_flight->get_seatsocc( ).
WRITE: / isflight_line-carrid, isflight_line-connid, isflight_line-fldate.
WRITE: / 'Seats Free: '(001), seatsfree.
WRITE: / 'Seats Max: '(002), seatsmax.
WRITE: / 'Seats Occupied: '(003), seatsocc.
ENDLOOP.
CATCH cx_os_object_not_found INTO ierr.
text = ierr->get_text( ).
MESSAGE text TYPE 'I' DISPLAY LIKE 'E'.
CATCH cx_os_system_error INTO ierr2.
text = ierr2->get_text( ).
MESSAGE text TYPE 'I' DISPLAY LIKE 'E'.
CATCH cx_os_error INTO ierr3.
text = ierr3->get_text( ).
MESSAGE text TYPE 'I' DISPLAY LIKE 'E'.
ENDTRY.

The final example is very similar to the first one. The only difference now is that we are going to use transactional object services to commit our changes through our persistent class. As you can see you can write an event handler for the completion of the update. It is in this event handler that we will reread the data to prove that the update was successful.
REPORT  zesu_00_persistance.
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME.
PARAMETER: p_carrid TYPE sbook-carrid,
p_connid TYPE sbook-connid,
p_fldate TYPE sbook-fldate.
SELECTION-SCREEN END OF BLOCK b1.
DATA: my_flight TYPE REF TO zcl_bc620_00_persist,
seatsocc TYPE sflight-seatsocc,
seatsmax TYPE sflight-seatsmax,
seatsfree TYPE sflight-seatsocc,
ierr TYPE REF TO cx_os_object_not_found,
ierr2 TYPE REF TO cx_os_system_error,
ierr3 TYPE REF TO cx_os_error,
exc TYPE REF TO cx_root.
DATA: text TYPE string.
CLASS: zca_bc620_00_persist DEFINITION LOAD,
cl_os_system DEFINITION LOAD.
DATA: transaction_manager TYPE REF TO if_os_transaction_manager.
DATA: transaction TYPE REF TO if_os_transaction.
*———————————————————————*
* CLASS transaction_handler DEFINITION
*———————————————————————*
*
*———————————————————————*
CLASS transaction_handler DEFINITION.
PUBLIC SECTION.
METHODS handle FOR EVENT finished OF if_os_transaction
IMPORTING status.
ENDCLASS. "transaction_handler definition
*———————————————————————*
* CLASS transaction_handler IMPLEMENTATION
*———————————————————————*
*
*———————————————————————*
CLASS transaction_handler IMPLEMENTATION.
METHOD handle.
DATA:
my_flight TYPE REF TO zcl_bc620_00_persist.
IF status = oscon_tstatus_fin_success.
MESSAGE 'Update commited' TYPE 'I'.
my_flight = zca_bc620_00_persist=>agent->get_persistent(
i_carrid = p_carrid
i_connid = p_connid
i_fldate = p_fldate ).
seatsfree = my_flight->get_seatsmax( ) - my_flight->get_seatsocc( ).
seatsmax = my_flight->get_seatsmax( ).
seatsocc = my_flight->get_seatsocc( ).
WRITE: / 'Seats Free: '(001), seatsfree.
WRITE: / 'Seats Max: '(002), seatsmax.
WRITE: / 'Seats Occupied: '(003), seatsocc.
ENDIF.
ENDMETHOD. "handle
ENDCLASS. "transaction_handler implementation
DATA handler TYPE REF TO transaction_handler.

LOAD-OF-PROGRAM.
cl_os_system=>init_and_set_modes(
i_external_commit = oscon_false
i_update_mode = oscon_dmode_default ).

START-OF-SELECTION.
transaction_manager = cl_os_system=>get_transaction_manager( ).
transaction = transaction_manager->create_transaction( ).
CREATE OBJECT handler.
SET HANDLER handler->handle FOR transaction.
TRY.
transaction->start( ).
my_flight = zca_bc620_00_persist=>agent->get_persistent(
i_carrid = p_carrid
i_connid = p_connid
i_fldate = p_fldate ).
seatsfree = my_flight->get_seatsmax( ) - my_flight->get_seatsocc( ).
seatsmax = my_flight->get_seatsmax( ).
seatsocc = my_flight->get_seatsocc( ).
WRITE: / 'Seats Free: '(001), seatsfree.
WRITE: / 'Seats Max: '(002), seatsmax.
WRITE: / 'Seats Occupied: '(003), seatsocc.
IF seatsfree > 0.
WRITE: / 'Now Booking a new Person'(004).
seatsocc = my_flight->get_seatsocc( ) + 1.
my_flight->set_seatsocc( seatsocc ).
transaction->end( ).
ENDIF.
CATCH cx_os_object_not_found INTO ierr.
text = ierr->get_text( ).
MESSAGE text TYPE 'I' DISPLAY LIKE 'E'.
CATCH cx_os_system_error INTO ierr2.
text = ierr2->get_text( ).
MESSAGE text TYPE 'I' DISPLAY LIKE 'E'.
CATCH cx_os_error INTO ierr3.
text = ierr3->get_text( ).
MESSAGE text TYPE 'I' DISPLAY LIKE 'E'.
CATCH cx_root INTO exc.
text = exc->get_text( ).
MESSAGE text TYPE 'I' DISPLAY LIKE 'E'.
ENDTRY.

Closing
Admittedly these have been rather simplistic examples. Hopefully thought, they give people a chance to see that persistent objects do exist and give some simple steps to get started using them. These examples do not really show off the power and benefits of using persistent classes. Once you have down the basics of creating and working with persistent objects, you will be ready to explore their greater possibilities.

68 Comments