ABAP Object Services — Persistence Service — часть 1.

ormПри разработке с использованием ABAP Objects разработчик неизбежно сталкивается с ситуацией, когда данные нужно либо загружать из каких-либо источников данных (БД, файлы и т.п.) в объекты, либо выгружать из объектов куда-либо.

Системы, решающие задачу сопоставления информации из БД в объекты и обратно принято называть ORM системами.

В объектно-ориентированном программировании объекты в программе представляют объекты из реального мира. В качестве примера можно рассмотреть адресную книгу, которая содержит список людей с нулём или более телефонов и нулём или более адресов. В терминах объектно-ориентированного программирования они будут представляться объектами класса «Человек», которые будут содержать следующий список полей: имя, список (или массив) телефонов и список адресов.

Суть задачи состоит в преобразовании таких объектов в форму, в которой они могут быть сохранены в файлах или базах данных, и которые легко могут быть извлечены в последующем, с сохранением свойств объектов и отношений между ними. Эти объекты называют «хранимыми» (англ. persistent).  В концепции SAP понятие «хранимый» объект заменено понятием «постоянный». В дальнейшем в статье будет использовано понятие «хранимый».

Работа с хранимыми объектами осуществляется с помощью встроенной в AS ABAP технологии ABAP Object Services. Данный инструмент можно разделить на три составляющие:

  • Инструмент загрузки и сохранения объектов (Persistence Service)
  • Инструмент поиска в объектах по определенным критериям (Query Service)
  • Инструмент обработки транзакций (Transaction Service)

Введение в Persistence Service

В большинстве языков программирования объекты являются временными данными и хранятся непосредственно в памяти во время выполнения программы (в ABAP до момента, когда сборщик мусора их удалит). Обычно для сохранения или загрузки данных из объектов в БД понадобится писать специальные методы преобразования данных объекта в реляционную структуру и наоборот (например, сериализовать/десериализовать объект в XML и сохранить в таком виде в таблице). В качестве более простой альтернативы можно использовать Persistence Service, который представляет из себя отдельный программный слой, скрывающий от разработчика большинство деталей по обработке данных.

Persistence service

Persistence Service позволяет связывать атрибуты хранимого объекта с полями в таблице базы данных, ракурсе или структуре. Каждая отдельная запись в таблице или ракурсе будет связана с отдельным объектом.

Есть несколько способов связывания хранимых объектов с данными в таблицах, ракурсах:

  • Связывание одного класса с одной записью в таблице (ракурсе). Данный способ используется, когда все данные объекта хранятся в рамках одной записи таблицы/ракурса.
  • Связывание одного класса с несколькими записями в разных таблицах (ракурсах). Применяется, когда данные класса разбросаны по нескольким таблицам (У таблиц должен быть один бизнес ключ, либо один общий GUID – глобальный идентификатор). В реальной жизни чаще встречаются связи таблиц, где есть основная таблица, например, заголовок закупочного заказа (EKKO) и таблица с позицией (EKPO). Так как бизнес ключ у них разный, (в EKPO есть ключевое поле позиция, которого нет в EKKO) данный способ связи не подойдет.
  • Связывание иерархий классов с записями в таблицах (ракурсах). Реализуется через ООП наследование. Наследование может быть двух видов: горизонтальным и вертикальным, подробное описание будет ниже.

Ракурсы используются в тех случаях, когда надо связать не все табличные поля с атрибутами хранимого класса.

Связывание со структурой из словаря используется в тех случаях, когда вы сами определяете методы для связи с объектом хранения информации (БД, файлом и т.п.) и сами заполняете данными хранимые объекты.

Далее в статье в качестве источника данных всегда будет рассматриваться БД.

Чтобы создать класс для хранимых объектов, необходимо при его создании выставить специальный флаг:­­

persistence flag

При создании хранимого класса, система автоматически генерирует код для манипуляции с объектами этого класса. Генерируемый код включает методы: чтения и изменения атрибутов объекта; загрузки объектов; сохранения объектов; удаление объектов и др. Если объект связан с БД, все эти методы генерируют SQL без участия программиста, в отдельных случаях программист может переопределить методы связывающие атрибуты хранимого объекта и базу данных.

