Additional Blogs by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member

Motivation

I've already outlined in my post Idioms in ABAP/4: Local table caching, how you can use table caching with classical ABAP/4 methods.

Now I'd like to give a guideline, how you'd use this with ABAP Objects, and while I've developed an example I've found a very generic version for table caching across the ABAP execution unit, which you can find at the end of this post.

Transforming into object oriented style

For object oriented programming style you'd need to transpose the required resources and figures into that different style:

Classical ABAP/4
ABAP Objects
Local (or global) static variablePrivate class attribute
Form routine, encapsulating the cache access (and selection)Public static access method
n/aPrivate statice method to fill cache
n/aPublic static method to purge the cache

When the cache become encapsulated by the Class, you'll now be able to make further operations with the cache without sacrificing the encapsulated cache, i.e. you'll now be able to explicitly purge the cache.

How to use it

Idiom for full table cache in ABAP Objects

   CLASS zcl_cache_<table to cache>.

     PUBLIC SECTION.

        CLASS-METHODS: select_single IMPORTING iv_keyfield TYPE <key field type>. "add more keyfields if required

     PRIVATE SECTION.

        CLASS-DATA:    gt_cache      TYPE STANDARD TABLE OF <table to cache>.

   ENDCLASS.

and the implementation of the select_single method is pretty straightforward, as it is basically the same as with the classical ABAP/4, depending on if you'd like to cache single entries or the full table at once (which will not change the interface of the Class).

   READ TABLE gt_cache INTO <return structure>

                       WITH KEY key(s) = <given key field(s)>

   IF sy-subrc <> 0.   "missed

      "Fill cache line

      SELECT SINGLE * FROM <table to cache>

                      INTO <return structure> WHERE ...

      me->add_to_cache( <return structure> )   "or something similar

   ENDIF.

Generic approach

A more generic approach would need to create dynamically cache objects, as well as to be very flexible with its parameters.

The idea is to provide a static class, that encapsulates the cache objects with a Singleton concept using a global registry for each instance of a table cache.

The table cache classes itself will dynamically assign and generate the cache, SQL statement and data.

Usage

The cache should be usable as simply as possible. So here it is!

   DATA: ls_t001                  TYPE T001.

   ls_t001 = ZCL_ADV_TABLE_CACHE=>get_cache( 'T001' )->select_single( pv_bukrs ).

You should use a constant name for the table's name you'd like to access to make the where-used list workable.

