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