Компонент TDatabase

Обычно при разработке приложений, использующих базы данных, с помощью утилит конфигурации BDE создаются псевдонимы (алиасы), указывающие на тип и местоположение данных. Компоненты типа TTable, TQuery, TStoredProc обладают свойством DatabaseName, при установке которого на этапе проектирования можно выбрать необходимый псевдоним из выпадающего списка или явно указать каталог, в котором располагаются плоские таблицы. Однако нередко бывает необходимо создать псевдоним динамически, или переопределить какие-либо параметры настройки драйвера базы данных (например, языковый драйвер, размер буферов, параметры кэширования структур таблиц на рабочей станции) для конкретного приложения без модификации файла конфигурации BDE. В этом случае обычно используется компонент TDatabase, помещаемый явно на форму или в модуль данных. Если определить свойство DatabaseName этого компонента, оно появится в списке псевдонимов при установке свойства DatabaseName компонентов TTable, TQuery, TS toredProc.

Отметим, что, если не поместить компонент TDatabase на форму (или в модуль данных), он в любом случае будет создан на этапе выполнения в процессе создания формы или модуля данных. Дело в том, что именно этот компонент отвечает за взаимодействие с Borland Database Engine, и поэтому его создание инициируется компонентами TTable, TQuery, TStoredProc, если таковые присутствуют в создаваемых на этапе выполнения формах или модулях данных.

Для динамического создания псевдонима следует поместить компонент TDatabase на форму или в модуль данных и выбрать опцию Database Editor из контекстного меню этого компонента (рис.4).

Рис.4. Компонент TDatabase в модуле данных

В редакторе свойств Database Editor (рис.5) можно выбрать либо имя существующего (т.е. описанного в файле конфигурации BDE или созданного с помощью другого, созданного ранее, компонента TDatabase) псевдонима базы данных, либо явно указать драйвер БД и переопределить параметры доступа к базе данных. Нажатие на кнопку Defaults приводит к внесению всех параметров и их значений, характерных для данного псевдонима (или данного драйвера, если указан драйвер БД), в список Parameter Overrides, и затем можно внести в него изменения. Если снять отметку с флажка Login prompt, можно подавить появление диалога ввода пароля пользователя (что иногда бывает полезно при отладке приложений, а также в случае, когда требования к безопасности данных невысоки по сравнению с требованиями к производительности работы пользователя). Опция Keep inactive connection указывает, сохранять ли соединение с базой данных, если пользователь закрыл все таблицы. Если эта опция выбрана, при закрытии и последующем повторном открытии таблиц пользователь должен заново регистрироваться на сервере.

Отметим, что переопределить параметры псевдонима базы данных можно также и с помощью инспектора объектов - они содержатся в опубликованных свойствах компонента TDatabase(рис.6).

Рис. 5. Редактор свойств компонента TDatabase

Помимо переопределения параметров псевдонимов или создания новых псевдонимов компонент TDatabase нередко используется для минимизации числа обращений к серверу. Как было сказано выше, компоненты TTable, TQuery и TStoredProc инициируют динамическое создание компонента TDatabase, если он в явном виде не присутствует в форме или модуле данных. Соответственно каждый раз, когда в процессе выполнения приложения создается новая форма, пользователь получает диалог ввода имени и пароля, и, что не менее существенно, происходит обращение клиентского приложения к серверу баз данных с целью выяснения существования такого пользователя, правильности его пароля, а также его прав на таблицы и иные объекты базы данных посредством вызовов функций BDE, обращающихся, в свою очередь, к функциям прикладного программного интерфейса клиентской части соответствующего сервера. Чтобы избежать этой ситуации, на форму или в модуль данных, создаваемый при запуске приложения, помещается компонент TDatabase (или несколько компонентов TDatabase, если приложение использует несколько различных баз данных), и свойство DatabaseName всех компонентов TTable, TQuer y , TStoredProc устанавливается равным не имени соответствующего псевдонима, а имени соответствующего компонента TDatabase.

Рис.7. Связь компонента TTable с компонентом TDatabase

Из других параметров серверных баз данных, которые можно переопределить с помощью компонента TDatabase, весьма важным является параметр SQLPassThruMode, определяющий, каким образом завершаются транзакции (т.е. согласованные изменения в нескольких таблицах базы данных), инициированные компонентами TTable и TQuery, и могут ли они использовать общие соединения с базой данных. Возможные значения этого параметра - NOT SHARED, SHARED AUTOCOMMIT и SHARED NOAUTOCOMMIT.

При использовании значения 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):

Рис. 8 Приложение для ввода данных в таблицу ORACLE

Значения свойств компонентов созданного приложения приведены в таблице 1. Особо отметим, что свойство CachedUpdates компонента Table1должно иметь значение true.

Таблица 1.

Компонент Свойство Значение
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 Выход

Нажатие на кнопки Button1 и Button2 приводит к попытке сохранения данных на сервере и к отмене внесенных изменений соответственно.

	//---------------------------------------------------------------------------
	#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):

Рис.9. Перенос содержимого кэша на сервер