Компоненты TStoredProc, TDatabase, TSession, TBatchMove и TUpdateSQL используются, как правило, намного реже, чем TTable, TQuery и TDataSource, однако в ряде случаев их применение позволяет эффективно использовать возможности, предоставляемые серверными СУБД.
Компонент TStoredProc
Компонент TStoredProc используется для выполнения из приложений C++ Builder хранимых процедур, содержащихся на серверах баз данных. Хранимая процедура представляет собой скомпилированную программу на процедурном расширении языка SQL, характерном для выбранного сервера. Хранимые процедуры могут возвращать наборы данных, основанные на выполнении запроса, если такие процедуры поддерживаются выбранным сервером (в этом случае TStoredProc может использоваться так же, как TQuery, и, так как при этом не требуется компиляция запроса сервером, использование TStoredProc может повысить производительность выполнения выбора данных), могут возвращать числовые параметры (в этом случае эти параметры можно использовать в приложении) и могут ничего не возвращать, а выполнять какие-либо действия на сервере баз данных. Хранимые процедуры также могут иметь входные параметры, передаваемые им из клиентского приложения.
Основные свойства компонента TStoredProc: DatabaseName - имя (alias) базы данных, в которой содержится данная процедура, StoredProcName - имя хранимой процедуры, Params (массив компонентов TParams) - параметры хранимой процедуры, а также ряд свойств, унаследованных от TDataSet: Active, Fields, Eof, Bof, State и др. Основные методы: ExecProc - выполняет хранимую процедуру, ParamByName - возвращает параметр, используя его имя. TStoredProc обладает также рядом методов, унаследованных от TDataSet: Append, AppendRecord, Close, Open, Delete, Edit, Post и т.д.
Рассмотрим простейший пример использования хранимой процедуры. Для этой
цели воспользуемся локальным сервером InterBase, входящим в комплект поставки
C++ Builder. Создадим новый проект, на главную форму которого поместим
компонент TStoredProc, четыре компонента TEdit и компонент TButton. Установим
значение свойства DatabaseName компонента StoredProc1 равным IBLOCAL (этот
псевдоним демонстрационной базы данных Employee.gdb создается автоматически
при установке сервера InterBase). Далее щелкнем на колонке значений напротив
свойства StoredProcName, после чего на экране должен появиться стандартный
диалог для ввода имени пользователя и пароля (рис.1):
По умолчанию при установке InterBase системный администратор базы данных имеет имя SYSDBA и пароль MASTERKEY (в дальнейшем его можно изменить).
После соединения с сервером (он должен перед этим быть запущен) напротив
имени свойства StoredProcName появится выпадающий список имеющихся в базе
данных хранимых процедур. Выберем для примера процедуру ORG_CHART, присваивающую
своим выходным параметрам значения одной из записей хранящейся на сервере
таблицы. Выбрав свойство Params, можно просмотреть параметры хранимой процедуры
с помощью соответствующего редактора свойств (рис. 2).
Создадим обработчик события OnClick для кнопки Button1:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
StoredProc1->ExecProc();
Edit1->Text=StoredProc1->Params->Items[0]->AsString;
Edit2->Text=StoredProc1->Params->Items[1]->AsString;
Edit3->Text=StoredProc1->Params->Items[2]->AsString;
Edit4->Text=StoredProc1->Params->Items[3]->AsString;
Edit5->Text=StoredProc1->Params->Items[4]->AsString;
}
После компиляции и запуска приложения нажатие на кнопку инициирует запуск хранимой процедуры и вывод ее параметров в соответствующие компоненты Edit1...Edit5 (рис.3).
Обычно при разработке приложений, использующих базы данных, с помощью утилит конфигурации BDE создаются псевдонимы (алиасы), указывающие на тип и местоположение данных. Компоненты типа TTable, TQuery, TStoredProc обладают свойством DatabaseName, при установке которого на этапе проектирования можно выбрать необходимый псевдоним из выпадающего списка или явно указать каталог, в котором располагаются плоские таблицы. Однако нередко бывает необходимо создать псевдоним динамически, или переопределить какие-либо параметры настройки драйвера базы данных (например, языковый драйвер, размер буферов, параметры кэширования структур таблиц на рабочей станции) для конкретного приложения без модификации файла конфигурации BDE. В этом случае обычно используется компонент TDatabase, помещаемый явно на форму или в модуль данных. Если определить свойство DatabaseName этого компонента, оно появится в списке псевдонимов при установке свойства DatabaseName компонентов TTable, TQuery, TStoredProc.
Отметим, что, если не поместить компонент TDatabase на форму (или в модуль данных), он в любом случае будет создан на этапе выполнения в процессе создания формы или модуля данных. Дело в том, что именно этот компонент отвечает за взаимодействие с Borland Database Engine, и поэтому его создание инициируется компонентами TTable, TQuery, TStoredProc, если таковые присутствуют в создаваемых на этапе выполнения формах или модулях данных.
Для динамического создания псевдонима следует поместить компонент TDatabase
на форму или в модуль данных и выбрать опцию Database Editor из контекстного
меню этого компонента (рис.4).
Помимо переопределения параметров псевдонимов или создания новых псевдонимов
компонент TDatabase нередко используется для минимизации числа обращений
к серверу. Как было сказано выше, компоненты TTable, TQuery и TStoredProc
инициируют динамическое создание компонента TDatabase, если он в явном
виде не присутствует в форме или модуле данных. Соответственно каждый раз,
когда в процессе выполнения приложения создается новая форма, пользователь
получает диалог ввода имени и пароля, и, что не менее существенно, происходит
обращение клиентского приложения к серверу баз данных с целью выяснения
существования такого пользователя, правильности его пароля, а также его
прав на таблицы и иные объекты базы данных посредством вызовов функций
BDE, обращающихся, в свою очередь, к функциям прикладного программного
интерфейса клиентской части соответствующего сервера. Чтобы избежать этой
ситуации, на форму или в модуль данных, создаваемый при запуске приложения,
помещается компонент TDatabase (или несколько компонентов TDatabase, если
приложение использует несколько различных баз данных), и свойство DatabaseName
всех компонентов TTable, TQuery, TStoredProc устанавливается равным не
имени соответствующего псевдонима, а имени соответствующего компонента
TDatabase.
При использовании значения NOT SHARED для компонентов TQuery и компонентов TTable создаются отдельные соединения с базой данных, что позволяет избежать возможных конфликтов и непредсказуемых результатов обновлений данных в ряде случаев (например, при попытке обновления одной и той же записи с помощью методов TTable и с помощью SQL-запроса на обновление данных, инициированное компонентом TQuery).
Наиболее эффективным с точки зрения минимизации соединений с базой данных значением этого параметра в большинстве случаев является значение SHARED AUTOCOMMIT. При использовании этого значения изменения каждой записи в таблицах немедленно фиксируются сервером независимо от того, к какому классу (TTable или TQuery) принадлежит компонент, их инициировавший, при этом компоненты обоих классов могут использовать общие соединения с базой данных.
Третье возможное значение этого параметра - SHARED NOAUTOCOMMIT. В этом случае компоненты TTable и TQuery могут также использовать одно и то же соединение с базой данных, но без завершения транзакций после редактирования каждой записи, но контроль за завершением транзакций следует осуществлять в клиентском приложении.
Свойство Transisolation компонента TDatabase определяет уровень изоляции транзакций разных пользователей друг от друга. Предположим, в процессе транзакции требуется использовать значение, хранящееся в поле какой-либо таблицы, например, для проведения расчетов. С момента начала транзакции это значение может быть изменено другим пользователем, поэтому в общем случае неочевидно, какое именно значение будет использовано - реально существующее в базе данных на момент, когда оно было затребовано, или то, которое существовало на момент начала транзакции.
Значение свойства Transisolation, равное tiDirtyRead, применяется, если в этой ситуации используются самые последние данные, независимо от того, завершил ли изменившую их транзакцию другой пользователь. В этом случае существует потенциальная опасность использовать данные, реально не сохраненные в базе данных, если другой пользователь выполнил откат транзакции. Отметим, что не все серверные СУБД поддерживают такой режим.
Значение tiReadCommitted применяется, если нужно использовать последнее значение на тот момент, когда оно затребовано, но только после того, как изменивший его пользователь завершил транзакцию. Этот режим поддерживается большинством серверных СУБД.
Значение tiRepeatableRead полностью изолирует транзакцию от вмешательства других пользователей. При его применении в течение всей транзакции используются одни и те же данные, существовавшие на момент начала транзакции, независимо от того, изменялись ли они другими пользователями. Такой режим может создать проблемы при высокой интенсивности транзакций. Представим себе, например, систему продажи авиабилетов, когда оба оператора одновременно получают данные о том, что данное место свободно, и продают два билета на одно место. В приложениях подобного рода не рекомендуется использовать значение tiRepeatableRead.
Повлиять на выполнение серверных транзакций можно также путем кэширования внесенных пользователем изменений вместо попытки немедленного сохранения их в базе данных, установив равным true значение свойства Cached Updates компонента TTable или TQuery. В этом случае накопленные в локальном кэше изменения пересылаются на сервер с помощью метода ApplyUpdates() компонента TTable или TQuery.
Кэширование изменений полезно по многим причинам. Во-первых, такой метод ввода данных снижает нагрузку на сеть, так как взаимодействие клиента с сервером происходит не постоянно, как в случае непосредственного редактирования таблиц на сервере, а эпизодически. Во-вторых, если сохранение кэшированных данных на сервере не удалось, например, из-за блокировок обновляемых записей другими пользователями, этот метод возвращает значение false, но при этом изменения сохраняются в кэше, что позволяет повторить попытку сохранения данных позже либо внести в изменяемые данные необходимые коррективы. Изменения также можно отменить с помощью метода CancelUpdates().
Отметим, что выполнение метода ApplyUpdates инициирует транзакцию на сервере, которая завершается после внесения всех изменений из кэша в базу данных, и лишь после успешного ее завершения внесенные данные удаляются из кэша. Поэтому до выполнения метода ApplyUpdates серверные данные остаются неизмененными, что позволяет, если этого требует логика клиентского приложения, проанализировать потенциальные изменения, внести в них коррективы и лишь затем сохранить их в базе данных.
Рассмотрим простейший пример применения компонента Database и кэширования
данных. Для этой цели скопируем на сервер ORACLE 7 таблицу CLIENTS.DBF
из входящей в комплект поставки C++Builder базы данных DBDEMOS (например,
с помощью утилиты Data Migration Wizard) и создадим приложение для ввода
данных в нее (рис. 8):
Компонент | Свойство | Значение |
Database1 | DatabaseName | my_database |
Params | SERVER NAME=ORA
USER NAME=USER1 NET PROTOCOL=TNS OPEN MODE=READ/WRITE SCHEMA CACHE SIZE=8 LANGDRIVER=ancyrr SQLPASSTHRU MODE=SHARED AUTOCOMMIT SCHEMA CACHE TIME=-1 MAX ROWS=-1 BATCH COUNT=200 ENABLE SCHEMA CACHE=FALSE SCHEMA CACHE DIR= ENABLE BCD=FALSE ENABLE INTEGERS=FALSE LIST SYNONYMS=NONE ROWSET SIZE=20 BLOBS TO CACHE=64 BLOB SIZE=32 PASSWORD=u |
|
Table1 | Active | true |
CachedUpdates | true | |
DatabaseName | my_database | |
TableName | CLIENTS | |
DataSource1 | DataSet | Table1 |
DBGrid1 | DataSource | DataSource1 |
DBNavigator1 | DataSource | DataSource1 |
DBImage1 | DataSource | DataSource1 |
DataField | IMAGE | |
LoginPrompt | false | |
Button1 | Caption | Сохранить |
Button2 | Caption | Отменить |
Button3 | Caption | Выход |
//---------------------------------------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "appl1.h"
//---------------------------------------------------------------------------
#pragma link "Grids"
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner): TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Table1->ApplyUpdates();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
Table1->CancelUpdates();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
Close();
}
//---------------------------------------------------------------------------
При выполнении метода Post компонента Table1 новые записи накапливаются
в кэше, а изменений в таблице, хранящейся на сервере, не происходит, что
можно проконтролировать, например, с помощью утилиты Database Explorer.
При нажатии на кнопку «Сохранить» внесенные записи переносятся на сервер,
что также можно проконтролировать, перечитав редактируемую таблицу (рис.9):
Компонент TSession автоматически создается при запуске приложения. Необходимость его явного использования может возникнуть только при создании многопоточных приложений с базами данных (например, когда в одном потоке выполняется длительный запрос, а в другом в это время производится ввод данных в кэш) . В этом случае число компонентов TSession равно числу потоков. Помимо этого, компонент TSession может быть использован для определения на этапе выполнения списка и параметров драйверов и псевдонимов или списка хранимых процедур.
Основные свойства этого компонента: Databases - массив активных компонентов TDataBase, SessionName - имя сеанса. Основные методы: GetDatabaseNames, GetAliasNames - возвращают списки драйверов и псевдонимов, GetAliasParams - возвращает параметры для данного псевдонима, GetDriverNames - возвращает имена доступных драйверов BDE, GetTableNames и GetStoredProcNames - возвращает список таблиц базы данных.
Компонент TBatchMove
Этот компонент обеспечивает копирование данных из одной таблицы в другую. Основные свойства: Source - таблица (или запрос), откуда копируются данные, Destination - таблица, куда копируются данные, Mapping - определяет соответствие между колонками исходной и результирующей таблиц (для идентичных таблиц это свойство определять не обязательно), Mode - тип перемещения (batAppend - добавляет новые строки в результирующую таблицу, batUpdate - заменяет строки в результирующей таблице на соответствующие строки оригинала, batCopy - копирует строки в результирующую таблицу, переписывая ее, batDelete - удаляет записи в результирующей таблице, соответствующие записям оригинала), KeyViolTableName и ProblemTableName - имена дополнительных таблиц для помещения записей, чье копирование запрещено правилами ссылочной целостности или по каким-либо причинам невозможно (например, из-за несоответствия типов данных), ChangedTableName - имя таблицы для помещения измененных записей.
Копирование происходит при выполнении метода Execute. Отметим, что этот метод может быть вызван непосредственно из среды разработки с помощью контекстного меню компонента TBatchMove.
Чаще всего подобное копирование используется при смене сервера баз данных или при переносе desktop-приложения в архитектуру клиент/сервер. В этом случае возможно выявить нарушения ссылочной целостности в исходных таблицах и при необходимости отредактировать данные, связав с компонентами TTable управляющие элементы через соответствующие компоненты TDataSource. Пример подобного применеия этого компонента был приведен в предыдущей статье данного цикла.
Компонент TUpdateSQL
TUpdateSQL предназначен для модификации данных на сервере с помощью заранее подготовленных SQL-предложений. Он позволяет определить различные операторы SQL для удаления, вставки и модификации записи, в том числе отличные от простых операторов DELETE, INSERT, APPEND. Эти операторы SQL содержатся в свойствах DeleteSQL, InsertSQL, ModifySQL соответственно.
Имя компонента TUpdateSQL может быть значением свойства UpdateObject какого-либо компонента TDataSet (например, TTable или TQuery). Если в этом случае используется кэширование данных, то в процессе выполнения транзакции, инициированной применением метода ApplyUpdates компонента TDataSet при попытке вставки, удаления или изменения записи генерируется SQL-запрос, содержащийся в свойстве InsertSQL, DeleteSQL или ModifySQL соответственно. Эти свойства можно редактировать, выбрав пункт UpdateSQL editor из контекстного меню компонента TUpdateSQL. Если же кэширование не используется, соответствующие SQL-запросы генерируются при выполнении метода Post компонента TDataSet.
Отметим, что эти три свойства поддерживают специальное расширение SQL, обеспечивающее возможность использования в запросах значений полей, существовавших до начала выполнения транзакции, переносящей на сервер данные из кэша (обычно это требуется при создании предложения WHERE в SQL-запросах). Такие значения полей обозначаются следующим образом: префикс «OLD_» + <имя поля>.
На этапе выполнения в процессе сохранения какой-либо записи на сервере компонент TUpdateSQL выбирает один из трех описанных в его свойствах запросов в соответствии со значением свойства UpdateKind компонента TDataSet, пересылает параметры запроса на сервер и выполняет запрос с целью фиксации на сервере данного обновления.
Для управления каждым отдельным обновлением внутри транзакции, переносящей данные из кэша на сервер, можно использовать событие OnApplyUpdate соответствующего компонента TDataSet, а также параметры UpdateKind (тип обновления) и UpdateAction, которому должно быть присвоено значение uaApplied, если обновление было успешным. Если установить значение этого параметра равным uaSkip, данная запись в кэше будет проигнорирована и не перенесена на сервер.
Для изучения поведения TUpdateSQL внесем изменения в уже созданное приложение, заменив компонент Table1 на компонент TQuery и добавив на форму один компонент TUpdateSQL и компонент TCheckBox. Свойства этих компонентов изменим следующим образом (табл.2):
Компонент | Свойство | Значение |
Query1 | Active | true |
CachedUpdates | true | |
UpdateObject | UpdateSQL1 | |
SQL | Select * from clients | |
DataSource1 | DataSet | Query1 |
CheckBox1 | Caption | Использовать кэширование |
UpdateSQL1 | ModifySQL | update CLIENTS
set LAST_NAME = :LAST_NAME, FIRST_NAME = :FIRST_NAME, ACCT_NBR = :ACCT_NBR where LAST_NAME = :OLD_LAST_NAME and FIRST_NAME = :OLD_FIRST_NAME and ACCT_NBR = :OLD_ACCT_NBR |
DeleteSQL | delete from CLIENTS
where LAST_NAME = :OLD_LAST_NAME and FIRST_NAME = :OLD_FIRST_NAME and ACCT_NBR = :OLD_ACCT_NBR |
|
InsertSQL | insert into CLIENTS
(LAST_NAME, FIRST_NAME, ACCT_NBR) values (:LAST_NAME, :FIRST_NAME, :ACCT_NBR) |
Добавим в запрос вычисляемое поле STATUS для отображения состояния записи в кэше, уберем часть полей из списка отображаемых колонок DBGrid1, внесем изменения в существующие обработчики событий и добавим несколько новых (для события OnCalcField компонента Query1 и для события OnClick компонента CheckBox1):
//---------------------------------------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "appl1.h"
//---------------------------------------------------------------------------
#pragma link "Grids"
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner): TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Query1->ApplyUpdates();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
Close();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
Query1->CancelUpdates();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Query1CalcFields(TDataSet *DataSet)
{
AnsiString UpdStr[] = {"Не изменялась", "Изменена", "Добавлена",
"Удалена"};
if (Query1->CachedUpdates)
Query1STATUS->Value=UpdStr[Query1->UpdateStatus()];
}
//---------------------------------------------------------------------------
void __fastcall TForm1::CheckBox1Click(TObject *Sender)
{
Query1->CachedUpdates=CheckBox1->Checked;
}
//---------------------------------------------------------------------------
Если перед запуском приложения запустить утилиту SQL Monitor, то можно
пронаблюдать, как генерируются запросы, содержащиеся в свойствах компонента
UpdateSQL1, при использовании кэширования и без него, а также как отображается
состояние конкретной записи при использовании кэширования (рис.11):
Отметим, что свойства DeleteSQL, InsertSQL, ModifySQL компонента TUpdateSQL
могут содержать более сложные запросы, нежели сгенерированные автоматически
в его редакторе свойств, в соответствии с логикой содержащего его приложения.
Координаты автора:
Учебно-консалтинговый центр Interface Ltd.,
Тел. (095)135-55-00, 135-25-19, 135-77-81,
e-mail: mail@interface.ru
http://www.interface.ru