Delphi-Help

Главная

Работа с транзакциями и их использование в FIBPlus. Часть 1

Оцените материал
(0 голосов)


Работа с транзакциями и их использование в FIBPlus. Часть 1

Довольно часто использование механизма транзакций при работе множества пользователей с базами данных, поддерживаемыми системами управления базами данных InterBase или Firebird (да и другими серверами баз данных), вызывает немалые затруднения. Как правило, программисты используют только уровень изоляции READ COMMITTED, независимо от конкретных условий выполнения задачи.

Мы получаем немало писем с просьбой подробнее рассказать о транзакциях и использовании их характеристик при работе с компонентами FIBPlus.

Помимо документации по InterBase Language Reference, Embedded SQL Guide и API Guide, книги Helen Borrie The Firebird Book: A Reference for Database Developers (в издательстве БХВ-Петербург выходит перевод — «Firebird: справочник для разработчиков базы данных», перевод мой, научный редактор Дмитрий Кузьменко) и книги Алексея Ковязина и Сергея Вострикова «Мир InterBase», где, в принципе, содержится полное описание транзакций, существует множество статей, посвященных транзакциям. Мы решили не повторять этих описаний, а дать возможность «потрогать руками» основные характеристики транзакций и посмотреть, каким образом эти характеристики влияют на многопользовательскую работу с базой данных.

Для этого было написано несколько небольших программ, с которыми удобно проводить эксперименты. Для работы с базой данных мы, естественно, использовали компоненты FIBPlus. Результаты таких действий описываются в данной статье. Мы подробно рассмотрим компонент TpFIBTransaction — задаваемые параметры транзакции и их соответствие элементам языка SQL, используемым для описания характеристик транзакций.

Используемая база данных

Для экспериментов создадим базу данных FIBTRANSACT.FDB, поддерживаемую сервером баз данных Firebird 1.5. В базе данных создадим две таблицы: REFCOUNTRY, являющуюся справочником стран, и REFREGION, содержащую список регионов некоторых стран. Это фрагмент реально используемой базы данных. Вначале создадим домены:

CREATE DOMAIN DCodCtr  AS CHAR(3);
CREATE DOMAIN DName30  AS VARCHAR(30) COLLATE PXW_CYRL;
CREATE DOMAIN DName60  AS VARCHAR(60) COLLATE PXW_CYRL;
CREATE DOMAIN DDescr   AS BLOB SUB_TYPE 1 SEGMENT SIZE 400;

Фрагмент скрипта создания таблиц:

/*** Справочник стран REFCOUNTRY ***/

CREATE TABLE REFCOUNTRY
(  Name        DName30,            /* Краткое название страны */
   FullName    DName60,            /* Полное название страны  */
   CodCtr      DCodCtr NOT NULL,   /* Код страны */
   Capital     DName30,            /* Столица */
   Region      DName30,            /* Название региона */
   Description DDescr,             /* Дополнительные сведения */
   CONSTRAINT "Country_PRIMARY_KEY" PRIMARY KEY (CodCtr)
);
COMMIT;

/*** Справочник регионов REFREGION ***/

CREATE TABLE REFREGION
(  CodCtr      DCodCtr NOT NULL,   /* Код страны */
   CodReg      DCodCtr NOT NULL,   /* Код региона */
   Center      DName30,            /* Название центра региона */
   RegName     DName60,            /* Название региона */
   Description DDescr,             /* Дополнительные сведения */
   CONSTRAINT "Region_PRIMARY_KEY"
     PRIMARY KEY (CodCtr, CodReg),
   CONSTRAINT "Region_FOREIGN_KEY"
     FOREIGN KEY (CodCtr) REFERENCES REFCOUNTRY (CodCtr)
        ON DELETE CASCADE
        ON UPDATE CASCADE
);
COMMIT;

Заполним таблицы данными по странам и по регионам стран USA и ENGLAND.

