
- •2 Семестр Лабораторная работа №2 Создание базы данных под управлением sql-сервера interbase и написание клиентского приложения
- •Теоретические сведения: / Указания по выполнению
- •Создание бд
- •Иерархия компонентов
- •Механизм master-detail
- •Ведение журнала
- •Указания по выполнению:
- •Контрольные вопросы:
- •Источники информации:
Иерархия компонентов
TIBDatabase — предназначен для подключения к базе данных. Основные методы: Open, Close.
TIBTransaction — предназначен для явного управления транзакцией. Основные методы: StartTransaction, Commit, Rollback, CommitRetaining, RollbackRetaining.
TIBTable — аналог стандартного TTable. Компонент предназначен для получения данных из одной таблицы или представления базы данных. Основное свойство — TableName. Основные методы: Open, Close. Набор данных, полученных при помощи TIBTable, является редактируемым, если речь идет о таблице базы данных или обновляемом представлении. Компонент совместим с визуальными компонентами.
TIBQuery — аналог стандартного TQuery. Компонент предназначен для получения данных на основе SQL-запроса. Этот набор данных не всегда будет редактируемым, зачастую необходимо использовать дополнительный компонент TIBUpdateSQL, чтобы иметь возможность редактировать полученные сведения. Основное свойство — SQL. Основные методы: Open, Close, ExecSQL. Компонент совместим с визуальными компонентами.
TIBDataSet — предназначен для получения и редактирования данных, является потомком стандартного класса TDataSet и полностью совместим со всеми визуальными компонентами. Основные методы: Prepare, Open, Close, Insert, Append, Edit, Delete, Refresh.
TIBStoredProc — предназначен для выполнения хранимых процедур и получения набора данных на основе результатов выполнения процедуры. Получаемый набор данных является нередактируемым. Компонент совместим с визуальными компонентами. Основное свойство — StoredProcName. Основной метод — ЕхесРгос.
TIBUpdateSQL — аналог TUpdateSQL. Используется в паре с TIBQuery и предназначен для создания модифицируемых наборов данных. Основные свойства: DeleteSQL, InsertSQL, ModifySQL и RefreshSQL.
TIBSQL — предназначен для выполнения SQL-запросов. В отличие от TIBQuery или TIBDataSet, TIBSQL не имеет локального буфера для набора данных и несовместим с визуальными компонентами.
TIBDatabaseInfo — позволяет получить системную информацию о некоторых свойствах базы данных, соединения и сервера. Например, UserNames — список пользователей, подключенных к базе данных, PageSize — размер страницы базы данных.
TIBSQLMonitor — предназначен для перехвата и отслеживания всех запросов, которые выполняют приложения, использующие IBX.
TIBEvents — предназначен для получения пользовательских событий InterBase. Основное свойство — Events. Основные методы: RegisterEvents, UnregisterEvents.
Подключение к базе данных:
На форме (а лучше в контейнере Data Module) размещается компонент TIBDatabase и при помощи специального редактора указываются свойства подключения (см. рисунок).
Установленный флаг Login Prompt приведет к тому, что идентификация пользователя БД будет проходить при каждом подключении к БД (при каждом запуске приложения).
Необходимо помнить, что любое действие с базой данных происходит в рамках той или иной транзакции. Работа с InterBase основана на явном управлении транзакциями, а поскольку библиотека IBX — это обертка вокруг соответствующих функций InterBase API, то использование этих компонентов также предполагает, что программист явным образом будет управлять транзакциями из своего приложения. Для контроля транзакций в IBX существует специальный компонент TIBTransaction.
TIBTransaction ссылается на компонент базы данных при помощи свойства DefaultDatabase. Если также указать свойство DefaultTransaction у TIBDatabase, то в дальнейшем любые компоненты (TIBDataSet, TIBSQL и т. д.), которые ссылаются на TIBDatabase, будут автоматически "подхватывать" и указанную транзакцию.
Теперь рассмотрим свойства AllowAutoStart и AutoStopAction. Как вам уже известно, любой запрос к базе данных должен выполняться в контексте транзакции. То есть, прежде чем выполнить даже простейший запрос вида SELECT * FROM TABLE1, необходимо предварительно запустить транзакцию при помощи вызова IBTransaction.StartTransaction.
Такой "ручной" вызов не всегда удобен. Более того, каждый раз совершенно определенно известно: если мы хотим выполнить запрос, то мы должны запустить транзакцию. Чтобы избежать лишнего кода, связанного с запуском транзакций, можно установить значение свойства AllowAutoStart равным True. В этом случае если мы попробуем, например, открыть TIBDataSet, то он сам автоматически запустит соответствующую транзакцию.
Аналогичный смысл имеет свойство AutoStopAction. Когда мы закрываем все запросы, выполнявшиеся в контексте автоматически запущенной транзакции, то TIBTransaction выполняет действие, указанное в AutoStopAction. Например, автоматически подтверждает всю транзакцию при помощи метода Commit, если свойство AutoStopAction равно saCommit. Таким образом, разработчику предоставляется возможность указать, как компоненты должны автоматически взаимодействовать друг с другом!
После размещения в проекте двух компонентов TIBDatabase и TIBTrasaction и связи их между собой, нужно выбрать компоненты для доступа к данным.
Фактически, компонент TIBCustomDataSet имеет всю необходимую функциональность для получения базы данных InterBase и поддерживает возможность редактирования этой информации с помощью визуальных db-aware-компонентов.
Для выборки данных, их изменения, удаления и вставки в TIBCustomDataSet используется набор свойств, представляющих собой SQL-запросы для манипулирования данными, — это SelectSQL, DeleteSQL, InsertSQL и ModifySQL.
Отдельно следует сказать о RefreshSQL. Этот запрос не используется для модификации записи, но является очень полезным для получения значений полей, которые были изменены триггерами базы данных и конкурирующими транзакциями.
В свойстве SelectSQL указывается запрос на выборку данных (SELECT... FROM...), которые будут доступны для просмотра и, в зависимости от содержимого остальных запросов, для редактирования, удаления и т. д.
В свойствах DeleteSQL, InsertSQL и ModifySQL указываются соответствующие запросы, которые будут вызываться автоматически самим компонентом при вызове методов Delete, Insert и Edit для удаления, вставки и редактирования записей.
Фактически все, что нужно сделать программисту, — это написать нужные запросы, выполняющие нужные операции над записями.
Компонент TIBTable прячет все указанные выше свойства, а вместо этого пользователю предоставляется свойство TableName. Пользователь указывает имя таблицы в свойстве TableName, а компонент автоматически формирует набор "спрятанных" запросов.
Например, для таблицы с именем Table1 запрос в SelectSQL будет иметь вид:
SELECT * FROM Table1 ...
Легко представить, что в нашей таблице несколько миллионов записей и этот запрос попытается получить их в полном объеме на клиента. Например, при вызове метода Locate, если запись, соответствующая условиям поиска, не найдена в загруженном наборе записей, то TIBTable будет запрашивать оставшиеся записи, пока не найдется подходящая запись или пока не закончатся записи в таблице.
Очевидно, что это вызовет колоссальную нагрузку на SQL-сервер и клиента. Ни один специалист не рекомендует использование компонента TIBTable в реальных программных проектах, предназначенных для управления серьезными базами данных в многопользовательской среде.
Аналогично TIBTable, компонент TIBQuery скрывает запросы для получения и редактирования данных. Вместо скрытого в этом компоненте свойства SelectSQL разработчику предлагается использовать свойство SQL. На самом деле после присвоения свойства SQL компонент присваивает его значение свойству SelectSQL.
Но самое примечательное с точки зрения проектирования классов начинается тогда, когда мы хотим сделать наш запрос редактируемым (live-query).
Поскольку свойства DeleteSQL, InsertSQL и ModifySQL спрятаны, то TIBQuery сам по себе не может предоставить разработчику редактируемые запросы.
Однако, как уже было сказано, TIBQuery был сделан как аналог TQuery и для полной аналогии в IBX введен компонент TIBUpdateSQL. Он содержит собственные свойства DeleteSQL, InsertSQL и ModifySQL и может подключаться к TIBQuery. После чего TIBQuery начинает использовать свойства компонента TIBUpdateSQL для редактирования собственных данных! Получается, что готовую функциональность TIBCustomDataSet, уже заложенную в него с самого начала, приходится дублировать в отдельном компоненте.
Тем не менее, что в данной связке имеется несомненный смысл, если речь идет о миграции готовых BDE-приложений на IBX. В общем-то, вероятно, только ради этого данные классы и были введены. Поэтому, при написании нового приложения, основывающегося на IBX, рекомендуется использовать компонент TIBDataSet как вместо TIBTable, так и вместо TIBQuery.
Компонент TIBStoredProc предназначен для выполнения исполняемых (executed) хранимых процедур. Т.е. получить с его помощью набор данных невозможно. Он также является потомком TIBCustomDataSet и полностью совместим с визуальными компонентами.
Являясь прямым наследником TIBCustomDataSet, компонент TIBStoredProc прячет все основные свойства предка и добавляет такое свойство, как ProcedureName.
В результате указания названия процедуры (например, proс1) компонент сформирует SQL-запрос следующего вида: EXECUTE PROCEDURE Proс1.
Если нужно получить набор данных, формируемых хранимой процедурой, то, используя TIBDataSet, сформировать запрос на выборку, указав имя процедуры в предложении FROM оператора SELECT.
Единственным преимуществом использования TIBStoredProc по сравнению с TIBDataSet является тот факт, что TIBStoredProc самостоятельно формирует список параметров процедуры по ее имени, обращаясь к системным таблицам.
Итак, размещаем на форме еще три компонента: IBDataSetMovie: TIBDataSet, DataSourceMovie: TDataSource и DBGridMovie: TDBGrid и связываем их между собой при помощи соответствующих свойств.
Свойство SelectSQL компонента получает значение 'select * from movie' (см. рис). После этого мы можем открыть таблицу в design-time, установив в True IBDatabase1.Connected и IBDataSetMovie.Active. В случае, если мы не укажем текста запроса в SelectSQL, открыть таблицу не удастся.
Аналогично, для того, чтобы можно было добавлять, изменять и удалять записи из таблицы необходимо написать запросы в свойства InsertSQL, ModifySQL и DeleteSQL, которые будут выполнятся серверов при вызове (явном и неявном) методов Insert, Edit и Delete компонента IBDataSetMovie.
Например, свойство ModifySQL для изменения всех полей таблицы Movie выглядит следующим образом:
UPDATE MOVIE SET ID_M = :ID_M, TITLE = :TITLE, YEAR= :YEAR, LEN= :LEN, KIND= :KIND, ID_PR= :ID_PR WHERE ID_M = :OLD_ID_M
Здесь, в качестве значений полей указаны параметры (с двоеточием), названия которых совпадают с названиями реальных полей.
Когда пользователь изменяет запись, изменения происходят в локальном буфере IBDataSetMovie и в базе данных никак не фиксируются. Для этого нужно выполнить соответствующий оператор UPDATE, значения параметров которого подставит сам компонент IBDataSetMovie из своего локального буфера.
Особое внимание обратите на параметр :OLD_ID_M в предложении WHERE запроса. Префикс OLD_ в названии означает, что IBDataSetMovie должен подставить в параметр значение поля до изменения пользователем.
Аналогичная идея подразумевается и для свойств InsertSQL и DeleteSQL. Для автоматической генерации текстов запросов можно использовать редактор IBDataSet.
В редакторе нужно указать таблицу, ключевые поля и поля, которые будут изменяться (CALCULATED-поля изменять нельзя). Если есть поля, названия которых совпадают с ключевыми словами (например, YEAR), их, в соответствии с диалектом 3, нужно взять в кавычки (установка флага Quote Identifiers приведет к взятию в кавычки всех идентификаторов).
Нажатие на кнопку Generate SQL сформирует необходимые запросы для свойств InsertSQL, ModifySQL, DeleteSQL и RefreshSQL. Последний запрос должен возвращать только одну текущую запись и нужен для обновления значений полей текущей записи.
Смысл данного запроса становится очевидным, если допустить существование в базе данных триггеров для таблицы MOVIE, которые модифицируют значения полей. Поскольку изменения происходят в самой базе данных сразу после вставки или после изменения записей, то без повторного перечитывания записи (т. е. без Select только что вставленной или измененной записи), мы не узнаем о тех изменениях, которые были сделаны в триггерах. Можно, конечно, вообще переоткрыть весь запрос, заданный в SelectSQL.
Именно такой механизм и реализуется в BDE, когда мы вынуждены целиком переоткрывать все запросы. В IBX без этого легко можно обойтись, используя RefreshSQL, значительно сэкономив при этом сетевой трафик и снизив нагрузку на сервер, поскольку получение всего лишь одной измененной записи гораздо более эффективно, чем переоткрытие запроса целиком.
У TIBDataSet как наследника TDataSet существует три основных метода для изменения данных: Delete, Insert (Append) и Edit. Например,
procedure TForm1.Button1Click(Sender: TObject); begin with IBDataSetMovie do begin Insert; FieldByName('ID_M').Aslnteger := 47; FieldByName('TITLE').AsString := 'Десять негритят' ; FieldByName('YEAR').Aslnteger := 1984; FieldByName('KIND').AsString := 'детектив'; FieldByName('LEN').AsInteger := 201; FieldByName('ID_PR').AsInteger := 5; Post; end; end;
После того как мы вызываем метод Insert, IBDataSetMovie формирует пустой буфер для нашей (пока еще не введенной) записи. Далее мы задаем значения нужных полей при помощи вызовов метода FieldByName и заканчиваем (точнее, подтверждаем) редактирование вызовом метода Post. В этот момент IBDataSetMovie выполняет запрос, прописанный в свойстве InsertSQL, подставив вместо параметров те значения полей, которые мы задали.
Если запрос выполнился успешно, то IBDataSetMovie автоматически выполняет RefreshSQL для обновления только что вставленной записи — для проверки изменений, внесенных на стороне базы данных.
Аналогичным образом мы можем редактировать записи.
В приведенном примере для поля ID_M значение указано явно (47), однако для первичных ключей принято использование генераторов и триггеров. Т.е., возложить задачу присвоения уникального значения ID_M на сервер и вообще не присваивать программно никакого значения. И здесь можно натолкнуться на неожиданное препятствие. Дело в том, что метод Post проверяет значения полей еще до выполнения запроса к серверу и обнаружив значение NULL для первичного ключа возбудит исключительную ситуацию.
Поэтому у компонента IBDataSet существует свойство Generator Field со своим редактором:
Укажите название генератора, имя поля в таблице, значения для которого будут генерироваться, шаг увеличения счетчика (в большинстве случаев это 1), а также опцию, указывающую, когда будет генерироваться значение поля: сразу при вставке новой записи (On New Record), при завершении вставки (On Post) или при помощи триггера (On Server). В последнем случае вы не увидите значения сгенерированного поля, пока не переоткроете весь запрос, однако данная опция все равно может оказаться полезной.
Указав же явным образом, что значение поля будет получено именно в триггере, мы обойдем это ограничение. Тем не менее рекомендуется использовать опции On New Record или On Post, поскольку они лучше укладываются в общую идеологию применения IBX.
Если мы получаем значение первичного ключа до отправки на сервер, то IBDataSetMovie сумеет корректно выполнить RefreshSQL. Если же мы будем использовать для генерации триггер, то IBDataSetMovie не сможет подставить правильное значение в RefreshSQL, поскольку значения полей в условии WHERE еще неизвестны.