Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

6. Пример 2: создание объектов для управления распределенными транзакциями

6.1. Создание серверных объектов для реализации распределенной транзакции

Теперь создадим второй объект для управления созданной ранее в базе данных DBDEMOS таблицей delivery.db. Закроем все открытые проекты и создадим новый серверный объект, такой же, как и предыдущий (рис. 17):

Рис. 17. Серверный объект DelDM для управления таблицей delivery.db

В отличие от предыдущего случая компонент TDatabase свяжем с базой данных DBDEMOS (рис. 18):

Рис. 18. Свойства компонента TDatabase серверного объекта delDM

В качестве свойства SQL компонента TQuery используем следующее SQL-предложение:

delete from delivery where OrdNum=:d

Затем отредактируем библиотеку типов, добавив три метода GetDelivery, AddDeliery и DelDelivery для получения данных из таблицы, добавления и удаления записи (рис. 19):

Рис. 19. Библиотека типов серверного объекта, управляющего таблицей delivery.db

Реализация этих методов имеет следующий вид:

unit del1;
//Another simple MTS server
//By N.Elmanova
//01.12.1998

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComServ, ComObj, VCLCom, StdVcl, BdeProv, BdeMts, DataBkr, DBClient,
  MtsRdm, Mtx, dels_TLB, DBTables, Provider, Db;

type
  TdelDM = class(TMtsDataModule, IdelDM)
    deltable: TTable;
    DelProvider: TProvider;
    Database2: TDatabase;
    Query4: TQuery;
    Session2: TSession;
  private
    { Private declarations }
  public
    { Public declarations }
  protected
    function GetDelivery: OleVariant; safecall;
    procedure AddDelivery(OrdNum: Integer; const  OrdName: WideString; const OrdAddr: WideString);
      safecall;
    procedure DelDelivery(OrdNum: Integer); safecall;
  end;

var
  delDM: TdelDM;

implementation

{$R *.DFM}

function TdelDM.GetDelivery: OleVariant;
begin
Result:=DelProvider.Data;
SetComplete;
end;

procedure TdelDM.AddDelivery(OrdNum: Integer; 
const OrdName: WideString; const  OrdAddr: WideString);
begin
try
    deltable.open;
    deltable.append;
    deltable.fieldbyname('OrdNum').Value:=OrdNum;
    deltable.fieldbyname('GoodsName').Value:=OrdName;
    deltable.fieldbyname('Address').Value:=OrdAddr;
    deltable.post;
    deltable.close;
    SetComplete;
except
   SetAbort;
raise;
end;
end;

procedure TdelDM.DelDelivery(OrdNum: Integer);
begin
try
  database2.open;
  deltable.open;
  deltable.SetRangeStart;
  deltable.FieldByName('OrdNum').AsInteger:=OrdNum;
  deltable.SetRangeEnd;
  deltable.FieldByName('OrdNum').AsInteger:=OrdNum;
  deltable.ApplyRange;
  deltable.Delete;
  deltable.close;
  database2.close;
except
  SetAbort;
raise;
end;
end;
initialization
  TComponentFactory.Create(ComServer, TdelDM,
    Class_delDM, ciMultiInstance, tmApartment);
end.

Скомпилируем и установим данный объект в тот же "пакет", что и предыдущий.

Рекомендуется протестировать данный объект, создав клиентское приложение, более или менее аналогичное предыдущему. При тестировании следует помнить, что в этой таблице есть уникальный первичный ключ, поэтому при вводе записей с одинаковым значением поля OrdNum транзакции завершаться не будут.

И, наконец, создадим третий серверный объект, который будет управлять распределенными транзакциями и с этой целью порождать два предыдущих серверных объекта внутри своих транзакций. Вначале создадим объект, аналогичный двум предыдущим (рис. 20):

delete from ord where ordnum=:d

Рис. 20. Модуль данных серверного объекта pays для управления таблицей ord.dbf

Теперь компонент TDatabase свяжем с созданной нами базой данных dbpay, содержащей таблицу ord.dbf (с ней мы свяжем компонент TTable, рис. 21):

Рис. 21. Свойства компонента TDatabase серверного объекта, управляющего распределенными транзакциями

Значение свойства SQL компонента TQuery будет выглядеть следующим образом:

delete from ord where ordnum=:d

