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.
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 variable | Private class attribute |
Form routine, encapsulating the cache access (and selection) | Public static access method |
n/a | Private statice method to fill cache |
n/a | Public 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.
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.
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.
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).
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 = 1 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.