При работе с хранимыми объектами, система позволяет создать в рамках рабочего процесса только один уникальный экземпляр для каждого хранимого объекта (не может быть создано несколько одинаковых хранимых объектов с одним и тем же ключом). Если во время работы программы потребуется получить ссылку на уже инициализированный хранимый объект, Persistence Service вернет ссылку на полученный ранее объект, без использования базы данных (получит ссылку из памяти). Таким образом, мы избегаем возможной несогласованности данных, если бы мы работали с несколькими одинаковыми объектами в рамках одного рабочего процесса и избежать лишней нагрузки на БД при получении объекта из памяти.

В специальном атрибуте агента класса (см. ниже), сохраняются все хранимые объекты. Объекты со статусом NOT_LOADED, хранятся только по слабой ссылке, что позволяет сборщику мусора в нужный ему момент их удалять (например, если не хватает памяти). Объекты с другими статусами хранятся как по слабой ссылке, так и по «сильной ссылке».

Так как объекты PS всегда хранятся в системной памяти, сборщик мусора не удаляет хранимые объекты, когда на них нет ссылок внутри программы. Сделано это для того, чтобы потом не загружать объект заново.  Если Вам необходимо сократить объем выделенной в приложении памяти, можно освободить неиспользуемые более хранимые объекты вручную, вызвав специальный метод. (IF_OS_FACTORY~RELEASE см. ниже).

Каждый хранимый объект имеет уникальный идентификатор, который отличает его от остальных объектов. Существует два типа идентификаторов:

  • Бизнес ключ, может состоять из определенных пользователем ключевых полей, всех допустимых типов кроме OS_GUID. Если вы используете бизнес ключ, вы должны связать ключевые поля из таблицы с атрибутами хранимого объекта. Если используется структура, то Вы сами решаете какое поле будет являться ключевым:business key
  • GUID (глобальный идентификатор) генерируемый Persistence Service при создании хранимого объекта. Если вы используете GUID, в таблице, с которой связан хранимый объект, должно быть поле с типом OS_GUID. Данное поле может быть как ключевым полем таблицы, так и нет. Если вдруг, по какой-то причине, понадобится генерация GUID ручным образом, можно использовать класс: cl_system_uuid. При работе с объектами на основе GUID, может возникать ситуация, когда в БД сохранились записи с одним идентификатором, а класс уже получил новый GUID (после удаления и создания класса заново). После чего при попытке получить по GUID ссылку на объект будет вызываться исключение CX_OS_CLASS_NOT_FOUND (класс не найден). Для решения проблемы смотрите ноту 567253.

Persistence Service позволяет работать со ссылочными атрибутами на другие хранимые объекты. К примеру, если хранимый объект имеет атрибут А со ссылкой на хранимый объект Б, система сохранит ссылку на хранимый объект Б в базе данных. А когда объект А будет загружаться из БД, объект Б по сохраненной ранее ссылке будет восстановлен. В целях производительности Persistence Service не загружает связанные таким образом объекты (объект Б) до момента их реального использования в программе («ленивая загрузка»). Это следует запомнить т.к. в случае отладки, может возникнуть вопрос, почему объект получен, но все еще пустой.

По типу хранения атрибуты могут быть двух видов:

  • Хранимые атрибуты. Содержимое хранимых атрибутов которые были изменены, после выполнения COMMIT WORK, сохраняется в связанной с этим атрибутом таблице, а при инициализации объекта загружается из неё. Загрузка хранимых атрибутов происходит только в момент доступа к какому-либо атрибуту через соответствующий метод. Доступ к ним возможен только через сгенерированные методы, несмотря на то, что доступ у такого атрибута будет Public.
  • Временные атрибуты. Содержимое временных атрибутов не сохраняется в БД и существует только во время выполнения программы.  При первоначальной загрузке хранимого объекта из БД, эти атрибуты имеют свое первоначальное (intial) значение. Подобные атрибуты могут использоваться, например для каких либо расчётных полей.

Следует запомнить, что для табличных полей должен быть явно определен тип в словаре, иначе их нельзя будет связывать с атрибутами классов. Т.е. если вы захотите использовать встроенный тип в таблице:

table key

Система не даст вам сделать сопоставление этого поля в атрибут класса.

Сопоставление с атрибутами хранимых классов

