Библиотека IBAccess

Библиотеку можно скачать по этой ссылке (24 КиБ).

IBAccess - объектно ориентированная Delphi библиотека, предоставляющая удобный доступ к базам FireBird и InterBase. Для работы с IBAccess не требуется добавлять её в панель компонентов, по этому она может быть использована в Turbo Delphi Explorer. Все обращения к низкоуровневым функциям gds32.dll обрамлены объектом TCriticalSection, что делает код потокобезопасным. Библиотека состоит из 4 файлов:

  • ibase.pas - перевод файла ibase.h, поставляемого вместе с InterBase, на язык Pascal. Этот файл содержит объявления констант, структур и функций, содержащихся в gds32.dll, с достаточно полными комментариями на русском языке.
  • ibase_exception.pas - содержит объявление исключения EIbaseError. Сюда планировалось поместить перевод сообщений, выдаваемых исключениями FireBird и Interbase, но как показала практика, системные сообщения Interbase и FireBird бесполезны для пользователя. В каждой конкретной задаче приходилось писать сообщения понятным для пользователя языком. (Одни и и те же сообщения, выглядили по разному в разных задачах).
  • ibase_level1_server.pas - объектно ориентированный слой, построенный для работы с функциями gds32.dll. Слой построен для обеспечения базиса более абстрактым объектно-ориентированным надстройкам. Дополнительного уровня абстракций не содержит и предоставляет механизм доступа к базе данных по правилам, описанным в документации API InterBase.
  • ibase_level2_server.pas - был написан позднее чем остальные, и являетя обобщением опыта использования IBAccess. Содержит дополнительный уровень абстракций и полностью скрывает механизм доступа, описанный в документации API InterBase. Добавление этого модуля позволяет использовать библиотеку IBAccess, без необходимости прочтения документация API InterBase.

Подключение к базе данных

Для подключения к базе данных необходимо создать объект TConnectPlus и выполнить метод Attach. Attach принимает три обязательных параметра: путь к базе, имя пользователя, пароль. И два не обязательных: кодовую страницу (по умолчанию устанавливается WIN1251) и роль (по умолчанию не указывается). Путь к базе указывается в формате: имя_или_ip_адрес_сервера:диск:полный_путь_до_базы_данных. Если база данных находится на локальной машине, сервер можно не указывать.

var base_ : TConnectPlus;
base_ :=TConnectPlus.Create;
base_.Attach.('127.0.0.1:c:\test\base.fdb','SYSDBA','masterkey');

Разрыв соединения с базой данных

Разрыв связи с базой данных выполняет метод Detach объекта TConnectPlus. Detach не проверяет была ли связь установлена, поэтому перед вызовом необходимо это проверить, иначе будет сгенерировано исключение. Метод IsAttach возвращает True если связь с базой была устанеовлена и False в противном случае.

if base_.IsAttach then base_.Detach;

Начало транзакции

InterBase и FireBird предлагают разнообразные уровни изоляции транзакций, но как показала практика, в большинстве случаев можно обойтись транзакциями двух типов:

  • Транзакция может только читать данные, видит только последнюю версию строки для которой выполнена команда commit, в случае конфликта (которого для данного типа транзакций не может быть) не ожидает снятия блокировки записи.
  • Транзакция может читать и записывать данные, видит последнюю версию строки для которой выполнена команда commit (иногда бывает необходимо при изменении строки сгенерировать исключение, если имеется не подтвержденная другой транзакцией версия этой строки), в случае конфликта не ожидается снятие блокировки.

В модуле ibase_level2_server.pas имеются объявление двух типов транзакций: TReadOnlyTransaction (транзакция первого типа) и TReadWriteTransaction (транзакция второго типа). Класс TConnectPlus имеет два метода cReadOnlyTransaction и cReadWriteTransaction, которые запускают транзакции и возвращают объекты TReadOnlyTransaction и TReadWriteTransaction соответственно. cReadWriteTransaction принимает параметр типа boolean. Если этот параметра равен True, будет сгенерировано исключение при попытке изменить строку для которой имеется не потвержденная другой транзакцией версия.

var t_ : TReadWriteTransaction;
t_ :=base_.cReadWriteTransaction(false);

