Продолжаем знакомство с ABAP Object Services, в этой части будут рассмотрены темы: менеджеры инстанций и постоянства, пользовательские проверки при манипуляции с атрибутами хранимых классов, преобразование объектов в структуры и таблицы, загрузка связанных объектов без использования ссылочных атрибутов.
Менеджер инстанций и менеджер постоянства (Persistency Manager)
Как мы уже знаем каждый агент класса, содержит методы для создания, изменения, удаления хранимых объектов, получения хранимых объектов определенного статуса. Данные методы предназначены для хранимых объектов конкретного хранимого класса. Используя менеджер инстанций и менеджер постоянства, мы можем запускать аналогичные методы, не имея ссылки на агент конкретного хранимого класса.
Для доступа к менеджеру инстанции (классу реализующему интерфейс IF_OS_INSTANCE_MANEGER) нужно воспользоваться статическим методом get_instance_manager() класса CL_OS_SYSTEM. Данный класс реализует те же самые методы, что и IF_OS_CA_INSTANCE, за исключением методов для временных хранимых объектов. Кроме того данный класс позволяет получить (изменить SET_STATUS()) статус любого объекта, метод GET_STATUS().
Пример использования менеджера инстанций:
1 2 3 4 5 6 |
DATA: go_instance_manager TYPE REF TO IF_OS_INSTANCE_MANAGER, gt_all_objects TYPE osreftab. go_instance_manager = cl_os_system=>get_instance_manager( ). gt_all_objects = go_instance_manager->get_loaded( ). |
Менеджер постоянства позволяет инициировать объекты с помощью QUERY или таблицы с ключевыми полями, но с обязательным указанием имени класса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
DATA: gt_flights_keys TYPE STANDARD TABLE OF scol_flight_key, go_pm TYPE REF TO if_os_persistency_manager, gs_cn TYPE SEOCLSKEY, gt_spflights TYPE osreftab. go_pm = cl_os_system=>get_persistency_manager( ). " 1. Получаем ключевые поля для инициализации объектов SELECT * FROM sflight INTO CORRESPONDING FIELDS OF TABLE gt_flights_keys. " 2. Получаем список всех объектов TRY. gs_cn-clsname = 'ZCL_SFLIGHT'. gt_sflights = go_pm->get_persistent_by_key_tab( i_class_name = gs_cn i_key_tab = gt_flights_keys ). CATCH cx_os_class_not_found. CATCH cx_os_object_not_found. ENDTRY. |
Преобразование объектов в структуры и обратно
Многие сервисы в R/3, как например ALV, не работают напрямую с объектами. Чтобы сформировать ALV, необходимо передать в качестве параметра внутреннюю таблицу. Допустим нам необходимо перенести все хранимые атрибуты объекта в произвольную структуру (имена полей и хранимых атрибутов должны совпадать), для этого напишем следующий код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
METHOD object_to_structure. DATA: lo_str_descr TYPE REF TO cl_abap_structdescr, lv_method TYPE string. FIELD-SYMBOLS: <ls_component> TYPE abap_compdescr, <lv_component> TYPE any. TRY. lo_str_descr ?= cl_abap_structdescr=>describe_by_data( ch_structure ). CATCH cx_sy_move_cast_error. RETURN. ENDTRY. LOOP AT lo_str_descr->components ASSIGNING <ls_component>. ASSIGN COMPONENT <ls_component>-name OF STRUCTURE ch_structure TO <lv_component>. CHECK sy-subrc = 0. lv_method = 'GET_' && <ls_component>-name. TRY. CALL METHOD im_object->(lv_method) RECEIVING result = <lv_component>. CATCH cx_sy_dyn_call_illegal_method cx_os_object_not_found. CONTINUE. ENDTRY. ENDLOOP. ENDMETHOD. |
В коде мы динамически формируем имя метода для получения хранимого атрибута, вызываем его и результат записываем в поле структуры. Получать структуру из объекта мы уже умеем, но для ALV нам нужна таблица, следующий код будет заполнять таблицу относительно таблицы ссылок:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
METHOD objects_to_table. DATA: ls_structure TYPE REF TO DATA, lo_tab_descr TYPE REF TO cl_abap_tabledescr, lo_str_descr TYPE REF TO cl_abap_structdescr, lo_object TYPE REF TO object. FIELD-SYMBOLS: <ls_line> TYPE ANY. IF im_objects_table IS INITIAL. RETURN. ENDIF. CLEAR ch_table. lo_tab_descr ?= cl_abap_tabledescr=>describe_by_data( ch_table ). TRY. lo_str_descr ?= lo_tab_descr->get_table_line_type( ). CATCH cx_sy_move_cast_error. RETURN. ENDTRY. CREATE DATA ls_structure TYPE HANDLE lo_str_descr. ASSIGN ls_structure->* TO <ls_line>. LOOP AT im_objects_table INTO lo_object. CLEAR <ls_line>. zcl_po_utils=>object_to_structure( EXPORTING im_object = lo_object CHANGING ch_structure = <ls_line> ). APPEND <ls_line> TO ch_table. ENDLOOP. ENDMETHOD. |
Ну и последний метод – заполнение таблицы относительно объектов класса (статус объекта заполняется опционально):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
METHOD agent_objects_to_table. DATA: lt_buffer TYPE ostyp_ref_tab, lt_all_objects TYPE ostyp_ref_tab. CHECK im_agent IS BOUND. IF im_loaded = abap_true. lt_buffer = im_agent->if_os_ca_instance~get_loaded( ). APPEND LINES OF lt_buffer TO lt_all_objects. ENDIF. IF im_created = abap_true. lt_buffer = im_agent->if_os_ca_instance~get_created( ). APPEND LINES OF lt_buffer TO lt_all_objects. ENDIF. IF im_deleted = abap_true. lt_buffer = im_agent->if_os_ca_instance~get_deleted( ). APPEND LINES OF lt_buffer TO lt_all_objects. ENDIF. IF im_changed = abap_true. lt_buffer = im_agent->if_os_ca_instance~get_changed( ). APPEND LINES OF lt_buffer TO lt_all_objects. ENDIF. IF im_not_loaded = abap_true. lt_buffer = im_agent->if_os_ca_instance~get_not_loaded( ). APPEND LINES OF lt_buffer TO lt_all_objects. ENDIF. IF im_transient = abap_true. lt_buffer = im_agent->if_os_ca_instance~get_transient( ). APPEND LINES OF lt_buffer TO lt_all_objects. ENDIF. IF lt_all_objects IS INITIAL. RETURN. ENDIF. zcl_po_utils=>objects_to_table( EXPORTING im_objects_table = lt_all_objects CHANGING ch_table = ch_table ). ENDMETHOD. |
Пример отчета, выводящего данные запроса в ALV:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
DATA: go_query_manager TYPE REF TO if_os_query_manager, go_query TYPE REF TO if_os_query, gt_result TYPE osreftab, gt_sflight TYPE STANDARD TABLE OF sflight, go_salv_table TYPE REF TO cl_salv_table, go_sflight_agent TYPE REF TO zca_sflight. go_sflight_agent = zca_sflight=>agent. go_query_manager = cl_os_system=>get_query_manager( ). go_query = go_query_manager->create_query( i_filter = 'FLDATE <= ''' && '20130306''' ). go_sflight_agent->if_os_ca_persistency~get_persistent_by_query( EXPORTING i_query = go_query RECEIVING result = gt_result ). zcl_po_utils=>objects_to_table( EXPORTING im_objects_table = gt_result CHANGING ch_table = gt_sflight ). cl_salv_table=>factory( IMPORTING r_salv_table = go_salv_table CHANGING t_table = gt_sflight ). go_salv_table->display( ). |
В некоторых случаях, удобно заполнять объект на основе структуры, реализация аналогична заполнению структуры, только используется вызов метода SET:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
METHOD STRUCTURE_TO_OBJECT. DATA: lo_str_descr TYPE REF TO cl_abap_structdescr, lt_params TYPE abap_parmbind_tab, ls_param TYPE abap_parmbind, lv_method TYPE string. FIELD-SYMBOLS: <ls_component> TYPE abap_compdescr, <lv_component> TYPE any. TRY. lo_str_descr ?= cl_abap_structdescr=>describe_by_data( im_structure ). CATCH cx_sy_move_cast_error. RETURN. ENDTRY. LOOP AT lo_str_descr->components ASSIGNING <ls_component>. CLEAR: lt_params, ls_param. ASSIGN COMPONENT <ls_component>-name OF STRUCTURE im_structure TO <lv_component>. CHECK sy-subrc = 0. lv_method = 'SET_' && <ls_component>-name. ls_param-name = 'I_' && <ls_component>-name. ls_param-kind = cl_abap_objectdescr=>exporting. GET REFERENCE OF <lv_component> INTO ls_param-value. INSERT ls_param INTO TABLE lt_params. TRY. CALL METHOD im_object->(lv_method) PARAMETER-TABLE lt_params. CATCH cx_os_object_not_found cx_sy_dyn_call_error. CONTINUE. ENDTRY. ENDLOOP. ENDMETHOD. |
Пользовательская проверка при изменении атрибутов
В ходе использования хранимых объектов и их атрибутов перед разработчиком встает задача контроля входных, выходных данных в SET_ GET_ методах. Изменять методы напрямую нельзя, т.к. при повторной генерации хранимого класса они будут перезатерты стандартным кодом.
Выйти из этой ситуации можно несколькими способами:
- Сделать атрибуты приватными, для доступа к хранимым атрибутам в таком случае придётся писать свои дополнительные методы, например: READ_ATTRNAME, WRITE_ATTRNAME. Из-за объема необходимого к написанию кода данный способ не является оптимальным.
- Создать дочерний хранимый класс, в котором надо будет переопределить методы доступа к атрибутам и добавить необходимые проверки. Настройки сопоставления наследуются от родительского класса. Нагромождение классов, исходя только из необходимости дополнительных проверок, так же не является самым лучшим решением.
- В качестве обертки над хранимым классом создать обычный класс с доп. проверками при доступе к хранимым атрибутам. По своей сложности сопоставим с предыдущим.
- Ну и наконец, самым лучшим с точки зрения оптимальности, является способ использования Enhancement Framework. Дополнительная проверка в таком случае вставляется через расширение метода перед началом выполнения основного кода (неявная точка расширения), при повторной генерации класса, затирается только сгенерированная часть, проверки в расширении остаются не тронутыми.
Загрузка вложенных объектов без хранимых атрибутов
Рассмотренные ранее ссылочные атрибуты, когда один объект, через свой атрибут, ссылается на другой и система его автоматически подгружает, не могут быть использованы для связи один ко многим. Реализовать подобную схему можно следующим образом:
- Внутри класса создать временный атрибут содержащий таблицу ссылок на вложенные объекты и флаг, указывающий на то, что они были загружены. В качестве примера возьмем хранимый класс «факультет» включающий в себя внутреннюю таблицу со ссылками на объекты хранимого класса «студент» — gt_students (TYPE STANDART TABLE OF REF TO zcl_student). Флаг загруженности – students_loaded.
- Создать метод для получения внутренней таблицы со ссылками на студентов – get_students, метод должен содержать примерно следующий код (обработка исключений опущена):
12345678910111213141516171819202122232425262728293031DATA:lo_qm TYPE REF TO if_os_query_manager,lo_query TYPE REF TO if_os_query,lo_ca_student TYPE REF TO zca_student,lo_students TYPE osreftab,lo_student TYPE REF TO zcl_student.FIELD-SYMBOLS: <lo_ref> TYPE REF TO object.IF me->students_loaded = abap_false.lo_qm = cl_os_system=>get_query_manager( ).lo_query = lo_qm->create_query(i_filter = 'PROFID = PAR1').lo_ca_student = zca_student=>agent.lo_students = lo_ca_student->if_os_ca_persistency~get_persistent_by_query(i_query = lo_queryi_par1 = me->get_id( )).LOOP AT lo_students ASSIGNING <lo_ref>.lo_student ?= <lo_ref>.APPEND lo_student TO me->gt_students.ENDLOOP.me->students_loaded = abap_true.ENDIF.rv_students = me->gt_students.
По аналогии можно написать метод для получения ссылки на конкретного студента из внутренней таблицы, относительно его идентификатора. Метод должен быть вызван на этапе инициализации объекта «Факультет». - Так как объект «факультет» может быть удален из памяти PS (через метод RELEASE), надо позаботиться о том, чтобы инициализировать временные атрибуты — таблицу со ссылками на студентов и флаг. Делается это через определение метода INVALIDATE:
12CLEAR me->gt_students.me->students_loaded = abap_false.
В следующей части будут рассмотрены вопросы интеграции с системой блокировок SAP и переопределение методов доступа к данным.
Отличный цикл статей про abap-object-services-persistence, спасибо.
Когда ожидается след.часть про интеграцию с системой блокировок и переопределение методов?
Спасибо за комментарий, на следующей неделе точно ожидается.