Аслан Корытов дата публикации 01-11-2005 04:41 Обмен опытом. Часть II.
В предыдущей статье был описан генератор кода, позволяющий значительно ускорить разработку БД-приложений. Описание его было бы неполным без объяснения, на какие вспомогательные классы опирается получающийся программный код.
Сгенерированные исходники компонентов так лаконичны и красивы по той причине, что все компоненты, описывающие сущности, происходят (наследуются) от компонента TAIEssence, код которого приведён в "прицепе".
Этот компонент инкапсулирует некоторые понятия и методы, общие для всех сущностей. Во-первых, мы приняли, что каждый объект сущности обладает целым уникальным идентификатором; поэтому в классе TAIEssence есть public-поле ID, и каждая сущность наследует его.
Во-вторых (см. рисунок), каждый компонент имеет свойства-указатели на:
- БД
- TIBQuery
- TIBStoredProc
- транзакции для чтения и записи (TrR, TrW)
Естественно, что каждый объект должен знать, какими манипуляторами ему пользоваться при работе с БД. Метод Notification обслуживает корректное функционирование этих свойств-манипуляторов.
Далее. Объект каждого класса должен знать, как называются серверные GET- и PUT-процедуры, а также их параметры. Для этого заведёны поля ENTITY_ID, PutProc, GetProc, P_ID, R_ID, которые инициализируются правильными значениями в момент присвоения имени сущности свойству ENTITY_NAME. А этот код создаётся генератором кода; в этом можно убедиться, взглянув на исходники предыдущей статьи. В конструкторе каждого компонента обязательно присутствует строка типа «ENTITY_NAME="USER";».
Компонент TAIEssence реализует метод InheritProperties, который устанавливает упомянутые свойства-манипуляторы для всех объектов, динамически создаваемых в текущем классе, как в контейнере (см. исходники к предыдущей статье).
Следующий полезный метод, который позволяет лаконично выражать свои действия классам-потомкам, - это Prepare(). Этот метод подготавливает к исполнению хранимую процедуру с заданным именем. Предварительно процедуре "подсовывается" транзакция, "заточенная" под задачи процедуры: чтение или запись в БД.
Мы плавно подошли к рассмотрению "хитрых" свойств TrR, TrW (типа TAskIBTransaction, который рассмотрим ниже), которые также наследуются сущностями. Эти свойства возвращают наиболее подходящую транзакцию для выполнения какого-либо запроса/процедуры.
Кроме того, каждый экземпляр любой сущности обладает унаследованным от TAIEssence методом с приятным названием Del(). Его функциональность очевидна: каждая сущность должна уметь удалить себя из БД (помимо умений прочитать и записать себя в БД).
Теперь опишем тип TAskIBTransaction, производный от TIBTransaction. Он позволяет более свободно работать с БД, и писать меньше кода; свойства PtrTranR, PtrTranW каждого компонента указывают именно на транзакцию этого типа.
Исходный код класса TAskIBTransaction совсем простенький, и позволяет организовать - нет, не вложенные транзакции, - а "вложенное" к ним обращение.
Мы можем в клиентском приложении написать следующий код:
try {
DM->TrW->StartTransaction();
ScreenToEnterprise(); Enterprise->Put();
PutContacts(); PutPersons(); PutPersContactsToDB();
PutContracts(); PutOffers(); PutDocs();
DM->TrW->Commit();
}
catch(EIBInterBaseError &E) {
DM->TrW->Rollback(); throw;
}
А внутри метода Put() каждого компонента-сущности уже сгенерирован код:
try {
TranW->StartTransaction();
Prepare(PutProc, ettWrite);
PtrStoredProc->ParamByName(P_ID)->AsInteger = ID;
<…>
PtrStoredProc->ExecProc();
ID = PtrStoredProc->ParamByName("R_ID_ENTERPRISE")->AsInteger;
PtrStoredProc->Close();
TranW->Commit();
}
catch(EIBInterBaseError &E) {
TranW->Rollback(); throw;
}
То есть, если PtrTranW компонента указывает на DM->TrW (а обычно так оно и есть), мы можем не писать код, анализирующий, активна ли транзакция, и предпринимающий в соответствии с её состоянием, какие-либо действия.
Соответственно, подобный код не нужен и при завершении транзакции (Commit или Rollback).
Эта возможность обеспечивается отслеживанием глубины вызовов "старта" и окончания транзакции, и выполнением соответствующих методов (StartTransaction() и Commit() или Rollback()) только при первом (последнем) вызове.
Таким образом, этот простенький механизм позволяет не заморачиваться техническими вопросами, а спокойно ваять приложение, справедливо полагая, что механизм "вложенного" вызова транзакция сам во всём разберётся.
Вообще-то, осталось ещё несколько компонентов, облегчающих работу с БД InterBase/Firebird, но их описание отложим "на потом".
Программы, разработанные с использованием описанной методики, "живут" на сайте http://asksoft.net/
К материалу прилагаются файлы:
[Передача данных на клиента]
Обсуждение материала нет сообщений |