Теперь добавим в проект библиотеки типов двух созданных ранее серверов. Для этого следует выбрать из меню Delphi опцию Project/Import type library, нажать кнопку Add и выбрать соответствующий файл с расширением *.tlb (рис. 22):

Рис. 22. Импорт библиотек типов серверных объектов - участников распределенной транзакции

Далее отредактируем библиотеку типов данного серверного объекта, создав методы GetPays. AddPay, DelPay для доставки данных клиентскому приложению, добавления и удаления записей, а также метод DoTrans, реализующий распределенную транзакцию (удаление записи о выбранном товаре из таблицы STOCKTABLE в базе данных IBLOCAL и добавление по одной записи в таблицу заказов на доставку delivery.db в базе данных DBDEMOS и в таблицу счетов за заказы ord.dbf в базе данных paydb, рис. 23).

Рис. 23. Библиотека типов серверного объекта, управляющего распределенными транзакциями

Реализация этих методов имеет следующий вид:

unit pay1;
//MTS server for managing distributed transactions
//By N.Elmanova
//04.12.1998

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComServ, ComObj, VCLCom, StdVcl, BdeProv, BdeMts, DataBkr, DBClient,
  MtsRdm, Mtx, paysrv_TLB, dels_TLB, st_TLB, DBTables, Provider, Db;

type
  Tpays = class(TMtsDataModule, Ipays)
    paytable: TTable;
    PayProvider: TProvider;
    Database3: TDatabase;
    Query6: TQuery;
    Session3: TSession;
    Query5: TQuery;
 private
 FStockDM1: IStockDM1;
 FDelDM: IDelDM;
    { Private declarations }
  public
    { Public declarations }
  protected
    function GetPays: OleVariant; safecall;
    procedure AddPay(Pnum: Integer; Pval: Double; const Address: WideString);
      safecall;
    procedure DelPay(Pnum: Integer); safecall;
    procedure DoTrans(Num: Integer; Val: Double; const Addr,
      Gname: WideString); safecall;
  end;

var
  pays: Tpays;

implementation

{$R *.DFM}

function Tpays.GetPays: OleVariant;
begin
     Result:=PayProvider.Data;
     SetComplete;
end;

procedure Tpays.AddPay(Pnum: Integer; Pval: Double;
  const Address: WideString);
begin
try
    paytable.open;
    paytable.append;
    paytable.fieldbyname('OrdNum').Value:=PNum;
    paytable.fieldbyname('Payment').Value:=Pval;
    paytable.fieldbyname('Address').Value:=Address;
    paytable.post;
    paytable.close; 
    SetComplete;
except
    SetAbort;
end;
end;

procedure Tpays.DelPay(Pnum: Integer);
begin
try
  Database3.Open;
  paytable.Open;
  paytable.SetRangeStart;
  paytable.FieldByName('ordnum').AsInteger:=Pnum;
  paytable.SetRangeEnd;
  paytable.FieldByName('ordnum').AsInteger:=Pnum;
  paytable.ApplyRange;
  paytable.Delete;
  paytable.Close;
  Database3.Close;
  SetComplete;
except
  SetAbort;
raise;
end;
end;

procedure Tpays.DoTrans(Num: Integer; Val: Double; const Addr,
  Gname: WideString);
begin
try
  OleCheck(ObjectContext.CreateInstance(CLASS_StockDM1, IStockDM1, FStockDM1));
  OleCheck(ObjectContext.CreateInstance(CLASS_DelDM, IDelDM, FDelDM));
  FStockDM1.DeleteGoods(Num);
  FDelDM.AddDelivery(Num,Gname,Addr);
  AddPay(Num,Val,Addr);
except
  DisableCommit;
raise;
end;
  EnableCommit;
end;
initialization
  TComponentFactory.Create(ComServer, Tpays,
    Class_pays, ciMultiInstance, tmApartment);
end.

Прокомментируем приведенный выше код для метода DoTrans. Этот код реализует распределенную транзакцию, вызывая методы двух порожденных ей серверных объектов и выполняя собственные манипуляции с таблицей счетов. При вызове метода DoTrans клиентским приложением все три серверных объекта функционируют согласованно.