Далее на основе демонстрационной модели с полётами (пакет SAPBC_DATAMODEL) рассмотрим пример создания хранимого класса. Запускаем транзакцию SE24 и создаем класс с именем ZCL_SFLIGHT:

new class
Переходим к связыванию данных из таблицы SFLIGHT с атрибутами нашего класса:

sflight connect

sflight connect 2
На следующем экране необходимо связать поля из таблицы с атрибутами класса, т.к. в таблице SFLIGHT нет поля с типом OS_GUID, в качестве ключа будут использоваться ключевые поля из таблицы (бизнес ключ). Ключевые поля в классе будут доступны только для чтения. На данном этапе можно переопределить имя и описание для атрибута, видимость методов для доступа к атрибуту и необходимость создавать метод для записи атрибута (только чтение):

sflight attributes

Для некоторых полей на данном диалоге можно выбрать тип присвоения (на рисунке С – Атрибут значения), все они будут рассмотрены на примерах более подробно.

Для каждого отображения создается запись в системных таблицах: SEOMAPATT и SEOMAPCLS. Если в ходе разработки к таблицам, связанным с хранимыми классами, добавятся поля (не ключевые), классы по-прежнему будут активны и никаких проблем с их работой не возникнет. Но если вы хотите добавить новые поля к классу, понадобится заново перейти в диалог сопоставления и связать все новые поля с атрибутами класса, после чего активировать класс.

Следующим шагом сохраняем наш класс и активируем его. После сохранения будут сгенерированы методы доступа к атрибутам класса и специальные классы для манипулирования хранимыми объектами нашего класса.

Не рекомендуется вносить изменения в SET_ и GET_ методы, т.к. повторная активация класса связанная с изменением связывания объекта приведет к их повторной генерации и все пользовательские изменения будут затерты (для добавления в эти методы каких-либо своих проверок, можно воспользоваться специальными методиками, будут рассмотрены отдельно).

generated methods

Если потребуется создать временные атрибуты, их можно создать, так же, как и обычные атрибуты класса, непосредственно на закладке атрибуты.

Компоненты Persistence Service

Прежде чем работать с созданными классами, необходимо понять, каким образом с ними можно работать и что они из себя представляют.

Persistence Service это программный слой состоящий из основной части (системных классов) и части относящейся непосредственно к хранимому классу (классы генерируемые на этапе сохранения/активации). На следующем изображении показаны составные части PS:

pscomp

CL_persistent

Данный класс создается Вами в строителе классов (созданный ранее ZCL_SFLIGHT). Нельзя создать объект этого класса непосредственно через оператор CREATE OBJECT, т.к. в его свойствах выставлен параметр CREATE PRIVATE или CREATE ABSTRACT.

Для создания класса необходимо будет воспользоваться специальными методами сгенерированного класса агента или актора (перевод SAP). Как было уже упомянуто выше в примере, система сама создает методы для доступа к атрибутам класса.

Каждый хранимый класс реализует интерфейс IF_OS_STATE. В данном интерфейсе можно переопределить следующие методы:

  • HANDLE_EXCEPTION. Данный метод вызывается, если произошло исключение в методах доступа к атрибутам. В данном методе можно обработать вызванное исключение, однако обычно это делает тот, кто использует методы доступа к атрибутам.
  • INIT. Вызывается при создании временного или хранимого объекта сразу после того как были считаны хранимые атрибуты. В данном методе можно инициализировать временные атрибуты, назначить обработчики событий и т.п.
  • INVALIDATE. Вызывается при удалении хранимого объекта, обновления его хранимых атрибутов из БД. Тут вы можете вновь инициализировать временные атрибуты, переназначить обработчики событий и т.п.

Остальные методы (SET,GET) являются системными и их нельзя переопределять. Вообще, что касается переопределения, увидеть разрешено оно или нет, можно увидеть непосредственно в коде:

donotmodify

modify

CA_persistent и CB_persistent

Для каждого хранимого класса, среда создает еще два класса начинающихся с префикса CA_ (агент класса) и CB_ (базовый агент). Эти два класса относятся к части Persistence Service специфичной для конкретного хранимого класса. CA_ является наследником от CB_, который в свою очередь наследуется от класса CL_OS_CA_COMMON.

