Delphi-Help

Главная Статьи FireBird/Interbase FIBPlus: Обработка ошибок базы данных

FIBPlus: Обработка ошибок базы данных

Оцените материал
(1 Голосовать)


FIBPlus: Обработка ошибок базы данных

Еще одним из несомненных достоинств FIBPlus являются средства обработки ошибок базы данных. В данной статье мы рассмотрим эти средства.

Основной компонент, используемый в обработке ошибок, — TpFibErrorHandler. Он позволяет централизованно обрабатывать ошибки базы данных.

Подготовка к обработке ошибок

Для иллюстрации возьмем базу данных FIBSAMPLE.GDB, используемую в большинстве примеров по FIBPlus. В нашем примере мы будем использовать две таблицы из этой базы данных: TREFCOUNTRY и PERSON. Они имеют следующее объявление:

CREATE TABLE TREFCOUNTRY (
    NAME         DNAME30,
    FULLNAME     DNAME60,
    CODCTR       DCODCTR NOT NULL,
    CAPITAL      DNAME30,
    REGION       DNAME30,
    DESCRIPTION  DDESCR
);
 
CREATE TABLE PERSON (
    CODPERS     INTEGER NOT NULL,
    FIRST_NAME  DNAME20,
    LAST_NAME   DNAME20,
    COUNTRY     DCODCTR
);
ALTER TABLE PERSON ADD CONSTRAINT "PERS CHECK LASTNAME NOTNULL"
 check (last_name is not null);
ALTER TABLE PERSON ADD CONSTRAINT "PERS CHECK LASTNAME VALUE"
 check (last_name not containing '***');
ALTER TABLE PERSON ADD CONSTRAINT "PERS PRIMARYKEY"
 PRIMARY KEY (CODPERS);
ALTER TABLE TREFCOUNTRY ADD CONSTRAINT "Country PRIMARY KEY"
 PRIMARY KEY (CODCTR);
ALTER TABLE PERSON ADD CONSTRAINT "PERS FOREIGN KEY"
 FOREIGN KEY (COUNTRY) REFERENCES TREFCOUNTRY (CODCTR);

Создайте в Delphi или C++Builder новый проект ErrorHandler. Положите на форму следующие компоненты:

StatusBar1: TStatusBar;
Panel1: TPanel;
Panel2: TPanel;
Splitter1: TSplitter;
Splitter2: TSplitter;
Memo1: TMemo;
DBGrid1: TDBGrid;
DBGrid2: TDBGrid;
BExit: TSpeedButton;
BRefresh: TSpeedButton;
BSRollback: TButton;
BSCommit: TButton;
Database1: TpFIBDatabase;
WriteTransaction: TpFIBTransaction;
CountryData: TpFIBDataSet;
DSCountry: TDataSource;
PersData: TpFIBDataSet;
DSPerson: TDataSource;
ErrorHandler1: TpFibErrorHandler;

Примечание. Поскольку вы используете компонент TpFibErrorHandler, вы должны явно указать модуль fib в uses для Delphi:

uses fib;

Свойство DBName компонента Database1 ссылается на базу данных FIBSAMPLE.GDB. Компонент CountryData типа TpFIBDataSet ссылается на таблицу TREFCOUNTRY. Компонент PersData ссылается на таблицу PERSON. Используя SQL Generator, сгенерируйте обычным образом все операторы SQL для этих компонентов.

Компонент DSCountry свяжите с CountryData. Свойство DataSource у DBGrid1 установите в DSCountry. Компонент DBGrid1 отображает содержимое набора данных CountryData. Аналогичным образом свяжите компонент DSPerson с PersData. Свойство DataSource у DBGrid2 установите в DSPerson. Компонент DBGrid2 отображает содержимое PersData.

В поле Memo1 будет отображаться информация об ошибках.

[Image]

Рис. 1. Проект ErrorHandler. Обработка ошибок базы данных

Напишите следующие обработчики событий щелчка по кнопкам Rollback и Commit:

procedure TFormMain.BSRollbackClick(Sender: TObject);
begin
  WriteTransaction.RollbackRetaining;
  CountryData.FullRefresh;
  StatusBar1.Panels.Items[1].Text :=
    IntToStr(CountryData.RecordCount);
  DBGrid1.SetFocus;
end;
 
procedure TFormMain.BSCommitClick(Sender: TObject);
begin
  WriteTransaction.CommitRetaining;
  CountryData.FullRefresh;
  PersData.FullRefresh;
  DBGrid1.SetFocus;
end;

Напишите следующий обработчик события щелчка по кнопке Refresh:

procedure TFormMain.BRefreshClick(Sender: TObject);
begin
  CountryData.FullRefresh;
  PersData.FullRefresh;
  StatusBar1.Panels.Items[1].Text :=
    IntToStr(CountryData.RecordCount);
end;

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

Свойства и особенности компонента TpFIBErrorHandler

Центральная часть этой программы — компонент ErrorHandler1 и обработчик его события OnFIBEventError.

Установите в True все подсвойства свойства Options этого компонента: oeException, oeForeignKey, oeLostConnect, oeCheck, oeUniqueViolation. Это позволит перехватывать и обрабатывать в программе все основные типы исключений при работе с базой данных.

[Image]

Рис. 2. Характеристики компонента ErrorHandler1.

Компонент TpFibErrorHandler также содержит следующие свойства (только для чтения):

Таблица 1. Свойства только для чтения компонента TpFibErrorHandler.

Свойство

Значение

ConstraintName

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

ExceptionNumber

Номер пользовательского исключения (exception), вызвавшего ошибку. Если ошибка вызвана не исключением, то значение будет –1.

LastError

Тип последнего исключения — объект класса TKindIBError:
keNoError — ошибка отсутствует,
keException — обработано пользовательское исключение,
keForeignKey — обработано нарушение внешнего ключа,
keLostConnect — соединение с базой данных потеряно,
keSecurity — обработано нарушение полномочий пользователя,
keUniqueViolation — обработано нарушение уникального значения,
keCheck — обработано нарушение ограничения CHECK,
keOther — обработан иной тип ошибки.

Следующая таблица содержит описание значения подсвойств свойства Options.

Таблица 2. Список подсвойств свойства Options компонента TpFibErrorHandler.

Подсвойство

Значение

oeException

Обрабатываются пользовательские исключения. Текст ошибки не выводится, номер пользовательского исключения передается в свойстве компонента ExceptionNumber.

oeForeignKey

Нарушение значения внешнего ключа (foreign key).

oeLostConnect

Потеря связи с базой данных.

oeCheck

Нарушение ограничения CHECK.

oeUniqueViolation

Нарушение ограничения UNIQUE.

Вернемся к основной части нашей программы. Напишите следующий обработчик события OnFIBEventError для компонента ErrorHandler1:

procedure TFormMain.ErrorHandler1FIBErrorEvent(Sender: TObject;
  ErrorValue: EFIBError; KindIBError: TKindIBError;
  var DoRaise: Boolean);
var Lasterror: string;
    FKindIBError: string;