В начале выполнения создается так называемый контекст транзакции - интерфейс ITransactionContextEx. Этот интерфейс контролирует выполнение транзакции и обладает методами CreateInstance (создание экземпляра порожденного объекта), Commit (завершение транзакции) и Abort (откат транзакции). Отметим, что если для порождения серверного объекта MTS клиентским приложением используется компонент TDCOMConnection, то для порождения серверного объекта другим серверным объектом используется вызов метода CreateInstance интерфейса ITransactionContextEx. Параметрами этого метода являются CLSID объекта, интерфейс объекта и указатель на объект (возвращаемый параметр).

Далее следуют вызовы методов порожденных серверных объектов и собственные манипуляции с данными. Если все операции были успешны, транзакция завершена, и может быть выполнен метод Commit интерфейса ITransactionContextEx. Если же операции были неуспешны, и в одном или обоих порожденных серверах либо во время собственных манипуляций с данными возникнут исключения (например, другой пользователь уже удалил запись из списка товаров, сделав заказ, или какая-то из таблиц заблокирована), будет вызван метод Abort.

Для тестирования распределенных транзакций установим все три серверных объекта в один и тот же "пакет" (рис. 24):

Рис. 24. Серверные объекты, участвующие в распределенной транзакции

6.2. Создание клиентского приложения, использующего распределенные транзакции

Для тестирования созданного ранее сервера и инициации распределенных транзакций создадим клиентское приложение, имитирующее процесс оформления заказов. На главной форме приложения поместим кнопку с надписью "Connect", три компонента TDCOMConnection, связанные с соответствующими серверами, три компонента TClientDataSet, связанные с соответствующими компонентами TDCOMConnection, три компонента TDataSource, связанные с компонентами TClientDataSet и блокнот из двух страниц. На одной из страниц блокнота разместим компонент TDBGrid, отображающий данные из таблицы со списком товаров на складе, компонент TEdit для ввода пользователем адреса доставки, и кнопку для инициирования транзакции - принятия заказа. На второй странице поместим два компонента TDBGrid для отображения данных из двух других таблиц и компонент TSplitter между ними (рис. 25):

Рис. 25. Клиентское приложение для тестирования распределенных транзакций

Создадим обработчики событий, связанных с нажатием на кнопки:

unit allcl1;
//Client application for using distributed transactions
//By N.Elmanova
//05.12.1998

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Grids, DBGrids, Db, DBClient, StdCtrls, MConnect, ExtCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    PageControl1: TPageControl;
    TabSheet1: TTabSheet;
    TabSheet2: TTabSheet;
    DCOMConnection1: TDCOMConnection;
    ClientDataSet1: TClientDataSet;
    DataSource1: TDataSource;
    DBGrid1: TDBGrid;
    Edit1: TEdit;
    Label1: TLabel;
    Button1: TButton;
    DBGrid2: TDBGrid;
    DBGrid3: TDBGrid;
    Splitter1: TSplitter;
    DCOMConnection2: TDCOMConnection;
    ClientDataSet2: TClientDataSet;
    DataSource2: TDataSource;
    DCOMConnection3: TDCOMConnection;
    ClientDataSet3: TClientDataSet;
    DataSource3: TDataSource;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var n:integer;val:double;gnam,addr:widestring;
begin
try
 n:= ClientDataSet1.FieldByName('GOODSNUMBER').Value;
 val:= ClientDataSet1.FieldByName('PRICE').Value;
 gnam:= ClientDataSet1.FieldByName('GOODSNAME').Value;
 addr:=Edit1.Text;
 DcomConnection2.Connected:=true;
 DCOMConnection2.AppServer.DoTrans(n,val,addr,gnam);
 ShowMessage('Заказ принят');
except
 ShowMessage('Заказ не принят ');
end;
DcomConnection2.Connected:=false;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
DCOMConnection1.Connected:=true;
DCOMConnection2.Connected:=true;
DCOMConnection3.Connected:=true;
CLientdataset1.data:=Dcomconnection1.Appserver.GetGoods;
CLientdataset2.data:=Dcomconnection2.Appserver.GetPays;
CLientdataset3.data:=Dcomconnection3.Appserver.GetDelivery;
DCOMCOnnection1.Connected:=false;
DCOMCOnnection2.Connected:=false;
DCOMCOnnection3.Connected:=false;
end;
end.