Класс, с префиксом CA_ является так называемым агентом (актором) класса CL_, данный класс реализует шаблон одиночки (т.е. в рамках рабочего процесса может существовать всего одна его инстанция) доступ к которому можно получить через статический атрибут agent:

agentattribute

Когда вы первый раз обращаетесь к этому атрибуту, в статическом конструкторе агента происходит его инициализация.

Агент класса используется для управления объектами хранимого класса: созданием, удалением, обновлением и т.п. Программист может переопределять некоторые отдельные методы класса агента, в частности используется для переопределения доступа к БД. Класс CB_ не предназначен для изменения. При генерации CL_ класса в качестве его «друга» определяется автоматом класс агент:

friendship

Класс агент реализует следующие интерфейсы: IF_OS_FACTORY, IF_OS_CA_SERVICE, IF_OS_CA_PERSISTENCY и IF_OS_CA_INSTANCE. Вы можете использовать как методы интерфейсов, так и методы класса.

 

Методы, унаследованные от класса CB_

CREATE_PERSISTENT. Инициирует создания нового хранимого объекта. Интерфейс данного метода генерируется в зависимости от настроек сопоставления в хранимом классе. Если хранимый класс управляется бизнес ключом, в интерфейсе будут соответствующие поля. Для указанного выше примера ZCL_SFLIGHT:

sflight_keys

Если управление происходит через GUID, никаких дополнительных полей вы тут не увидите (т.к. GUID система генерирует самостоятельно). Когда Persistence Service инициализирует объект, система НЕ проверяет наличие объекта с теми же ключами в БД, ошибок происходить не будет до тех пор, пока вы не захотите сохранить объект, вызвав COMMIT WORK. Соответственно следует помнить, что перед созданием объекта, надо убедиться в том, что он отсутствует в БД, сделать это можно с помощью метода GET_PERSISTENT. Пример создания объекта:

Если в памяти PS уже есть объект с таким же ключом, система вызовет исключение — CX_OS_OBJECT_EXISTING.

GET_PERSISTENT. Загружает хранимый объект и создает экземпляр хранимого класса внутри программы. Интерфейс так же зависит от способа сопоставления в хранимом классе (может либо принимать на вход бизнес ключ, либо ничего, если используется GUID). Как уже было упомянуто выше, если объект ранее в программе уже был создан, Persistence Service вернет ссылку на него, при этом, не загружая его заново из базы данных.

DELETE_PERSISTENT. Удаляет хранимый объект, для удаления его данных из БД, необходимо вызвать COMMIT WORK. Интерфейс так же зависит от способа сопоставления в хранимом классе (может либо принимать на вход бизнес ключ, либо ничего, если используется GUID). Пример:

CREATE_TRANSIENT. Создает временный объект хранимого класса. Интерфейс аналогичен интерфейсу CREATE_ PERSISTENT. Временные объекты не связаны с каким-либо источником данных и используются только внутри программы.  Не существует способа автоматического преобразования временного объекта в постоянный объект. Чтобы сделать подобное нужно сначала сохранить данные временного объекта в каком-либо буфере, затем удалить временный объект из памяти PS, после чего создать хранимый объект и загрузить в него данные из буфера.

GET_TRANSIENT. Получает временный объект хранимого класса. Интерфейс аналогичен интерфейсу GET_ PERSISTENT. Объект должен быть создан с помощью метода CREATE_TRANSIENT.

 

Методы интерфейса IF_OS_FACTORY

IF_OS_FACTORY~CREATE_PERSISTENT. Создает хранимый объект. Используется для хранимых объектов, идентифицируемых через GUID, его генерация происходит в данном методе.

IF_OS_FACTORY~CREATE_PERSISTENT_BY_KEY. Создает хранимый объект. Используется если объект хранимого класса идентифицируется через бизнес ключ. В качестве входящего параметра i_key должна передаваться либо структура (составной ключ), либо строка. Структура должна быть с полями в определённом порядке (по алфавиту), чтобы узнать, что от вас ожидает система, можно открыть класс CB_ и посмотреть определение типа TYP_BUSINESS_KEY:

keys