The cache factory cannot produce different caching strategies, i.e. you'd need to separate global classes, one for each type of caching, i.e. line-based caching or full-table caching, as the given class will produce cache objects of one type only, unless you specify the type each time you'd access the cache (which isn't really a benefit here).


Implementation

In the following example I will give you the one for line-based caching, leaving the implementation of full-table cache up to you.

So first we will need the Interface, that defines the public access to a caching class. This type is defined in the Data Dictionary.

interface ZIF_ADV_TABLE_CACHE_ACCESS public.
 
   methods SELECT_SINGLE
     importing
       !IV_KEY1 type ANY optional
       !IV_KEY2 type ANY optional
       !IV_KEY3 type ANY optional
       !IV_KEY4 type ANY optional
       !IV_KEY5 type ANY optional
     preferred parameter IV_KEY1
     returning value(RS_TABLE_LINE) type STRING"a TYPE REF TO DATA is not possibly for syntax reasons
   methods FLUSH.
endinterface.

Up to five key fields are supported (not counting the client field, if it was the first field). This number should be okay with most of the SAP database tables.

Then we define a single static class, that produces singleton-based instances of cache object (Factory). The implementation of a caching class is encapsulated within the class as a local class implementation (ECC 6.00 upwards, as far as I know).


class ZCL_ADV_TABLE_CACHE definition public create public .

   public section.

   class-methods GET_CACHE
     importing   !IV_TABLENAME type TABNAME
     returning   value(RO_TABLE_CACHE) type ref to ZIF_ADV_TABLE_CACHE_ACCESS .
   protected section.
   private section.

   class-data GT_CACHEOBJECTS type TT_CACHE_LIST .   "global singleton's registry
ENDCLASS.

and it's implemented as following

CLASS ZCL_ADV_TABLE_CACHE IMPLEMENTATION.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_ADV_TABLE_CACHE=>GET_CACHE
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_TABLENAME                   TYPE        TABNAME
* | [<-()] RO_TABLE_CACHE                 TYPE REF TO ZIF_ADV_TABLE_CACHE_ACCESS
* +--------------------------------------------------------------------------------------</SIGNATURE>
   method GET_CACHE.

      "Singleton with global registry

      FIELD-SYMBOLS: <ls_cache>        TYPE LINE OF tt_cache_list.
      READ TABLE gt_cacheObjects ASSIGNING <ls_cache>
                                 WITH KEY tableName = iv_tableName.
      IF sy-subrc <> 0.

         CREATE OBJECT ro_table_cache TYPE lc_table_cache
                                      EXPORTING
                                         iv_tableName = iv_tableName.

         "fill cache

         APPEND INITIAL LINE TO gt_cacheObjects ASSIGNING <ls_cache>.
         <ls_cache>-tableName = iv_tableName.
         <ls_cache>-cacheObject = ro_table_cache.
      ELSE.
         ro_table_cache = <ls_cache>-cacheObject.
      ENDIF.
 
   endmethod.


ENDCLASS.

So you're now only missing the definition of tt_cache_list and the definition of the local class, which is both put into the Class-Relevant Local Definitions:

*"* use this source file for any type of declarations (class
*"* definitions, interfaces or type declarations) you need for
*"* components in the private section
TYPES: BEGIN OF ts_cache_list,
           tablename               TYPE TABNAME,
           cacheobject             TYPE REF TO ZIF_ADV_TABLE_CACHE_ACCESS,
        END OF ts_cache_list,

       tt_cache_list              TYPE STANDARD TABLE OF ts_cache_list.



CLASS lc_table_cache DEFINITION.

    PUBLIC SECTION.

       INTERFACES ZIF_ADV_TABLE_CACHE_ACCESS.

       METHODS constructor     IMPORTING iv_tablename   TYPE TABNAME.

    PRIVATE SECTION.

       DATA: mv_tablename          TYPE TABNAME,
             mt_cache              TYPE REF TO DATA,
             mt_keyfields          TYPE STANDARD TABLE OF FIELDNAME,
             mv_sql_template       TYPE STRING.

       METHODS create_cache.
       METHODS create_keyfield_list.
       METHODS create_sql_template.

ENDCLASS.

So what's missing now is the actual implementation of the class. Watch out for the method create_sql_template and select_single, which is most interesting to see them working together.


*"* use this source file for the definition and implementation of
*"* local helper classes, interface definitions and type
*"* declarations

CLASS lc_table_cache IMPLEMENTATION.

    METHOD constructor.

       "Save the table name for identification
       mv_tablename = iv_tablename.

       create_cache( ).           "Create the empty cache
       create_keyfield_list( ).   "Provide the key field's name here,
                                  "to make dynamic SELECT statement workable
       create_sql_template( ).    "Prepare a dynamic WHERE clause

    ENDMETHOD.


    METHOD create_cache.
       "As we're caching only public data dictionary object, we can use the
       "given create data statement.
       CREATE DATA mt_cache TYPE STANDARD TABLE OF (mv_tablename).
    ENDMETHOD.


    METHOD create_keyfield_list.

       "This part is a little bit more complicated, as it uses RTTI with ECC 6.00

       DATA: lo_table_description      TYPE REF TO cl_abap_typedescr,
             lo_struct_description     TYPE REF TO cl_abap_structdescr.

       lo_table_description   = CL_ABAP_TYPEDESCR=>describe_by_name( mv_tablename ).

       DATA: ls_header                    TYPE X030L,
             lt_components                TYPE DD_X031L_TABLE.

       ls_header     = lo_table_description->get_ddic_header( )"Get key field counter
       lt_components = lo_table_description->get_ddic_object( )"Get list of fields

       FIELD-SYMBOLS: <ls_component>  TYPE LINE OF DD_X031L_TABLE.
       LOOP AT lt_components ASSIGNING <ls_component> FROM 1 TO ls_header-keycnt. "up to key count
          IF sy-tabix = AND <ls_component>-dtyp = 'CLNT'.                      "Skip client-specific declaration
             CONTINUE.
          ENDIF.
          APPEND <ls_component>-fieldname TO mt_keyfields.
       ENDLOOP.

    ENDMETHOD.


    METHOD create_sql_template.

       FIELD-SYMBOLS: <lv_fieldname>      TYPE fieldname.
       CLEAR mv_sql_template.

       LOOP AT mt_keyfields ASSIGNING <lv_fieldname>.

          DATA: lv_key_counter       TYPE fieldname,
                lv_key_counter_N     TYPE N.
          lv_key_counter_N = sy-tabix.
          CONCATENATE 'IV_KEY' lv_key_counter_N INTO lv_key_counter.

          IF NOT mv_sql_template IS INITIAL.
             CONCATENATE mv_sql_template 'AND'
                         INTO mv_sql_template
                         SEPARATED BY SPACE.
          ENDIF.
          CONCATENATE mv_sql_template <lv_fieldname> '=' lv_key_counter
                      INTO mv_sql_template
                      SEPARATED BY SPACE.

       ENDLOOP.
 
    ENDMETHOD.


    METHOD ZIF_ADV_TABLE_CACHE_ACCESS~select_single.

       "Cache lookup first
       "We use the LOOP statement for simplification reasons here

       "Some magic ABAP coding I

       FIELD-SYMBOLS: <lv_any_table>             TYPE ANY TABLE.
       ASSIGN mt_cache->* TO <lv_any_table>.

       LOOP AT <lv_any_table> INTO rs_table_line WHERE (mv_sql_template).
          EXIT.
       ENDLOOP.

       IF sy-subrc <> 0.   "cache missed

       "Some magic ABAP coding II

          DATA: lr_tableline                     TYPE REF TO DATA.
          FIELD-SYMBOLS: <lv_any_tableline>      TYPE ANY.

          CREATE DATA lr_tableline    TYPE (mv_tablename).
          ASSIGN lr_tableline->* TO <lv_any_tableline>.

          SELECT SINGLE * FROM  (mv_tablename)
                          INTO  <lv_any_tableline>
                          WHERE (mv_sql_template).
          IF sy-subrc = 0.
             "Hope that conversion will work, otherwise class implementation is not usable
             "for that particular data dictionary table, and you need to implement manually.
             rs_table_line = <lv_any_tableline>.
             INSERT  <lv_any_tableline> INTO TABLE <lv_any_table>.
             SORT <lv_any_table>.
          ELSE.
             CLEAR rs_table_line.
          ENDIF.
       ENDIF.

    ENDMETHOD.


    METHOD ZIF_ADV_TABLE_CACHE_ACCESS~flush.
       "Clear the cache
       CLEAR mt_cache.
   ENDMETHOD.
 
ENDCLASS.



Take care

   Florin

Please ->rate  or  ->like

if you find this blog post useful or if you'd like to give feedback.

1 Comment