Большинство современных языков программирования достаточно хорошо поддерживают объектно-ориентированную технологию. Никого уже не удивить наличием в языке наследования, полиморфизма, солидной библиотеки классов.
На этом фоне крайне неприглядно выглядит 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].