IF_OS_FACTORY~REFRESH_PERSISTENT. Метод используется для обновления хранимого объекта (считывания данных происходит заново).  В качестве входного параметра I_OBJECT необходимо передать ссылку на хранимый объект. Данные будут считаны не сразу, а только в момент работы с каким-либо хранимым атрибутом. При обновлении хранимый объект может получить либо статус OSCON_OSTATUS_LOADED при успешной загрузке, либо OSCON_OSTATUS_NOT_LOADED. Сразу после вызова метода, до момента считывания, статус объекта меняется на OSCON_OSTATUS_NOT_LOADED. Вызывать данный метод можно только для объектов со статусами: OSCON_OSTATUS_NOT_LOADED, OSCON_OSTATUS_LOADED,  для объектов со статусами: CHANGED, NEW и DELETE вызовет исключение. Чтобы обновить измененные или удаленные объекты можно воспользоваться методом UNDO для транзакции (см. описание Transaction Service).    Приведу два дополнительных метода для обновления всех объектов относительно агента и для всех активных для текущей программы агентов:

refresh_by_agent

Обновление относительно всех активных агентов:

IF_OS_FACTORY~DELETE_PERSISTENT.Удаление хранимого объекта. В качестве входного параметра ожидает ссылку на хранимый объект.

IF_OS_FACTORY~CREATE_TRANSIENT. Если хранимый объект не идентифицируется через бизнес ключ, для создания временного объекта хранимого класса (не связанного с источником данных) можно использовать данный метод.

IF_OS_FACTORY~CREATE_TRANSIENT_BY_KEY. Создает временный объект хранимого класса. Используется, если хранимый объект идентифицируется через бизнес ключ. В качестве входящего параметра i_key должна передаваться либо структура (составной ключ), либо строка.

IF_OS_FACTORY~RELEASE. Удаляет хранимый объект из памяти Persistent Service. Может использоваться для объектов со статусами: OSCON_OSTATUS_LOADED и OSCON_OSTATUS_NOT_LOADED. При повторной инициализации хранимого объекта, он будет загружен из БД заново.

 

Методы интерфейса IF_OS_CA_PERSISTENCY

IF_OS_CA_PERSISTENCY~GET_PERSISTENT_BY_OID. Загружает хранимый объект и создает экземпляр хранимого класса внутри программы. Данный метод используется, когда хранимый объект не идентифицируется по бизнес ключу. Входной параметр ID объекта с типом OS_GUID:

IF_OS_CA_PERSISTENCY~GET_PERSISTENT_BY_KEY. Загружает хранимый объект и создает экземпляр хранимого класса внутри программы. Данный метод используется, когда хранимый объект идентифицируется по бизнес ключу. В качестве входящего параметра i_key должна передаваться либо структура (составной ключ), либо строка.

IF_OS_CA_PERSISTENCY~GET_PERSISTENT_BY_OID_TAB. Аналогичен методу GET_PERSISTENT_BY_OID, за исключением того что в данном методе объекты можно загрузить массово. Использование метода возможно, только если в генераторе стоит галочка:

mass_access

IF_OS_CA_PERSISTENCY~GET_PERSISTENT_BY_KEY_TAB. Аналогичен методу IF_OS_CA_PERSISTENCY~GET_PERSISTENT_BY_KEY, за исключением того что в данном методе объекты загружаются массово. Использовать можно так же только если стоит вышеуказанная галочка. Данный метод вызывает исключение, если в памяти есть объект с тем же ключом и статусом: OSCON_OSTATUS_TRANSIENT  и OSCON_OSTATUS_DELETED.Если какой-либо объект по указанным ключам не будет найден, исключение не будет вызвано (будет возвращена пустая ссылка). Пример массовой инициализации объектов:

IF_OS_CA_PERSISTENCY~GET_PERSISTENT_BY_QUERY. Данный метод используется для массового выбора объектов из базы данных по указанному в параметрах метода запросу. Запрос представлен в виде класса реализующего интерфейс IF_OS_QUERY. Данная методика относится к Query Service и будет рассматриваться далее. Объект запроса создается через метод CREATE_QUERY в классе, реализующем интерфейс IF_OS_QUERY_MANAGER.  Значения для сравнения в запросе передаются через таблицу I_PARAMETER_TAB, если для сравнения используется не более трех атрибутов, и они являются атрибутами элементарного типа, тогда можно использовать параметры I_PAR1-3. Вы можете использовать параметры I_SUBCLASSES и I_UPTO для того чтобы загружать подклассы и ограничить максимальное число возвращаемых объектов. Использовать можно, только если стоит галочка:

query methods

 

Методы интерфейса IF_OS_CA_INSTANCE

IF_OS_CA_INSTANCE~GET_STATUS. Получение статуса хранимого объекта относительно переданной ссылочной переменной.
IF_OS_CA_INSTANCE~GET_NOT_LOADED. Получение всех объектов (относительно класса агента) которые имеют статус OSCON_OSTATUS_NOT_LOADED (0).
IF_OS_CA_INSTANCE~GET_CREATED. Получение всех объектов (относительно класса агента) которые имеют статус OSCON_OSTATUS_NEW (1).
IF_OS_CA_INSTANCE~GET_LOADED. Получение всех объектов (относительно класса агента) которые имеют статус OSCON_OSTATUS_LOADED (2).
IF_OS_CA_INSTANCE~GET_CHANGED. Получение всех объектов (относительно класса агента) которые имеют статус OSCON_OSTATUS_CHANGED (3).
IF_OS_CA_INSTANCE~GET_DELETED. Получение всех объектов (относительно класса агента) которые имеют статус OSCON_OSTATUS_DELETED (4).
IF_OS_CA_INSTANCE~TRANSIENT. Получение всех объектов (относительно класса агента) которые имеют статус OSCON_OSTATUS_TRANSIENT (10).

 

Методы интерфейса IF_OS_CA_SERVICE

GET_OID_BY_REF. Если для идентификации используется GUID, данный метод позволяет по ссылке на объект его получить.
GET_REF_BY_OID. Если для идентификации используется GUID, данный метод позволяет получить по GUIDссылку на объект.

Одним из важнейших атрибутов агента класса является атрибут – OBJECT_INFO:

object_info

Внутри этой внутренней таблицы находится информация обо всех управляемых этим агентом объектах. Основные поля таблицы:

  • OBJECT_ID. Идентификатор объекта, используемый для каждого класса ABAP.
  • OBJECT_IREF. Ссылка на объект ABAP
  • PM_STATUS. Статус объекта
  • PM_DBSTATUS. Статус хранимого объекта, относительно БД. OSCON_DBSTATUS_UNKNOWN (0) – нет информации о том, есть ли объект в БД. OSCON_DBSTATUS_EXISTING (1) – объект существует в БД. OSCON_DBSTATUS_NOT_EXISTING (2) – объект не существует в БД.

 

Статусы хранимых объектов

Статус хранимого объекта позволяет определить, например, был ли объект сохранен, изменен или удален, перенесены ли эти изменения в БД и т.п. Статус отвечает за то, какие в данный момент времени можно вызывать методы, а какие приведут к ошибкам. Существует семь различных статусов. Для получения информации о статусе хранимого объекта, можно использовать метод GET_STATUS интерфейса  IF_OS_CA_INSTANCE, который реализован в агенте класса.

Имеющиеся статусы (группа типов OSCON):

Константа Значение Обозначение
OSCON_OSTATUS_NOT_LOADED 0 Объект создан, но данные из БД не считаны. Может возникать в случае связанных объектов (один является вложенным атрибутом другого), пример рассматривается ниже.
OSCON_OSTATUS_NEW 1 Создан новый хранимый объект
OSCON_OSTATUS_LOADED 2 Хранимый объект успешно загружен
OSCON_OSTATUS_CHANGED 3 Хранимый объект изменен
OSCON_OSTATUS_DELETED 4 Хранимый объект удален
OSCON_OSTATUS_TRANSIENT 10 Объект хранимого класса является временным, т.е. не связан с БД.
OSCON_OSTATUS_LOADING 12 Объект загружается. Данный статус может быть у объекта только в методе INITинтерфейса IF_OS_STATE.

Ниже представлена таблица методов и возможного вызова COMMIT WORK при различных статусах хранимых объектов, и к чему они приведут, т.е. вызову исключения (exc) или новому статусу:

