Объектно-реляционная база данных

Виноградов С. А.
27.05.2002 г.


Введение

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

На этом фоне крайне неприглядно выглядит SQL со всеми своими расширениями от разных производителей. А допотопная техника работы с хранимыми процедурами навевает ностальгию по давно ушедшим временам. Однако, реляционные СУБД не зря завоевали популярность. Конкурирующие с ними ООБД либо отстают по производительности, либо дороги, либо просто неизвестны широкому кругу разработчиков.

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


Структура таблиц

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

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

При необходимости, можно создать единственный базовый класс, являющийся корнем для всей иерархии классов. Пример таблицы базового класса Objects (здесь и далее — синтаксис MS SQL):

create table [Objects]
(
    [Id] int not null identity primary key,
    [Class] int not null references [Classes]([Id]),
    [Code] varchar(120) not null,
    [Name] varchar(120) not null,
    [Note] varchar(250)
)

Пример таблицы наследуемого класса People:

create table [People]
(
    [Id] int not null primary key references [Objects]([Id]),
    [LastName] varchar(120) not null,
    [FirstName] varchar(120) not null,
    [MiddleName] varchar(120) not null,
    [BirthDay] datetime,
    ...
)


Физическая модель

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

Всегда желательно иметь информацию о физической модели данных, описываемой тремя основными таблицами: DataTypes, Tables и Columns.

create table [DataTypes]
(
    [Id] int not null identity primary key,
    [Parent] int references [DataTypes]([Id]),
    [Name] varchar(120) not null unique,
    [Note] varchar(250),
    [SQLType] varchar(250) not null
)

create table [Tables]
(
    [Id] int not null identity primary key,
    [Name] varchar(120) not null unique,
    [Note] varchar(250),
    [Key] int references [Columns]([Id])
)

create table [Columns]
(
    [Id] int not null identity primary key,
    [Table] int not null references [Tables]([Id]),
    [Name] varchar(120) not null,
    [Note] varchar(250),
    [DataType] int not null references [DataTypes]([Id]),
    [Size] int not null
)

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

Tables содержит имена и описания всех таблиц базы. Key определяет поле, являющееся первичным ключом каждой таблицы.

Columns определяет названия, типы данных и размер полей в таблицах.


Логическая модель

Но больше всего нас интересует логическая модель, которая описывает наследование классов, связи между ними и ограничения на данные. Для хранения этой модели также достаточно трех таблиц: AttributeTypes, Classes и Attributes.

create table [AttributeTypes]
(
    [Id] int not null identity primary key,
    [Parent] int references [AttributeTypes]([Id]),
    [Name] varchar(120) not null unique,
    [Alias] varchar(120) not null unique,
    [Note] varchar(250),
    [DataType] int not null references [DataTypes]([Id])
)

create table [Classes]
(
    [Id] int not null identity primary key,
    [Parent] int references [Classes]([Id]),
    [Name] varchar(120) not null unique,
    [Alias] varchar(120) not null unique,
    [Note] varchar(250),
    [MainTable] int not null references [Tables]([Id])
)

create table [Attributes]
(
    [Id] int not null identity primary key,
    [Class] int not null references [Classes]([Id]),
    [Name] varchar(120) not null,
    [Alias] varchar(120) not null,
    [Note] varchar(250),
    [Column] int references [Columns]([Id]),
    [AttributeType] int not null references [AttributeTypes]([Id]),
    [Size] int not null,
    [LinkClass] int references [Classes]([Id]),
    [Default] varchar(250),
    [Obligatory] bit not null defaul(0),
    [Unique] bit not null defaul(0)
)

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

Classes содержит имена и описания всех классов. Наличие поля Parent дает возможность создать иерархию. MainTable определяет главную таблицу для класса, которая содержит автонумеруемое поле, используемое в качестве первичного ключа для объектов этого класса. Дополнительные таблицы соединяются с главной по этому ключу один-к-одному.

Attributes определяет названия атрибутов класса, поля таблиц, в которых хранятся значения атрибутов, типы атрибутов, значения по умолчанию, флаги обязательности, уникальности и т.п. Поле LinkClass содержит идентификатор связанного класса (для атрибутов ссылочного типа).

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

Кроме того, можно добавить возможность создания атрибутов, не привязанных к конкретному полю в таблице, а определяемых SQL-выражением (аналогично вычисляемым полям). Для удобства работы со всем этим, можно, на основе метаданных, генерировать представления (view) для каждого класса.


Многие-ко-многим

Отношение вида многие-ко-многим заслуживает более тщательного рассмотрения, как выходящее за рамки прямого соответствия атрибута класса и поля в таблице.

Определение атрибута такого типа происходит так же, как определение атрибута ссылочного типа.
Аналогично, поле LinkClass в таблице атрибутов должно содержать идентификатор связанного класса.

Для хранения данных такого типа необходимо создать отдельную таблицу Links:

create table [Links]
(
    [Attribute] int not null references [Attributes]([Id]),
    [Object] int not null references [Objects]([Id]),
    [LinkObject] int not null references [Objects]([Id])
    constraint [Link] primary key ([Attribute], [Object], [LinkObject])
)

Attribute содержит идентификатор атрибута, имеющего тип отношения многие-ко-многим.

Object содержит идентификатор объекта того класса, которому принадлежит Attribute.

LinkObject содержит идентификатор объекта связанного класса LinkClass, на который ссылается упомянутый атрибут.

Возможны и другие варианты определения и хранения отношений вида многие-ко-многим.


Объектный код

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

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

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

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


Заключение

Разумеется, у описанного подхода есть и свои недостатки.

Во-первых, это уменьшение скорости модификации объектов — ведь для каждого объекта должны быть выполнены методы, как базового класса, так и наследников.

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

Гораздо более радикальный способ построения объектно-реляционной БД подробно рассмотрен в статье [1].



Литература

  1. Тенцер А. «База данных — хранилище объектов», II.



© 2002 Сергей Виноградов
Hosted by uCoz