Экспериментальная программа

Создадим в IDE Delphi или C++Builder новое приложение. Создадим на форме панель инструментов с кнопками TButton, положим два компонента TDBDrid: DBGridCountry и DBGridRegion. Добавим два компонента TDataSource: DataSourceCountry и DataSourceRegion.

С закладки FIBPlus поместим на форму следующие компоненты: Database1 типа TpFIBDatabase, Transaction1 типа TpFIBTransaction, два компонента типа TpFIBDataSet: DataSetCountry и DataSetRegion. Поместим также компонент ErrorHandler:

0008_01

Рис. 1. Главная форма экспериментальной программы

Для объекта базы данных зададим необходимые значения.

Имя базы данных (свойство DBName) — FIBTRANSACT.FDB (базу данных следует поместить в тот же каталог, что и сам проект).

Зададим имя пользователя (UserName) SYSDBA,

пароль (Password) masterkey,

набор символов (CharSet) WIN1251,

установим диалект базы данных (SQLDialect) равным 3.

В качестве транзакции по умолчанию (DefaultTransaction) и транзакции для изменений выберем Transaction1:

0008_02

Рис. 2. Свойства компонента базы данных

Для компонента транзакции Transaction1 установим следующие значения.

Выберем из выпадающего списка имени базы данных (DefaultDatabase) Database1.

Для уровня изоляции транзакции TPBMode из выпадающего списка выберем значение tpbDefault — только в этом случае мы сможем изменять содержимое буфера параметров транзакции (transaction parameter buffer, TPB); при выборе значения tpbReadCommitted или tpbRepeatableRead в момент запуска транзакции в буфер параметров будут помещаться соответствующие константы, и изменить эту ситуацию никакими силами нельзя.

0008_03

Рис. 3. Свойства компонента транзакции

Для компонента набора данных DataSetCountry большинство свойств можно оставить в том виде, как они задаются по умолчанию. Установим следующие значения.

База данных (Database) — Database1.

Транзакция (Transaction) и транзакция для изменений (UpdateTransaction) — Transaction1.

В списке режимов (Option) для подсвойства poStartTransaction установим значение False. Это важно, поскольку мы собираемся явно управлять запуском и подтверждением (или откатом) транзакций. Подсвойство poKeepSorting установим в True. Для целей исследования поведения транзакций это особой роли не играет, однако бывает полезным для сохранения упорядоченности набора данных в случае внесения изменений в столбцы, по которым осуществляется упорядочивание набора данных (предложение ORDER BY).

В списке PrepareOptions можно (в нашем случае необязательно) установить psAskRecordCount в True. Это бывает полезным, если вам после открытия набора данных нужно в строке состояния указать количество полученных записей.

Вызовем генератор SQL (щелчок правой кнопкой мыши на компоненте и выбор в контекстном меню строки SQL Generator). В списке таблиц выберем REFCOUNTRY и дважды щелкнем мышью по этой строке. Будет сгенерирован оператор SELECT. В конец оператора добавим предложение ORDER BY NAME, чтобы упорядочить получаемый набор данных по именам стран.

0008_04

Рис. 4. Генерация оператора SELECT

Перейдем на закладку Generate Modify SQLs и щелкнем мышью по кнопке Generate

SQLs. Будут сгенерированы операторы для добавления (Insert), изменения (Update), удаления (Delete) и повторного чтения текущей строки (Refresh).

0008_05

Рис. 5. Генерация модифицирующих операторов

Компонент DataSourceCountry свяжем с набором данных DataSetCountry (свойство DataSet).

Аналогичные действия нужно выполнить и с компонентом DataSetRegion. Помимо этого, поскольку этот компонент является детальным (дочерним, подчиненным) набором данных в связке master-detail, нужно установить его свойство DataSource в DataSourceCountry, а в свойстве DetailConditions следует установить в True подсвойства dcForceOpen и dcWaitEndMasterScroll. При генерации оператора SELECT в генераторе SQL нужно скорректировать оператор следующим образом:

SELECT
    CODCTR,
    CODREG,
    CENTER,
    REGNAME,
    DESCRIPTION
FROM
    REFREGION 
WHERE CODCTR = ?CODCTR
ORDER BY CENTER

Здесь предложение WHERE CODCTR = ?CODCTR задает выборку только тех строк таблицы регионов, которые относятся к текущей стране. Более подробно про связь главная-подчиненная и ее реализацию при помощи компонентов FIBPlus читайте в соответствующей статье на сайте www.devrace.com.

Далее создадим еще две формы: одна, Transaction's Characteristics, будет использоваться для формирования списка параметров транзакции TRParams, другая, Transaction's Parameters, позволит отображать содержимое буфера параметров транзакции (TPB).

0008_06

Рис. 6. Форма Transaction's Characteristics

Форма Transaction's Characteristics нам понадобится для создания списка характеристик транзакции. Левому компоненту ListBox присвоим имя AllParameters, правому — SelectedParameters. Напишем следующие обработчики событий щелчка по кнопкам. При щелчке по кнопке «>» выполняется перемещение выбранных строк из левого компонента ListBox в правый:

procedure TFormTrans.Button1Click(Sender: TObject);
var I: Integer;
begin
  I := 0;
  while (I <= AllParameters.Items.Count - 1) do
  begin
    if AllParameters.Selected[I] then
    begin
      SelectedParameters.Items.Add(AllParameters.Items[I]);
      AllParameters.Items.Delete(I);
      I := I - 1;
    end;
    I := I + 1;
  end;
end;

Замечание. Здесь не приводится программный текст для C++Builder, так как в принципе очень просто осуществить «перевод» с языка Delphi на C++.

В любом компоненте ListBox можно выбрать несколько строк, удерживая нажатой клавишу Ctrl и щелкая мышью по нужным строкам.

При щелчке по кнопке «>>» выполняется перемещение всех строк из левого компонента ListBox в правый. По правде сказать, эта функция для наших целей не нужна и приводится здесь для сохранения принятого порядка.

procedure TFormTrans.Button2Click(Sender: TObject);
var I: Integer;
begin
  for I := 0 to AllParameters.Items.Count - 1 do
    SelectedParameters.Items.Add(AllParameters.Items[I]);
  AllParameters.Items.Clear;
end;

При щелчке по кнопке «<» выполняется перемещение выбранных строк из правого компонента ListBox в левый:

procedure TFormTrans.Button4Click(Sender: TObject);
var I: Integer;
begin
  I := 0;
  while (I <= SelectedParameters.Items.Count - 1) do
  begin
    if SelectedParameters.Selected[I] then
    begin
      AllParameters.Items.Add(SelectedParameters.Items[I]);
      SelectedParameters.Items.Delete(I);
      I := I - 1;
    end;
    I := I + 1;
  end;
end;

При щелчке по кнопке «<<» выполняется перемещение всех строк из правого компонента ListBox в левый:

procedure TFormTrans.Button3Click(Sender: TObject);
var I: Integer;
begin
  for I := 0 to SelectedParameters.Items.Count - 1 do
    AllParameters.Items.Add(SelectedParameters.Items[I]);
  SelectedParameters.Items.Clear;
end;

0008_07

Рис. 7. Форма Transaction's Parameters

Форма Transaction's Parameters позволяет отобразить содержимое списка параметров транзакции (TRParams) и вектора буфера параметров транзакции (TPB). При вызове формы в поле Memo отображается список параметров и числовые значения из TPB.

procedure TFormTransactionParam.FormShow(Sender: TObject);
var I: Integer;
begin
  Memo1.Clear;
  for I := 0 to FormMain.Transaction1.TRParams.Count - 1 do
    Memo1.Lines.Add(FormMain.Transaction1.TRParams.Strings[I]);
  Memo1.Lines.Add('============================');
  for I := 0 to FormMain.Transaction1.TPBLength - 1 do
    Memo1.Lines.Add(IntToStr(Integer(FormMain.Transaction1.TPB[I])));