Для тестирования распределенных транзакций запустим приложение. Введем адрес в компонент TEdit, выберем строку в списке товаров и нажмем на кнопку "Заказать" (рис. 26).

Рис. 26. Тестирование распределенной транзакции

В результате получим сообщение о том, что заказ принят

Нажав на кнопку Connect, обновим данные в компонентах TDBGrid. При этом запись, выбранная ранее, исчезнет, а в двух других компонентах TDBGrid появятся две новых (рис. 27):

Рис. 27. Результат выполнения распределенной транзакции

Отметим, что, если не нажать на кнопку Connect, данные в компонентах TDBGrid останутся прежними (в данном примере не предусмотрено обновление данных после выполнения транзакции), у пользователя есть возможность попытаться повторно выбрать для заказа уже выбранную ранее запись (то есть заказать товар, заказ на который уже оформлен). В этом случае один из трех серверных объектов будет пытаться удалить уже удаленную запись (если вспомнить текст соответствующего SQL-запроса, она идентифицируется значением первичного ключа), и в этом случае соответствующая часть транзакции не завершится. Соответственно, произойдет откат назад всей распределенной транзакции.

Сведения о завершенных и отмененных транзакциях можно получить, выбрав в MTS Explorer опцию Transaction Statistics (рис. 28):

Рис. 28. Просмотр статистики выполнения и отката распределенных транзакций

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

Отметим, однако, что при создании подобного рода серверных объектов и их клиентов следует всегда пытаться исключить возможность ошибочных действий пользователя, поэтому более корректным было бы иметь следующий обработчик события, связанного с нажатием на кнопку "Заказать":

procedure TForm1.Button1Click(Sender: TObject);
var n:integer;val:double;gnam,addr:widestring;
begin
try
 n:= ClientDataSet1.FieldByName('GOODSNUMBER').Value;
 val:= ClientDataSet1.FieldByName('PRICE').Value;
 gnam:= ClientDataSet1.FieldByName('GOODSNAME').Value;
 addr:=Edit1.Text;
 DcomConnection2.Connected:=true;
 DCOMConnection2.AppServer.DoTrans(n,val,addr,gnam);
 ShowMessage('Заказ принят');
 Button2Click(self);
except
 ShowMessage('Заказ не принят ');
end;
DcomConnection2.Connected:=false;
end;
Отметим также, что при нажатии на кнопку "Заказать" в случае отсутствия данных в компонентах TDBGrid в клиентском приложении возникнет исключение, связанное с отсутствием нужного поля в компоненте TClientDataSet. Следовательно, данная кнопка в такой ситуации должна быть невыбираемой. Поэтому установим значение ее свойства Enabled равным False и перепишем обработчик события,связанного с нажатием на кнопку Connect::
procedure TForm1.Button2Click(Sender: TObject);
begin
try
 DCOMCOnnection1.Connected:=true;
 DCOMCOnnection2.Connected:=true;
 DCOMCOnnection3.Connected:=true;
 CLientdataset1.data:=Dcomconnection1.Appserver.GetGoods;
 CLientdataset2.data:=Dcomconnection2.Appserver.GetPays;
 CLientdataset3.data:=Dcomconnection3.Appserver.GetDelivery;
 Button1.Enabled:=true;
except
 Button1.Enabled:=false;
 ShowMessage('Один из серверных объектов недоступен');
end;
DCOMCOnnection1.Connected:=false;
DCOMCOnnection2.Connected:=false;
DCOMCOnnection3.Connected:=false;
end;

Итак, мы создали три серверных объекта, реализующих распределенную транзакцию, связанную с удалением записи из одной таблицы и добавлением записи в две другие таблицы, и клиентское приложение, инициирующее выполнение таких транзакций. Следует обратить внимание на то, что таблицы, участвующие в транзакции, содержатся в трех физически разных базах данных.

Итак, на примере Delphi 4 и Microsoft Transaction Server мы рассмотрели возможность и способы использования технологий COM и COM+ для организации распределенных вычислений и управления распределенными транзакциями.

Следующая статья данного цикла будет посвящена другой технологии, используемой при организации распределенных вычислений - DCE (Distributed Computing Environment), и ее реализации в многоплатформенном сервере приложений Inprise Entera.

<< Назад | Содержание

 

Новости мира IT:

Архив новостей

Последние комментарии:

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 985 1945361
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2015 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...