В добапвении к указанным выше двум типам транзакций модуль ibase_level2_server содержит объявление ещё одного типа TConcurrencyOnlyReadTransaction. Эта транзакция может только читать данные, не видит изменения выполненные после её начала, даже если для них выполнена команда commit, в случае конфликта (которого не может быть) не ожидает снятия блокировки. Транзакции этого типа оказались полезными при программировании отчетов, которые выполняются на "слепке" базы данных в определенный момент. Запуск транзакции TConcurrencyOnlyReadTransaction осуществляется вызовом метода cConcurrencyOnlyReadTransaction класса TConnectPlus.

var t_ : TConcurrencyOnlyReadTransaction;
t_ :=base_.cConcurrencyOnlyReadTransaction;

При возникновении ситуации когда функционал транзакций, предлагаемый модулем ibase_level2_server, не подходит, можно воспользоваться интерфейсом ibase_level1_server, поддерживающим все уровни изоляции, доступные в FireBird и InterBase. После того как транзакция будет создана интерфейсом ibase_level1_server, при её запуске можно установить связь с классом TConnectPlus и в дальнейшем перейти на использование функционала ibase_level2_server.

Подтверждение транзакции

InterBase и FireBird позволяют подтвердить транзакцию, выполнить команду Commit, как с её завершением, так и с продолжением. Классы TReadOnlyTransaction, TReadWriteTransaction,  TConcurrencyOnlyReadTransaction порождены от класса TTransaction модуля ibase_level1_server. Этот класс имеет публичные методы Commit и Commit_retaining, которые и выполняют подтверждение транзакции с завершением и продолжением соответственно. Подтверждение с продолжением можно рассматривать как примерную аналогию подтверждения транзакции с закрытием и начала новой. Но полной аналогии нет, например, в случае с транзакцией TConcurrencyOnlyReadTransaction Commit_retaining не создает заново "слепок" базы данных. При уничтожении объектов TReadOnlyTransaction, TReadWriteTransaction,  TConcurrencyOnlyReadTransaction если транзакция не закрыта, будет автоматически вызван метод Commit. При уничтожении объекта TTransaction если транзакция не завершена, будет сгенерировано исключение.

t_.Commit_retaining;
. . .
t_.Commit;

Откат транзакции

Для этих целей класс TTransaction содержит публичный метод Rollback, который откатывает изменения, проведенные транзакцией, и закрывает её. Из своего опыта могу констатировать, грамотное использование транзакций существенно сокращает код обработки ошибок.

t_.Rollback;

Выполнение запросов

Документация API InterBase, разделяет все запросы на четыре типа, в зависимости от того возвращается ли запросом информация и имеет ли запрос входные параметры:

Возвращает данные? Входные параметры? Тип
нет нет Тип 1
нет да Тип 2
да нет Тип 3
да да Тип 4

Данная градация показалась мне удачной, полностью абстрагироваться от неё не стал. Самыми типичными представителями третьего и четвертого типов запросов являются Select запросы без параметров и с параметрами соответственно. Приложения используют эти запросы наиболее часто, по этому в модуль ibase_level2_server.pas были добавлены два класса TSelect_ и TSelectWithParam_, порожденные от классов TDSQL_metod3 и TDSQL_metod4 модуля ibase_level1_server.pas. TSelect_ и TSelectWithParam являются абстрактными классами, которые перед использованием должны быть перекрыты. Схема перекрытия достаточно проста, рассмотрю на примере TSelect_:

  1. Необходимо создать новый класс, являющийся наследником TSelect_.
  2. В новом классе перекрыть зпщищенную функцию GetStrSelect, которая должна возвращать строку select запроса.
  3. В конструктуре создать объект ibase_level2_server.TRecord_, установить на него указатель FRecord_ и добавить поля. Самой последней строчкой вызвать конструктор базового класса.

Конструктор TSelect_ принимает два параметра. Первый указатель на объект ibase_level1_server.TConnection, потомком которого является ibase_level2_sever.TConnectPlus. Второй, не обязательный параметр, указатель на объект транзакции, если второй параметр не указан будет создана новая транзакция TReadOnlyTransaction и в рамках неё и будет выполнен запрос. Запрос выполнится при создание объекта.