Статус до вызова 0 1 2 3 4 10
CREATE_PERSISTENT 1 3 exc exc exc 3 exc
DELETE_PERSISTENT 4 0 4 4 4 exc
GET_PERSISTENT 2 2 1 2 3 exc exc
GET_attribute exc 2 1 3 3 exc 10
SET_attribute exc 3 1 3 3 exc 10
REFRESH_PERSISTENT exc 0 exc 0 exc exc exc
RELEASE exc exc exc exc exc
CREATE_TRANSIENT 10 exc exc exc exc exc exc
GET_TRANSIENT exc exc exc exc exc exc 10
COMMIT WORK 0 0 0 0 10

Далее диаграмма изменения статуса хранимого объекта, который еще не был создан в БД, при работе с ним:

no_in_db

Диаграмма изменения статуса хранимого объекта, который был загружен из БД, при работе с ним:

in_db

Как будет рассмотрено ниже в примерах, Persistence Service при загрузке объекта, атрибут которого является ссылкой на другой хранимый объект, не загружает ссылочный объект сразу, а делает это только при его использовании. До момента использования такой объект имеет статус – NOT_LOADED.

При удалении хранимого объекта, он помечается в памяти как удаленный, в БД удаление происходит только при подтверждении транзакции.

После пометки объекта как удалённый, вы можете создать новый объект с тем же идентификатором, при этом его статус изменится на changed.

Возможна ситуация, когда при изменении временных атрибутов хранимых объектов статус объекта изменится на OSCON_OSTATUS_CHANGED.  Таким образом, Persistence Service будет сохранять объект в БД, даже если не изменялись хранимые атрибуты, по умолчанию данное поведение отключено (Начиная с ASABAP 7.0 EhP2):

no_transient_status

Ранее подобное поведение нельзя было отключить, что могло приводить к значительным нагрузкам на БД при изменении большого числа объектов.

 

Ссылочные атрибуты

Объекты зачастую внутри себя содержат ссылки на другие объекты, например: «Студент» — «Пропуск», «Покупатель» — «Продавец» и т.п.

Persistence Service позволяет в качестве атрибута хранимого класса использовать ссылку на объект другого хранимого класса. Но для того чтобы иметь возможность сохранить подобный класс в базе данных, необходимо чтобы соблюдались некоторые условия:

  • Чтобы Persistence Service мог найти связанный (вложенный) класс, в таблице, где он хранится, должно существовать поле с типом OS_GUID (если другого ключа нет, должно быть ключевым).
  • В таблице, которая содержит данные вложенного объекта, должны быть два поля с типом OS_GUID, первое (ссылка на объект) поле будет идентификатором вложенного объекта, а второе (Ид. класса) идентификатором класса вложенного объекта (на одну и ту же таблицу может быть много классов).

При связывании атрибута содержащего хранимый объект, необходимо связать два поля с типом OS_GUID с данным атрибутом (при связывании для обоих полей OS_GUID имя атрибута должно быть одинаковым).

Рассмотрим пример. Таблица студентов:

students

Поле PASS_INSTANCE будет хранить ссылку на идентификатор объекта. Поле PASS_CLASS будет идентификатором класса. Поле ID будет содержать ключ студента.

Таблица с кодами пропуска:

pass

Так как в нашем примере объект пропуск не может существовать без объекта студент, отдельного ключевого поля для идентификации пропуска нам не нужно. Считывать пропуска всегда будем через объект студента.

Создаем класс ZCL_PASSKEY и связываем с таблицей ZPASSKEY.

Далее создаем класс ZCL_STUDENT, связываем с таблицей ZSTUDENTS. Поле PASS_INSTANCE объявляем, как поле со ссылкой на объект:

link_students

Для поля PASS_CLASSставим тип Ид. Класса и присваиваем с тем же именем:

class_students

В итоге получаем следующую связь:

class_links

Сохраняем класс и активируем.

Создадим программу создающую объект пропуска и студента и связывающую их вместе (обратите внимание, что для краткости в примерах опущена обработка исключений. Все исключения в Object Services являются ООП исключениями, как их обрабатывать смотрите в этой статье.):

Содержимое таблицы ZPASSKEY:

pass_tab

Содержимое таблицы ZSTUDENTS:

stud_tab

Считывание происходит следующим образом:

При удалении хранимого объекта «студент» из БД, «пропуск» не будет автоматически удаляться, если это необходимо, то вызвать удаление придётся вручную.

