entity
Библиотека Entity
предназначена для работы с данными БД как с простыми OneScript объектами. Является реализацией концепции ORM и шаблона DataMapper
в OneScript. Вдохновение черпается из Java Persistence API и TypeORM.
Возможности:
Описание публичного интерфейса - каталог docs.
Сущность - это обычный класс OneScript, размеченный служебными аннотациями. Обязательными аннотациями являются &Сущность
и &Идентификатор
.
Библиотека entity
считывает состав аннотаций класса, строит модель данных и инициализирует таблицы базы данных для работы с объектами данного класса.
Ограничения:
// file: СтраныМира.os
// Данный класс содержит данные о странах мира.
&Идентификатор // Колонка для хранения ID сущности
Перем Код Экспорт; // Колонка по умолчанию имеет строковый тип
Перем Наименование Экспорт; // Колонка `Наименование` будет создана в таблице, т.к. поле экспортное.
&Сущность // Объект с типом "СтраныМира" будет представлен в СУБД как таблица "СтраныМира"
Процедура ПриСозданииОбъекта()
КонецПроцедуры
// file: Документ.os
&Идентификатор
&ГенерируемоеЗначение // Заполняется автоматически при сохранении сущности
&Колонка(Тип = "Целое") // Хранит целочисленные значения
Перем Идентификатор Экспорт; // Имя колонки в базе - `Идентификатор`
&Колонка
Перем Номер Экспорт; // Колонка `Номер` будет создана в таблице, т.к. поле экспортное
&Колонка
Перем Серия Экспорт; // Колонка `Номер` будет создана в таблице, т.к. поле экспортное
&Сущность(ИмяТаблицы = "Документы")
Процедура ПриСозданииОбъекта() // Объект с типом "Документ" будет представлен в СУБД как таблица "Документы"
КонецПроцедуры
// file: ФизическоеЛицо.os
// Данный класс содержит информацию о физических лицах.
&Идентификатор // Колонка для хранения ID сущности
&ГенерируемоеЗначение // Заполняется автоматически при сохранении сущности
&Колонка(Тип = "Целое") // Хранит целочисленные значения
Перем Идентификатор Экспорт; // Имя колонки в базе - `Идентификатор`
Перем Имя Экспорт; // Колонка `Имя` будет создана в таблице, т.к. поле экспортное.
&Колонка(Имя = "Отчество") // Поле `ВтороеИмя` в таблице будет представлено колонкой `Отчество`.
Перем ВтороеИмя Экспорт;
&Колонка(Тип = "Дата") // Колонка `ДатаРождения` хранит значения в формате дата-без-времени
Перем ДатаРождения Экспорт;
&Колонка(Тип = "Ссылка", ТипСсылки = "СтраныМира")
Перем Гражданство Экспорт; // Данная колонка будет хранить ссылку на класс `СтраныМира`
&ПодчиненнаяТаблица(Тип = "Массив", ТипЭлемента = "Документы")
Перем Документы Экспорт; // Данное поле будет хранить массив ссылок на класс `Документ`. Для хранения
// массива будет создана отдельная таблица.
&Сущность(ИмяТаблицы = "ФизическиеЛица") // Объект с типом `ФизическоеЛицо` (по имени файла) будет представлен в СУБД в виде таблицы `ФизическиеЛица`
Процедура ПриСозданииОбъекта()
КонецПроцедуры
// Создание менеджера сущностей. Коннектором к базе выступает референсная реализация КоннекторSQLite.
// В качестве БД используется "база в оперативной памяти".
МенеджерСущностей = Новый МенеджерСущностей(Тип("КоннекторSQLite"), "Data Source=:memory:");
// Создание или обновление таблиц в БД.
МенеджерСущностей.ДобавитьКлассВМодель(Тип("СтраныМира"));
МенеджерСущностей.ДобавитьКлассВМодель(Тип("Документ"));
МенеджерСущностей.ДобавитьКлассВМодель(Тип("ФизическоеЛицо"));
// После заполнения модели менеджер необходимо проинициализировать.
МенеджерСущностей.Инициализировать();
// Работа с обычными объектом OneScript.
СохраняемоеФизЛицо = Новый ФизическоеЛицо;
СохраняемоеФизЛицо.Имя = "Иван";
СохраняемоеФизЛицо.ВтороеИмя = "Иванович";
СохраняемоеФизЛицо.ДатаРождения = Дата(1990, 01, 01);
СтранаМира = Новый СтраныМира;
СтранаМира.Код = "643";
СтранаМира.Наименование = "Российская Федерация";
Паспорт = Новый Документ;
Паспорт.Номер = "11 11";
Паспорт.Серия = "111000";
// Присваиваем колонке с типом "Ссылка" конкретный объект с типом "СтраныМира"
СохраняемоеФизЛицо.Гражданство = СтранаМира;
// Инициализируем массив для хранения документов.
// Это можно сделать и в методе ПриСозданииОбъекта в классе ФизическоеЛицо.os
СохраняемоеФизЛицо.Документы = Новый Массив;
// Добавляем новый документ
СохраняемоеФизЛицо.Документы.Добавить(Паспорт);
// Сохранение объектов в БД
// Сначала сохраняются подчиненные сущности, потом высокоуровневые
МенеджерСущностей.Сохранить(СтранаМира);
МенеджерСущностей.Сохранить(Паспорт);
МенеджерСущностей.Сохранить(СохраняемоеФизЛицо);
// После сохранения СохраняемоеФизЛицо.Идентификатор содержит автосгенерированный идентификатор.
// Колонка "Гражданство" в СУБД будет хранить идентификатор объекта СтранаМира - значение "643".
// Для хранения документов будет создана отдельная таблица, в которой будут сохранены
// значения массива с привязкой к физическому лицу.
Для поиска сущностей существуют методы Получить()
и ПолучитьОдно()
.
Метод Получить()
возвращает массив найденных сущностей.
Метод ПолучитьОдно()
возвращает одну (первую попавшуюся) сущность или Неопределено
, если найти сущность не удалось.
Оба метода в качестве второго параметра могут принимать в себя условия отбора в следующих видах:
Неопределено
(параметр не заполнен) - поиск без отборов;Соответствие
- пары ИмяПоля-ЗначениеПоля, используемые как отбор по "равно";ЭлементОтбора
- объект типа "ЭлементОтбора", позволяющий использовать более сложные условия, например, с видом сравнения "БольшеИлиРавно";Массив
- массив с элементами типа "ЭлементОтбора", позволяющий использовать сложные условия отбора, соединяемые через логическое И
.// Для поиска нескольких сущностей, удовлетворяющих условию, можно использовать метод Получить()
// При вызове метода без параметров будут полученные все сущности указанного типа.
// В массиве содержатся объекты типа "ФизическоеЛицо" с заполненными значениями полей.
// Поле "Гражданство" заполнится готовым объектом с типом "СтраныМира".
// Поле "Документы" заполнится массивом с готовыми объектами типа "Документ"
НайденныеФизЛица = МенеджерСущностей.Получить(Тип("ФизическоеЛицо"));
// В метод Получить() можно передать отбор в виде соответствия
Отбор = Новый Соответствие;
Отбор.Вставить("Имя", "Иван");
Отбор.Вставить("ВтороеИмя", "Иванович");
// В результирующем массиве окажутся все "Иваны Ивановичи", сохраненные в БД.
НайденныеИваныИванычи = МенеджерСущностей.Получить(Тип("ФизическоеЛицо"), Отбор);
// Допустим в БД сохранено физ. лицо с идентификатором, равным 123.
// Для получения одной (первой попавшейся) сущности можно использовать метод ПолучитьОдно()
СохраненноеФизЛицо = МенеджерСущностей.ПолучитьОдно(Тип("ФизическоеЛицо"));
// В метод можно передать отбор в виде соответствия, аналогично методу Получить()
СохраненноеФизЛицо = МенеджерСущностей.ПолучитьОдно(Тип("ФизическоеЛицо"), Отбор);
// Если вызвать метод "ПолучитьОдно" с параметром не-соответствием, то будет осуществлен поиск по идентификатору сущности.
Идентификатор = 123;
СохраненноеФизЛицо = МенеджерСущностей.ПолучитьОдно(Тип("ФизическоеЛицо"), Идентификатор);
// Найдем всех физических лиц, у которых дата рождения больше, чем 01.01.1990.
ЭлементОтбора = Новый ЭлементОтбора("ДатаРождения", ВидСравнения.БольшеИлиРавно, Дата(1990, 1, 1));
НайденныеФизЛица = МенеджерСущностей.Получить(Тип("ФизическоеЛицо"), ЭлементОтбора);
// Найдем всех физических лиц, рожденных в 90-ые.
МассивОтборов = Новый Массив;
МассивОтборов.Добавить(Новый ЭлементОтбора("ДатаРождения", ВидСравнения.БольшеИлиРавно, Дата(1990, 1, 1)));
МассивОтборов.Добавить(Новый ЭлементОтбора("ДатаРождения", ВидСравнения.Меньше, Дата(2000, 1, 1)));
ДетиДевяностых = МенеджерСущностей.Получить(Тип("ФизическоеЛицо"), МассивОтборов);
// Допустим имеется сущность, которую надо удалить.
МенеджерСущностей.Удалить(СущностьФизическоеЛицо);
// После выполнения метода в БД не останется строки с идентификатором, равным идентификатору сущности
Для упрощения взаимодействия с библиотекой помимо МенеджераСущностей, подразумевающего постоянную передачу типа сущности, осуществлять операции над сущностями можно через ХранилищеСущностей.
Хранилище сущностей предоставляет тот же базовый интерфейс, что и МенеджерСущностей, но не требует передачи типа сущности как параметра.
// Получение хранилища сущностей
ХранилищеФизЛиц = МенеджерСущностей.ПолучитьХранилищеСущностей(Тип("ФизическоеЛицо"));
// Поиск сущностей
Идентификатор = 1;
ФизЛицо = ХранилищеФизЛиц.ПолучитьОдно(Идентификатор);
ФизЛицо.Имя = "Петр";
ХранилищеФизЛиц.Сохранить(ФизЛицо);
Методы по работе с транзакциями есть как в Менеджере сущностей, так и в Хранилище сущностей.
Транзакционность поддерживается в рамках экземпляра менеджера сущностей или хранилища сущностей. При необходимости работы с транзакциями с несколькими типами сущностей следует использовать методы работы с транзакциями в Менеджере сущностей и модифицировать сущности через него же.
МенеджерСущностей.НачатьТранзакцию();
// Объекты ФизическоеЛицо и СтранаМира из примеров выше:
МенеджерСущностей.Сохранить(СтранаМира);
МенеджерСущностей.Сохранить(СохраняемоеФизЛицо);
МенеджерСущностей.ЗафиксироватьТранзакцию();
Для связями между классом на OneScript и таблицей в БД используется система аннотаций. Часть аннотаций обязательная к применению. Все параметры аннотаций необязательные.
При анализе типа сущности менеджер сущностей формирует специальные объекты модели, передаваемые конкретным реализациям коннекторов. Коннекторы могут рассчитывать на наличие всех описанных параметров аннотаций в объекте модели.
Применение: обязательно
Каждый класс, подключаемый к менеджеру сущностей должен иметь аннотацию Сущность
, расположенную над любым методом класса.
При отсутствии у класса методов рекомендуется навешивать аннотацию над методом ПриСозданииОбъекта()
.
Аннотация Сущность
имеет следующие параметры:
ИмяТаблицы
- Строка - Имя таблицы, используемой коннектором к СУБД при работе с сущностью. Значение по умолчанию - строковое представление имени типа сценария. При подключении сценариев стандартным загрузчиком библиотек совпадает с именем файла.Применение: обязательно
Каждый класс, подключаемый к менеджеру сущностей должен иметь поле для хранения идентификатора объекта в СУБД - первичного ключа. Для формирования автоинкрементного первичного ключа можно воспользоваться дополнительной аннотацией ГенерируемоеЗначение
.
Аннотация Идентификатор
не имеет параметров.
Применение: необязательно
Для части полей допустимо высчитывать значение колонки при вставке записи в таблицу. Например, для первичных числовых ключей обычно не требуется явное управление назначаемыми идентификаторами.
Референсная реализация коннектора на базе SQLite поддерживает единственный тип генератора значений - AUTOINCREMENT
.
Планируется расширение аннотации указанием параметров генератора.
Аннотация ГенерируемоеЗначение
не имеет параметров.
Применение: необязательно
Все экспортные поля класса (за исключением полей, помеченных аннотаций ПодчиненнаяТаблица
) преобразуются в колонки таблицы в СУБД. Аннотация Колонка
позволяет тонко настроить параметры колонки таблицы.
Аннотация Колонка
имеет следующие параметры:
Имя
- Строка - Имя колонки, используемой коннектором к СУБД при работе с сущностью. Значение по умолчанию - имя свойства.Тип
- ТипыКолонок - Тип колонки, используемой для хранения идентификатора. Значение по умолчанию - ТипыКолонок.Строка
. Доступные типы колонок:
ТипСсылки
- Строка - Имя зарегистрированного в модели типа, в который преобразуется значение из колонки. Имеет смысл только в паре с параметром Тип
, равным Ссылка
. Допустимо указывать примитивные типы из перечисления ТипыКолонок
и типы сущностей (например, "ФизическоеЛицо"
)Применение: необязательно
Аннотация ПодчиненнаяТаблица
используется для хранения коллекций - массивов и структур.
Аннотация ПодчиненнаяТаблица
имеет следующие параметры:
ИмяТаблицы
- Строка - Имя таблицы, используемой коннектором к СУБД при работе с сущностью. Значение по умолчанию - строка вида ИмяТаблицыСущности_ИмяСвойства
.Тип
- ТипыПодчиненныхТаблиц - Тип колонки, используемой для хранения идентификатора. Доступные типы подчиненных таблиц:
ТипЭлемента
- Строка - Имя зарегистрированного в модели типа, в который преобразуется значение из колонки. Допустимо указывать примитивные типы из перечисления ТипыКолонок
и типы сущностей (например, "ФизическоеЛицо"
)Описание публичного интерфейса - каталог docs.
МенеджерСущностей предоставляет публичный интерфейс по чтению, сохранению, удалению данных. МенеджерСущностей инициализируется конкретным типом коннектора к используемой базе данных. Все операции по изменению данных МенеджерСущностей делегирует Коннектору. В зоне ответственности МенеджераСущностей находятся:
ХранилищеСущностей предоставляет тот же интерфейс по работе с сущностями и транзакциями, но с глобальной привязкой к конкретному типу сущности. Для получения ХранилищаСущностей служит метод МенеджерСущностей::ПолучитьХранилищеСущностей
.
В отличие от МенеджераСущностей, ХранилищеСущностей не требует передачи в методы параметра "ТипСущности".
Хранилища сущностей и пулы сущностей совпадают в рамках одного типа сущности, типа коннектора и строки соединения. Другими словами, два менеджера сущности, инициализированные одним и тем же коннектором и строкой соединения, вернут одинаковые хранилища сущностей одного типа.
Коннектор содержит в себе логику по работе с конкретной СУБД. Например, КоннекторSQLite
служит для оперирования СУБД SQLite. В зоне ответственности коннектора находятся:
Ко всем коннекторам предъявляются определенные требования:
АбстрактныйКоннектор
;Например, КоннекторJSON
не умеет работать с транзакциями, однако, он имеет соответствующие методы, выводящие диагностические сообщения при их вызове.
Важно!
Каждое ХранилищеСущностей и МенеджерСущностей хранят в себе отдельные экземпляры Коннекторов. Тип, строка соединения и параметры коннектора определяются при создании МенеджераСущностей.
Модель данных хранит в себе список всех зарегистрированных классов-сущности в виде ОбъектовМодели
ОбъектМодели хранит детальную мета-информацию о классе сущности, его полях и данных всех аннотаций. Из ОбъектаМодели можно получить:
Помимо мета-информации ОбъектМодели позволяет получать значения колонок таблицы на основании имен полей сущности (и наоборот), вычислять значение идентификатора сущности, выполнять приведение типов и установку значений полей сущности.
В состав библиотеки входит референсная реализация интерфейса коннектора в виде коннектора к СУБД SQLite. Реализация базируется на библиотеке sql.
Коннектор SQLite поддерживает все CRUD-операции над сущностями, простой и сложный поиск, работу с транзакциями.
Внимание!
При использовании in-memory базы данных в моделях больше, чем с одним типом сущности, строка соединения должна выглядеть так: "FullUri=file::memory:?cache=shared"
В состав библиотеки входит референсная реализация интерфейса коннектора в виде упрощенного коннектора к набору файлов JSON. Каждая таблица хранится в отдельном файле в формате JSON в виде пар Ключ-Значение, где ключом выступает идентификатор сущности, а значением - сериализованная в JSON-объект сущность.
В качестве строки соединения указывается путь к каталогу, в котором будут сохранены файлы.
Коннектор SQLite поддерживает все CRUD-операции над сущностями, простой и сложный поиск, но не поддерживает работу с транзакциями. При вызове операций по работе с транзакциями будут выданы исключения.
Все операциями по записи и удалению сущностей одного типа проводятся синхронно, блокируя файл таблицы целиком. Для контроля над синхронным доступом используется библиотека semaphore.
Библиотека entity
в целом следует концепции семантического версионирования со следующими изменениями в правилах нумерации версий:
Таким образом:
Под контроль и обязательство соблюдения обратной совместимости попадают:
@unstable
) или "для служебного использования" (@internal
) методы классов:
АбстрактныйКоннектор
и их сигнатуры;@unstable
) методы классов:
To be continued...