Библиотека IBAccess поддерживает все многообразие типов InterBase и FireBird. На уровня модуля ibase_level2_server добавлена абстракция практически всех типов полей. Класс TFieldInteger предназначен для работы с типами InterBase(FireBird) SMALLINT, INTEGER; класс TFieldDouble для работы с FLOAT, DOUBLE PRECISION, DECIMAL, NUMERIC; TFieldString для работы с CHAR, VARCHAR (для NCHAR не предназначен); TFieldDate для работы с DATE; TFieldTimeStamp для TIMESTAMP; TFieldBlob для работы с BLOB.

После создание объекта курсор можно открыть методом Open_cursor, прочитать запись методом Fetch, проверить достигнут ли конец методом Eof. Курсоры InterBase и FireBird являются однонаправленными, по этому IBAccess не содержит механизма прохождения в обратном направлении нет. Но ни что не мещает эмулировать эту функцию в потомке TSelect_.

var base_ : TConnectPlus;
TSelectExample =class(ibase_level2_server.TSelect_)

    protected
      FA_  : ibase_level2_server.TFieldInteger;
      FB_  : ibase_level2_server.TFieldString;

      FC_ :  ibase_level2_server.TFieldDouble;
      FD_  : ibase_level2_server.TFieldTimeStamp;

      function GetStrSelect : string; override;
    public
      property A_  : ibase_level2_server.TFieldInteger  read FA_;
      property B_  : ibase_level2_server.TFieldString   read FB_;
      property C_  : ibase_level2_server.TFieldDouble read FC_;
      property D_  : ibase_level2_server.TFieldTimeStamp read FD_;

      constructor Create();
end; 

function TSelectExample.GetStrSelect: string;
begin
  Result :='select field_A, field_B, field_C, field_D from Table_Example';
end;              

constructor TSelectExample.Create();
begin
   FA_ := ibase_level2_server.TFieldInteger.Create('field_A');
   FB_ := ibase_level2_server.TFieldString.Create('field_B',20); //Второй параметр длина поля

   FC_ :=  ibase_level2_server.TFieldDouble.Create('field_B',);
   FD_ := ibase_level2_server.TFieldTimeStamp.Create('field_B');

   FRecord_ :=ibase_level2_server.TRecord_.Create;
   pRecord.Add(FA_);
   pRecord.Add(FB_);
   pRecord.Add(FC_);
   pRecord.Add(FD_);

   inherited Create(base_);
end;

. . .

base_ :=TConnectPlus.Create;
base_.Attach.('127.0.0.1:c:\test\base.fdb','SYSDBA','masterkey');

. . .

var s : TSelectExample;

s :=TSelectExample.Create();
try
  s.Open_cursor;
  s.Fetch;
  while not s.Eof do begin
     // s.A_.value, s.B_.value, s.C_.value, s.D_.value - значения полей; s.A_.IsNull, s.B_.IsNull, s.C_.IsNull, s.D.isNull - проверка на null
     . . .
     s.Fetch;
  end;  
finally
  s.Free;
end;

Организация программного кода при использовании IBAccess

Библиотека IBAccess позволяет выполнить любую операцию с базами InterBase и FireBird, для подавляющего большинства обращений к базе построена абстракция над уровнем API, которая инкапсулирована в модуле ibase_level2_server. Такие операции как соединение и разрыв связи, начало и завершение транзакции, выполнение select запросов, команды insert и update, да и вообще любые команды, могут выполняться на уровне этого модуля. Если функционал ibase_level2_server окажется не достаточен, можно опуститься на уровень ibase_level1_server, В отличие от всех  известных мне библиотек, предоставляющих доступ к базама InterBase и FireBird, эта библиотека объектно ориентированная, требующая перед обращением к базе построить необходимые классы. Что приводит к увеличению кода, который необходимо написать, но позволяет всю "техническую" часть работы с базой сосредоточен в одном модуле. Во всех проектах, где использовал IBAccess, я строил третий уровень абстракции, но уже в терминах конкретной задачи. Это, на мой взгляд, качественно улучшает программный код, облегчает дальнейшую поддержку проекта и позволяет в случае необходимости изменить SQL платформу, внеся изменения в один единственный модуль.

Добавить комментарий