end;

Вернемся в главный модуль. Напишем обработчики событий формы. В событии формы OnShow выполним подключение к базе данных, в событии OnClose отключимся от базы данных. Соответственно, это будут операторы:

Database1.Open;

и

Database1.Close;

Щелчок по кнопке Open DataSet приводит к открытию набора данных DataSetCountry. При этом автоматически открывается и набор данных DataSetRegion.

procedure TFormMain.BOpenDataSetClick(Sender: TObject);
begin
  DataSetCountry.Open;
end;

Щелчок по кнопке Close DataSet закрывает оба набора данных:

DataSetCountry.Close;

Щелчок по кнопкам BStartTransaction и BStopTransaction приводит, соответственно, к запуску и останову транзакции:

Transaction1.StartTransaction;
Transaction1.Active := False;

Обработка события щелчка по кнопке BCommit подтверждает транзакцию. После подтверждения транзакции она запускается заново, после чего открывается набор данных (он автоматически будет закрыт при завершении транзакции — по COMMIT или ROLL BACK):

procedure TFormMain.BCommitClick(Sender: TObject);
begin
  Transaction1.Commit;
  Transaction1.StartTransaction;
  DataSetCountry.Open;
end;

Похожим образом обрабатывается событие щелчка по кнопке BRollBack.

procedure TFormMain.BRollBackClick(Sender: TObject);
begin
  Transaction1.Rollback;
  Transaction1.StartTransaction;
  DataSetCountry.Open;
end;

Щелчок по кнопке Characteristics приводит к обращению к форме Transaction's Characteristics для формирования пользователем списка характеристик транзакции.

procedure TFormMain.BCharactTransactClick(Sender: TObject);
var I: Integer;
begin
  if FormTrans.ShowModal <> IDOK then exit;
  if Transaction1.Active then
    Transaction1.Active := False;
  Transaction1.TRParams.Clear;
  for I := 0 to FormTrans.SelectedParameters.Items.Count - 1 do
    Transaction1.TRParams.Add(
      FormTrans.SelectedParameters.Items[I]);
end;

Щелчок по кнопке ParamTransact приводит к вызову формы Transaction's Parameters, которая отобразит текущие характеристики транзакции.

И, наконец, напишем обработчик ошибок базы данных. Это позволит нам в случае ошибок увидеть не только сообщение сервера базы данных, но и значения кодов SQLCODE и GDSCODE. Для этих целей используем событие OnFIBErrorEvent компонента ErrorHandler:

procedure TFormMain.ErrorHandler1FIBErrorEvent(Sender: TObject;
  ErrorValue: EFIBError; KindIBError: TKindIBError;
  var DoRaise: Boolean);
var S: String;
begin
  S := S + 'SQLCode = ' + IntToStr(ErrorValue.SQLCode) + #10#13;
  S := S + 'IBErrorCode = ' + IntToStr(ErrorValue.IBErrorCode) + #10#13;
  S := S + 'IBMessage = ' + ErrorValue.IBMessage + #10#13;
  Application.MessageBox(PAnsiChar(S), 'Database Error',
    MB_OK + MB_ICONSTOP);
  DoRaise := False;
end;

При этом в список используемых модулей программы (предложение uses) необходимо добавить fib.

Замечание. Этот обработчик ошибок только выдает сообщение и не выполняет никаких других действий. Вам нужно вручную остановить транзакцию, заново ее запустить и открыть набор данных, чтобы продолжить работу. В реальной жизни вы, конечно же, проведете в программе грамотный анализ ошибки и выполните все необходимые действия по ее нейтрализации и восстановлению работоспособности программы.

Прочитано 7793 раз

Авторизация



Счетчики