begin
  Memo1.Lines.Add(#13#10 + '===== ErrorHandler FIBErrorEvent =====');
  Memo1.Lines.Add('Sender.ClassName = ' + Sender.ClassName);
  Memo1.Lines.Add('Sender.Name = ' + (Sender as TComponent).Name);
  if Sender is TFIBQuery then
    Memo1.Lines.Add('Owner.Name = ' +
      (Sender as TFIBQuery).Owner.Name);
  if Sender is TpFIBStoredProc then
    Memo1.Lines.Add('Sender.StoredProcName = ' +
      (Sender as TpFIBStoredProc).StoredProcName);
  Memo1.Lines.Add('ConstraintName = ' +
    ErrorHandler1.ConstraintName);
  Memo1.Lines.Add('ExceptionNumber = ' +
    IntToStr(ErrorHandler1.ExceptionNumber));
  case ErrorHandler1.LastError of
    keNoError: Lasterror := 'keNoError';
    keException: Lasterror := 'keException';
    keForeignKey: Lasterror := 'keForeignKey';
    keSecurity: Lasterror := 'keSecurity';
    keLostConnect: Lasterror := 'keLostConnect';
    keCheck: Lasterror := 'keCheck';
    keUniqueViolation: Lasterror := 'keUniqueViolation';
    keOther: Lasterror := 'keOther';
  else
    Lasterror := 'Undefined';
  end;
  Memo1.Lines.Add('Lasterror = ' + Lasterror);
  Memo1.Lines.Add('SQLCode = ' + IntToStr(ErrorValue.SQLCode));
  Memo1.Lines.Add('IBErrorCode = ' +
   IntToStr(ErrorValue.IBErrorCode));
  Memo1.Lines.Add('Message = ' + ErrorValue.Message);
  Memo1.Lines.Add('IBMessage = ' + ErrorValue.IBMessage);
  Memo1.Lines.Add('SQLMessage = ' + ErrorValue.SQLMessage);
  case KindIBError of
    keNoError: FKindIBError := 'keNoError';
    keException: FKindIBError := 'keException';
    keForeignKey: FKindIBError := 'keForeignKey';
    keSecurity: FKindIBError := 'keSecurity';
    keLostConnect: Lasterror := 'keLostConnect';
    keCheck: FKindIBError := 'keCheck';
    keUniqueViolation: FKindIBError := 'keUniqueViolation';
    keOther: FKindIBError := 'keOther';
  else
    FKindIBError := 'Undefined';
  end;
  Memo1.Lines.Add('KindIBError = ' + FKindIBError);
//  DoRaise := False;
end;

Обработчику передаются следующие параметры: 1. ErrorValue — объект класса EFIBError. Класс содержит следующие свойства:

1.IBErrorCode — содержит код ошибки InterBase. Является наиболее информативным описателем ошибки. Существует около 400 различных кодов. Список кодов приведен в документе InterBase Language Reference в 5 главе Error Codes and Messages (таб).

  • IBMessage — содержит текст сообщения об ошибке.
  • SQLCode — содержит код ошибки SQLCODE.
  • SQLMessage — содержит сообщение об ошибке SQL.

2. KindIBError — объект класса TKindIBError. Может иметь значения, описанные в таблице 1, в свойстве LastError.

3. DoRaise — переменная логического типа. Позволяет указать, следует ли после обработки ошибки в данном обработчике вызывать повторно исключение. Если DoRaise присваивается значение True (по умолчанию), то после выполнения действий в обработчике ошибок будет вызвано стандартное исключение с выдачей соответствующих сообщений. Если же DoRaise имеет значение False, то действие, вызвавшее ошибку, отменяется, никаких сообщений не выдается. В нашем обработчике ошибок базы данных вся возможная информация об ошибке — с использованием свойств компонента TpFibErrorHandler и параметров, передаваемых в процедуру, — выводится в поле Memo1.

Внимание. Компонент TpFibErrorHandler позволяет обрабатывать множество ошибок базы данных. При этом ошибки подключения к базе данных в нем не обрабатываются (не путать с ошибкой при потере подключения и попытках возобновления подключения). Для этого следует использовать стандартные средства Delphi или C++Builder — блок try…except (Delphi) или try…catch (C++Builder). Не обрабатываются также такие ситуации, когда для компонента, работающего с базой данных (DataSet, Query, SoredProc и некоторые другие), не указана база данных или транзакция.

Пример перехвата ошибки подключения к базе данных.

try
  Database1.Connected := True;
except
  ShowMessage(’Ошибки при подключении к базе данных’);
  Application.Terminate;
end;

Обработка исключений

Теперь мы можем посмотреть, как работает наш обработчик ошибок.

Запустите программу на выполнение. Удалите значение первичного ключа (CODCTR) в любой строке в таблице TREFCOUNTRY (левый DBGrid). Это приводит к тому, что значение поля становится NULL, что недопустимо для первичного ключа. В поле Memo1 появится следующий текст:

========= ErrorHandler FIBErrorEvent =========
Sender.ClassName = TFIBQuery
Sender.Name = UpdateQuery
Owner.Name = CountryData
ConstraintName = 
ExceptionNumber = -1
Lasterror = keOther
SQLCode = -625
IBErrorCode = 335544347
Message = FormMain.CountryData.UpdateQuery:
The insert failed because a column definition includes validation constraints.validation error
for column CODCTR, value "*** null ***".
IBMessage = validation error for column CODCTR, value "*** null ***".
SQLMessage = The insert failed because a column definition includes validation constraints.
KindIBError = keOther
Обратите внимание на первые три строчки сообщения после заголовка: 
Sender.ClassName = TFIBQuery
Sender.Name = UpdateQuery
Owner.Name = CountryData

Здесь Sender.ClassName содержит имя класса объекта, вызвавшего исключение (TFIBQuery). Sender.Name — имя объекта (UpdateQuery), Owner.Name — имя владельца объекта: имя компонента TpFIBDataSet (CountryData). Если мы в этот же набор данных попытаемся добавить новую запись с пустым значением первичного ключа, то получим такое же сообщение, только значением Sender.Name будет InsertQuery.

Аналогичный результат будет получен, если вы попытаетесь установить в NULL значение первичного ключа (CODPERS) таблицы PERSON.

Попытка ввести дублированное значение первичного ключа в любую строку в таблице PERSON приводит к выдаче сообщения:

========= ErrorHandler FIBErrorEvent =========
Sender.ClassName = TFIBQuery
Sender.Name = UpdateQuery
Owner.Name = PersData
ConstraintName = PERS
ExceptionNumber = -1
Lasterror = keUniqueViolation
SQLCode = -803
IBErrorCode = 335544665
Message = violation of PRIMARY or UNIQUE KEY constraint "PERS PRIMARYKEY" on table "PERSON".
IBMessage = violation of PRIMARY or UNIQUE KEY constraint "PERS PRIMARYKEY" on table "PERSON".
SQLMessage = Invalid insert or update value(s): object columns are
constrained - no 2 table rows can have duplicate column values.
KindIBError = keUniqueViolation

Аналогичным образом можно проверить реакцию на нарушение других ограничений базы данных. Проверим результат обработки ошибки «потеря соединения». Для моделирования потери соединения завершите выполнение сервера InterBase/Firebird. Если запущена программа Guardian, следует завершить ее до завершения сервера. После этого нужно щелкнуть по кнопке Refresh. Это вызовет желаемую ошибку. В результате обработчик выдаст следующие сообщения:

========= ErrorHandler FIBErrorEvent =========
Sender.ClassName = TFIBQuery
Sender.Name = RefreshQuery
Owner.Name = CountryData
ConstraintName = 
ExceptionNumber = -1
Lasterror = keLostConnect
SQLCode = -901
IBErrorCode = 335544741
Message = FormMain.CountryData.RefreshQuery:
Unsuccessful execution caused by system error that does not preclude successful execution of
subsequent statements.connection lost to database.
IBMessage = connection lost to database.
SQLMessage = Unsuccessful execution caused by system error that does not preclude successful
execution of subsequent statements.
KindIBError =

Обратите внимание на значения SQLCode и IBErrorCode. SQLCode = -901. Если посмотреть значения SQLCode и IBErrorCode в документации по InterBase (Language Reference), то можно увидеть, что этому значению соответствует ровно 60 вариантов ошибок. Значение же IBErrorCode = 335544741 имеет только один тип ошибки: «connection lost to database», то есть, потеря соединения с базой данных. Еще раз следует подчеркнуть, что с целью обработки ошибок базы данных в программе наиболее полезным является именно параметр IBErrorCode, передаваемый обработчику ошибок.

Отдельно стоит посмотреть, как работает обработчик ошибок при вызове из хранимой процедуры или из триггера пользовательского исключения. Нам нужно добавить в нашу базу данных FIBSAMPLE.GDB исключение и простенькую хранимую процедуру, вызывающую это исключение.

Создать исключение можно следующим образом, используя файл скрипта:

CONNECT 'D:\Указываем конкретный путь к базе данных\FIBSAMPLE.GDB'
USER 'SYSDBA' PASSWORD 'masterkey';
 
CREATE EXCEPTION EXCEPT1 'Exception 1';
commit;

Аналогично создается хранимая процедура после создания исключения:

CONNECT 'D:\ Указываем конкретный путь к базе данных\FIBSAMPLE.GDB'
USER 'SYSDBA' PASSWORD 'masterkey';
 
CREATE PROCEDURE EXEEXCEPT1
AS
begin
  EXCEPTION EXCEPT1;
end;
commit;

Для вызова исключения нужно также внести изменения в наш проект. Положите на форму кнопку с именем Exception и компонент TpFIBStoredProc. Для компонента TpFIBStoredProc задайте следующие характеристики:

[Image]

Рис. 3. Характеристики компонента TpFIBStoredProc обращения к хранимой процедуре

Напишите следующий обработчик щелчка по кнопке Exception:

procedure TFormMain.ExceptionClick(Sender: Tobject);
begin
  FIBStoredProc1.ExecProc;
end;

Запустите программу на выполнение, щелкните по кнопке Exception. В поле Memo1 отобразится следующее:

========= ErrorHandler FIBErrorEvent =========
Sender.ClassName = TpFIBStoredProc
Sender.Name = FIBStoredProc1
Sender.StoredProcName = EXEEXCEPT1
ConstraintName = 
ExceptionNumber = 1
Lasterror = keException
SQLCode = -836
IBErrorCode = 335544517
Message = Exception 1.
IBMessage = exception 1.
Exception 1.
SQLMessage = exception 268785020.
KindIBError = keException

Здесь Sender.ClassName имеет значение (TpFIBStoredProc), поскольку исключение мы получили при вызове хранимой процедуры. Sender.Name содержит имя компонента, через который была вызвана хранимая процедура (FIBStoredProc1). Sender.StoredProcName содержит имя хранимой процедуры.

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

Последний пример на конфликт одновременного изменения одной и той же записи разными клиентами. Запустите два экземпляра программы. Это будут два разных клиента. Измените любую запись в одном процессе и ту же запись в другом процессе. При переходе на следующую запись в DBGrid (при этом для изменяемой записи выдается Post) будет вызвано исключение:

========= ErrorHandler FIBErrorEvent =========
Sender.ClassName = TFIBQuery
Sender.Name = UpdateQuery
Owner.Name = CountryData
ConstraintName = 
ExceptionNumber = -1
Lasterror = keOther
SQLCode = -901
IBErrorCode = 335544345
Message = FormMain.CountryData.UpdateQuery:
Unsuccessful execution caused by system error that does not preclude successful execution of
subsequent statements.lock conflict on no wait transaction.
deadlock.
update conflicts with concurrent update.
IBMessage = lock conflict on no wait transaction.
deadlock.
update conflicts with concurrent update.
SQLMessage = Unsuccessful execution caused by system error that does not preclude successful
execution of subsequent statements.
KindIBError = keOther

Заключение

Мы рассмотрели механизм использования TpFIBErrorHandler и продемонстрировали перехват всех основных видов ошибок, которые могут возникнуть при работе приложения с реальной базой данных. Способ и смысл обработки этих исключительных ситуаций зависят от программы, которую вы разрабатываете, а FIBPlus предлагает вам достаточно гибкий и удобный инструмент для перехвата и анализа.

 

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

Авторизация



Счетчики