Создание клиентского приложения, использующего распределенные транзакции
Для тестирования созданного ранее сервера и инициации распределенных транзакций создадим клиентское приложение, имитирующее процесс оформления заказов. На главной форме приложения поместим кнопку с надписью "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.
<< |