На сервере приложений одновременно может работать большое число пользователей, которые в свою очередь работают параллельно с одними и теми же программами, может случиться ситуация, когда они попытаются одновременно обработать один и тот же объект (например, изменить поставку, заказ на закупку и т.п.). Чтобы в системе не возникало противоречивости данных, были придуманы блокировки, более подробно о концепции блокировок читайте в предыдущей статье.
К сожалению, в ABAP Object Services нет встроенного механизма поддержки блокировок, что может привести к ошибкам и противоречивости данных при одновременном изменении одинаковых объектов.
Далее в ходе статьи мы разберем один из способов интеграции с системой блокировок.
Но прежде чем рассматривать механизм интеграции, необходимо отметить следующее:
- При использовании системы блокировок, программисты должны позаботиться о том, чтобы заблокированный объект был, как можно быстрее разблокирован.
- За установку и снятие блокировок, прежде всего, отвечает разработчик, но снятие блокировок может происходить и автоматически, данный аспект следует учитывать при интеграции с хранимыми объектами. Так если при выставлении блокировки был указан параметр _SCOPE= 2, система может автоматически снимет все блокировки при завершении транзакции верхнего уровня через метод UNDO или при вызове ROLLBACK WORK в режиме совместимости. Если транзакция верхнего уровня была завершена методом END, либо в режиме совместимости был вызван оператор COMMIT WORK, система снимет блокировки только в том случае, если был зарегистрирован хотя бы один модуль обновления (V1). Transaction Service регистрирует модуль обновления, если не было указано что обновления необходимо производить напрямую (direct update) и если хранимый объект был изменен (по умолчанию, касается только изменения хранимых атрибутов). Если вы используете прямое обновление или хранимый объект не изменялся, при выставленном параметре _SCOPE = 2, блокировки снимаются при завершении SAP LUW, либо принудительно через модуль разблокирования DEQUEUE_*.
Пессимистичные и оптимистичные блокировки
Существует две основные стратегии блокирования: пессимистичная и оптимистичная. Стратегия, в данном случае, определяет порядок действий, выполняемых при блокировании и разблокировании объектов, а также поведение системы при обработке объектов блокировки.
- Оптимистичная стратегия подразумевает проверку на блокировку непосредственно перед записью в БД, т.е. множество пользователей в один момент времени могут изменять какой-либо объект (выставлена оптимистичная блокировка), но записывать эти изменения в БД можно только после преобразования оптимистичной блокировки в эксклюзивную. При этом после преобразования, все другие оптимистичные блокировки снимаются. Основное преимущество в такой стратегии, блокировка объекта происходит в достаточно короткие временные сроки.
- Пессимистичная стратегия подразумевает блокировку непосредственно до начала выполнения какого-либо действия над объектом, т.е. чтобы начать изменять объект выставляется блокировка и если она успешна, интерфейс из состояния чтения объекта переходит в состояние изменения. После сохранения данных объекта блокировка снимается.
Для того чтобы иметь возможность редактировать объект и быть уверенными в том, что блокировка установлена в классе агенте мы реализуем следующий метод:
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 50 51 52 53 54 55 56 |
METHOD lock_and_get_persistent. DATA: lv_status TYPE string, ls_key TYPE typ_business_key, lv_text TYPE string. ro_persistent = me->get_persistent( i_carrid = iv_carrid i_connid = iv_connid i_fldate = iv_fldate ). ls_key-carrid = ro_persistent->get_carrid( ). ls_key-connid = ro_persistent->get_connid( ). ls_key-fldate = ro_persistent->get_fldate( ). CHECK iv_enqueue_mode IS NOT INITIAL. READ TABLE gt_locks WITH KEY carrid = ls_key-carrid connid = ls_key-connid fldate = ls_key-fldate TRANSPORTING NO FIELDS. CHECK sy-subrc NE 0. CALL FUNCTION 'ENQUEUE_ESFLIGHT' EXPORTING mode_sflight = iv_enqueue_mode carrid = ls_key-carrid connid = ls_key-connid fldate = ls_key-fldate _scope = '3' EXCEPTIONS foreign_lock = 1 system_failure = 2 OTHERS = 3. IF sy-subrc <> 0. MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno INTO lv_text WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4. RAISE EXCEPTION TYPE zcx_os_object_not_blocked EXPORTING text = lv_text. ENDIF. APPEND ls_key TO gt_locks. " После установки блокировки, необходимо обновить хранимый объект TRY. me->if_os_factory~refresh_persistent( ro_persistent ). CATCH cx_os_object_not_refreshable. ENDTRY. ENDMETHOD. |
В методе присутствует входной параметр iv_enqueue_mode, внутри которого будем указывать стратегию блокировки, используемую для загружаемого объекта:
- «Е» — пессимистичная стратегия
- «О» — оптимистичная стратегия
Внутри метода происходит повторная загрузка хранимого объекта из БД и вызов модуля установки блокировки.
Благодаря параметру _scope = ‘3’ мы точно будем уверены в том, что блокировка не будет снята автоматически.
Так же был добавлен свой класс исключения zcx_os_object_not_blocked, данный класс уведомляет нас о невозможности продолжения работы с объектом из-за ошибки при выставлении блокировки. Подробнее о классах исключений смотрите тут. В классе исключения определен текст внутри которого выводится переданный в параметрах исключения text:
Все заблокированные нашей программой объекты будут храниться в атрибуте класса агента с табличным типом TYP_BUSINESS_KEY_TAB:
При повторном вызове метода для уже заблокированного объекта, модуль блокировки запускаться не будет. Кроме того надо иметь в виду, что вызывать данный метод необходимо перед редактированием хранимого объекта.
Тестовая программа, выводящая ошибку, в случае невозможности блокирования объекта (объект уже заблокирован эксклюзивной блокировкой):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
DATA: go_flight TYPE REF TO zcl_spfliht, go_agent TYPE REF TO zca_spfliht, go_error TYPE REF TO zcx_os_object_not_blocked, gv_error TYPE string. go_agent = zca_spfliht=>agent. TRY. go_flight = go_agent->lock_and_get_persistent( iv_enqueue_mode = 'E' iv_carrid = 'AA' iv_connid = 17 iv_fldate = '20130206' ). CATCH zcx_os_object_not_blocked INTO go_error. gv_error = go_error->get_text( ). WRITE gv_error. EXIT. ENDTRY. |
Результат обработки исключения:
Следующий шаг после изменения полученного объекта определяется в зависимости от стратегии блокировки, для пессимистичной стратегии необходимо чтобы после сохранения данных объекта в БД, выставленные ранее блокировки были сняты.
Для этого в классе агенте создан специальный метод снятия блокировки:
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 |
method UNLOCK_OBJECT. DATA: ls_key TYPE typ_business_key. TRY. ls_key-carrid = io_persistent->get_carrid( ). ls_key-connid = io_persistent->get_connid( ). ls_key-fldate = io_persistent->get_fldate( ). CATCH cx_os_object_not_found. RETURN. ENDTRY. DELETE TABLE gt_locks WITH TABLE KEY carrid = ls_key-carrid connid = ls_key-connid fldate = ls_key-fldate. IF sy-subrc = 0. CALL FUNCTION 'DEQUEUE_ESFLIGHT' EXPORTING mode_sflight = 'E' carrid = ls_key-carrid connid = ls_key-connid fldate = ls_key-fldate. CALL FUNCTION 'DEQUEUE_ESFLIGHT' EXPORTING mode_sflight = 'O' carrid = ls_key-carrid connid = ls_key-connid fldate = ls_key-fldate. ENDIF. 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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
METHOD convert_lock. DATA: ls_key TYPE typ_business_key, lv_key TYPE eqegraarg, lv_text TYPE string. TRY. ls_key-carrid = io_persistent->get_carrid( ). ls_key-connid = io_persistent->get_connid( ). ls_key-fldate = io_persistent->get_fldate( ). CATCH cx_os_object_not_found. MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno INTO lv_text WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4. RAISE EXCEPTION TYPE zcx_os_object_not_blocked EXPORTING text = lv_text. ENDTRY. READ TABLE gt_locks WITH TABLE KEY carrid = ls_key-carrid connid = ls_key-connid fldate = ls_key-fldate TRANSPORTING NO FIELDS. IF sy-subrc NE 0. RAISE EXCEPTION TYPE zcx_os_object_not_blocked EXPORTING text = 'Объект не блокирован, преобразование невозможно'. ENDIF. CALL FUNCTION 'ENQUEUE_ESFLIGHT' EXPORTING mode_sflight = 'R' carrid = ls_key-carrid connid = ls_key-connid fldate = ls_key-fldate _scope = '3' EXCEPTIONS foreign_lock = 1 system_failure = 2 OTHERS = 3. IF sy-subrc <> 0. MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno INTO lv_text WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4. IF sy-subrc = 1. " Блокировка уже преобразована, если блокировка установлена не " нами, сбросим внутренние блокировки lv_key = ls_key. lv_key = sy-mandt && lv_key. IF zenqueue_tools=>check_enqueue( im_table_name = 'SFLIGHT' im_key = lv_key im_mode = 'E' ) = abap_false. DELETE TABLE gt_locks WITH TABLE KEY carrid = ls_key-carrid connid = ls_key-connid fldate = ls_key-fldate. ELSE. " Если блокировка была преобразована нами, " выйдем RETURN. ENDIF. ENDIF. RAISE EXCEPTION TYPE zcx_os_object_not_blocked EXPORTING text = lv_text. ENDIF. 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 |
METHOD check_enqueue. DATA: lt_enqueues TYPE STANDARD TABLE OF seqg3, lv_wpnum TYPE wpinfo-wp_no. CALL FUNCTION 'ENQUEUE_READ' EXPORTING gname = im_table_name garg = im_key TABLES enq = lt_enqueues EXCEPTIONS communication_failure = 1 system_failure = 2 OTHERS = 3. IF sy-subrc <> 0. rv_ownwp_enqueue = abap_false. RETURN. ENDIF. CALL FUNCTION 'TH_GET_OWN_WP_NO' IMPORTING wp_no = lv_wpnum. READ TABLE lt_enqueues WITH KEY gtwp = lv_wpnum gmode = im_mode TRANSPORTING NO FIELDS. IF sy-subrc = 0. rv_ownwp_enqueue = abap_true. ELSE. rv_ownwp_enqueue = abap_false. ENDIF. ENDMETHOD. |
После успешного преобразования может быть вызван COMMIT WORK или метод транзакции END. Закончив сохранения объекта в БД, можно вызвать снятие блокировок через метод рассмотренный ранее.
Описанная в данной статье схема интеграции с системой блокировок является лишь одной из возможных, в качестве альтернативной можно использовать автоматическое выставление блокировок до момента загрузки хранимого объекта из БД, делается это через переопределение метода в классе агенте: MAP_LOAD_FROM_DATABASE_KEY — если бизнес ключ на основе ключевых полей таблицы, MAP_LOAD_FROM_DATABASE_GUID – если бизнес ключ на основе GUID.
Можно так же автоматически снимать блокировки через агента проверки консистентности (класс реализующий интерфейс IF_OS_CHECK) хранимого объекта, но подобная автоматизация возможна только для ООП транзакций, т.к. исключения выдаваемые в агентах проверки невозможно обработать в транзакциях в режиме совместимости.
Подробнее об альтернативной схеме интеграции с системой блокировок можете прочитать в книге: Object Services in ABAP.
Спасибо! Все очень доходчиво.
Пожалуйста!