Реализовать вложенные хранимые объекты можно и вручную, без необходимости использования специальных полей типа OS_GUID в таблицах, но это потребует дополнительной разработки, например при инициализации хранимого объекта ZCL_STUDENT нужно будет переопределить метод INIT, в котором во временный атрибут PASSKEY положить хранимый объект класса ZCL_PASSKEY.

В данном примере связь между хранимыми классами осуществляется через атрибут, к сожалению, нет стандартной возможности связать с одним хранимым классом группу хранимых классов, чтобы эта группа загружалась автоматически (Например, объект заказа и объекты позиции). Подобное поведение можно реализовать только вручную.

 

Наследование

Наследование — механизм языка, позволяющий описать новый класс на основе уже существующего (родительского, базового) класса. Класс-потомок может добавить собственные методы и свойства, а также пользоваться родительскими методами и свойствами. Позволяет строить иерархии классов.

Persistent Service позволяет использовать механизм наследования для хранимых классов. При наследовании наследуется и то, каким образом родительский класс был связан с объектами в БД (наследование сопоставления). При этом в дочернем классе нельзя переопределить настройки сопоставления родительского класса.

Существует два способа при наследовании настроек сопоставления:

Вертикальное наследование. На верхнем уровне иерархии находится не абстрактный класс. В наследуемых классах можно определить новые атрибуты из других таблиц, структур или ракурсов, но при этом ключевые поля должны совпадать с ключевыми полями, объявленными в родительском классе. Обычно схема выглядит следующим образом:

vertical_inheritence

Класс cl_e_ticket наследуется от класса cl_ticket. Оба хранимых класса связаны с разными таблицами, но ключевые поля в этих таблицах одинаковые.

Существует особый случай вертикального сопоставления, когда атрибуты, объявленные в дочернем классе, ссылаются на ту же таблицу, ракурс или структуру что и атрибуты в родительском классе. Для того чтобы установить к какому именно классу относится запись в таблице, структуре или ракурсе, необходимо чтобы в них было специальное поле с типом OS_GUID. При сопоставлении табличного поля, атрибут в классе будет иметь тип идентификатор типа:

type_ident

Горизонтальное наследование. Если родительский класс является абстрактным, вы можете объявлять хранимые атрибуты без необходимости их сопоставления.  Настройка сопоставления атрибутов необходима только в дочерних (не абстрактных) классах, при этом они могут быть связаны с совершенно разными таблицами.

hor_inh

Как видно из рисунка могут быть не только разные таблицы, но и разные ключевые поля для наследуемых объектов.

Если при создании класса наследника, возникает проблема в том, что интерфейс IF_OS_STATE не может быть реализован дважды, просто перейдите на закладку интерфейсы и удалите лишнюю запись.

В следующей части будут рассмотрены темы: Преобразование хранимых объектов в структуры (таблицы) и обратно,  менеджер инстанций и менеджер постоянства, пользовательские проверки в методах изменения/получения атрибутов, интеграция с системой блокировок SAP, переопределение методов доступа к данным, Transaction Service и Query Service.

  • vxd.dev

    Интересно, насколько это решение применимо в реальных проектах. Пока даже представить не могу где это может понадобится. Спасибо за статью 🙂

  • Astrafox

    Был опыт реализации вручную слоя обмена данными между объектами и БД, когда эти обязанности выполняет за тебя система стало проще 🙂

  • vxd.dev

    Теперь и я попробую :). А в новых версиях сапа нет таких уже заранее созданных классов для работы со стандартными табличками? Смущает, что все равно нужно расширять классы блокировками, еще и переживать, что каждая активация может перезатереть все мои методы. Кстати, как быть, если есть уже сгенерированный рабочий класс, но мне нужно добавить поле в таблицу — не перезатрутся ли мои методы в этом случае?

    • Astrafox

      Во второй части будут рассмотрены эти вопросы, добавлю в блог на днях 😉

      • Astrafox

        Что касается добавления новых полей в таблицу, не ключевых, классы будут работать и без перегенерации. Если надо будет добавить новый хранимый атрибут, только тогда понадобится повторная активация.

  • Андрей

    Мы (один из стандартных модулей) это делаем «ручками» — у каждого класса из Application Layer есть корреспондирующий класс с Persistence Layer (если нужно, конечно) — со всеми преимуществами и недостатками.

    Интересно, насколько техника, используется в реальный проектах.