⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 tiofp documentation - the visitor pattern and sql databases.htm

📁 tiOPF 面向对象的数据库持久层持久层开发的框架
💻 HTM
📖 第 1 页 / 共 5 页
字号:
 FbDirty: boolean;
protected
  function AcceptVisitor : boolean ; override ;
public
  procedure Execute( pVisited : TVisitedAbs ) ; override ;
  property Dirty : boolean read FbDirty write FbDirty ;
end ;</PRE>
<P>And the implementation looks like this:</P><PRE>function TVisPerObjIsDirty.AcceptVisitor : boolean;
begin
  result := ( Visited is TPerObjAbs ) and ( not Dirty ) ;
end;</PRE><PRE>procedure TVisPerObjIsDirty.Execute(pVisited: TVisitedAbs);
begin
  Inherited Execute( pVisited ) ;
  if not AcceptVisitor then
    exit ; //==&gt;
  Dirty := TPerObjAbs( pVisited ).ObjectState in
   [ posCreate,
   posUpdate,
   posDelete
   ]
end;</PRE>
<P>The call to this Visitor is wrapped up in the TPerObjAbs.GetDirty method like 
this:</P><PRE>function TPerObjAbs.GetDirty: boolean;
var
  lVis : TVisPerObjIsDirty ;
begin
  lVis := TVisPerObjIsDirty.Create ;
  try
    self.Iterate( lVis ) ;
    result := lVis.Dirty ;
  finally
    lVis.Free ;
  end ;
end;</PRE>
<P>This lets us extend the application’s main form by adding an ActionList. 
Double click the ActionList and add three actions as shown below. Move the code 
from the three save buttons OnClick methods to the actions then hook the save 
buttons up to the actions.</P>
<P>In the ActionList&amp;apos;s OnUpdate method add the following code to check 
the Dirty state of the data hierarchy and enable or disable the save buttons as 
necessary:</P><PRE>procedure TFormMain_VisitorManager.ALUpdate(Action: TBasicAction;
  var Handled: Boolean);
var
  lDirty : boolean ;
begin
  lDirty := FPeople.Dirty ;
  aSaveToInterbase.Enabled := lDirty ;
  aSaveToCSV.Enabled := lDirty ;
  aSaveToTXT.Enabled := lDirty ;
  Handled := true ;
end;</PRE>
<P>Now the save buttons are disabled when the object hierarchy is clean, and 
disabled when the object hierarchy is dirty like this:</P>
<TABLE cellSpacing=0 cellPadding=0>
  <TBODY>
  <TR class=Normal>
    <TD vAlign=top width=295><B>The object Hierarchy is clean</B></TD>
    <TD vAlign=top width=295><B>The Object Hierarchy is dirty</B></TD></TR>
  <TR class=Normal>
    <TD vAlign=top width=295>
      <P align=center><IMG height=175 
      src="tiOFP Documentation - The Visitor pattern and SQL databases_files/3_TheVisitorAndSQLDatabases_clip_image002_0001.jpg" 
      width=286></P></TD>
    <TD vAlign=top width=295>
      <P align=center><IMG height=175 
      src="tiOFP Documentation - The Visitor pattern and SQL databases_files/3_TheVisitorAndSQLDatabases_clip_image004.jpg" 
      width=286></P></TD></TR></TBODY></TABLE>
<H2>Adding more database constraints</H2>
<P>Now let’s create a slightly more realistic database schema by adding a unique 
key on the People table. This can be done by modifying the create SQL as shown 
below: </P><PRE>create table People
   ( OID Integer not null,
   Name VarChar( 20 ),
   EMailAdrs VarChar( 60 ),
   Primary Key ( OID )) ;</PRE><PRE>create unique index People_uk on People ( name, EMailAdrs ) ;</PRE><PRE>insert into People values ( 1, "Peter Hinrichsen", "peter_hinrichsen@techinsite.com.au");
insert into People values ( 2, "Don Macrae", "don@xpro.com.au" ) ;
insert into People values ( 3, "Malcolm Groves", "malcolm@madrigal.com.au" ) ;</PRE>
<P>Run the application and deliberately try to insert duplicate name records to 
see how the framework handles a database error. Add two duplicate records then 
click ‘Save to Interbase’. You will get unique key error like the one shown 
below:</P>
<P><IMG height=229 
src="tiOFP Documentation - The Visitor pattern and SQL databases_files/3_TheVisitorAndSQLDatabases_clip_image001_0008.gif" 
width=521> </P>
<P>Click the ‘Read from Interbase’ button and you will find that one record was 
saved, while the other was not. The record that was not saved is still in the 
client with an ObjectState of posCreate. What we clearly want here is some 
transaction management so either all objects are saved, or none are saved. 
Before we can setup transaction management, we must modify the framework so all 
the visitors share the same database connection.</P>
<H2>Share the database connection between Visitors</H2>
<P>The First step towards providing transaction support is to change the 
framework so all calls to the database are funneled through the same database 
connection and transaction object. You will remember that the TVisSQLAbs class 
owned an instance of a TIBDatabase and TIBTransaction. This meant that every 
visitor was processed within its own database connection and transaction. 
Transactions would be implicitly started and committed by Delphi around each SQL 
statement. What we want is for all visitors to share the same database 
connection, and for the Visitor Manager to have control over the database 
transaction.</P>
<P></P>To achieve this, we will do four things:
<P></P>
<OL>
  <LI>Move the database and transaction objects out of the TVisSQLAbs class and 
  wrap them up in an object we will call TtiDatabase. (This will become 
  especially useful when we start work on building a swappable persistence 
  layer. Also, one of the slowest things you can do to a database is connect to 
  it, so sharing a database connection between visitor will improve the 
  application’s performance.) 
  <LI>Modify the TVisSQLAbs class with a Database property. The SetDatabase 
  method will assign a field variable for later reference, and hook the Query 
  object up to the database connection at the same time. 
  <LI>Create a single instance (Singleton pattern) of the database object that 
  has application wide visibility. 
  <LI>Modify the Visitor Manager so it sets each Visitors Database property 
  before executing, and then clears it when done. </LI></OL>
<P>These four steps are detailed below.</P>
<P>Firstly, we will move the database and transaction objects into their own 
class. The interface of TtiDatabase is shown here:</P><PRE>TtiDatabase = class( TObject )
private
  FDB : TIBDatabase ;
  FTransaction : TIBTransaction ;
public
  constructor Create ;
  destructor Destroy ; override ;
  property DB : TIBDatabase read FDB ;
end ;</PRE>
<P>And the implementation of TtiDatabase is shown here:</P><PRE>constructor TtiDatabase.Create;
begin
  inherited ;
  FDB := TIBDatabase.Create( nil ) ;
  FDB.DatabaseName := 'C:\TechInsite\OPFPresentation\Source\3_SQLVisitors\Test.gdb' ;
  FDB.Params.Add( 'user_name=SYSDBA' ) ;
  FDB.Params.Add( 'password=masterkey' ) ;
  FDB.LoginPrompt := False ;
  FDB.Connected := True ;
  FTransaction := TIBTransaction.Create( nil ) ;
  FTransaction.DefaultDatabase := FDB ;
  FDB.DefaultTransaction := FTransaction ;
end;</PRE><PRE>destructor TtiDatabase.Destroy;
begin
  FTransaction.Free ;
  FDB.Free ;
  inherited;
end;</PRE>
<P>As well as moving the database connection out of the Visitor class, and 
allowing it to be shared between all visitors in the application, we have 
wrapped the TIBDatabase component in our own code. This is an important step 
towards using the Adaptor pattern to make our framework independent of any 
one-database vendor’s API.</P>
<P>Next, we modify the TVisSQLAbs class. The owned database and transaction 
objects are removed and a field variable is added to hold a pointer to the 
shared database object. A database property with a SetDatabase method is added 
and SetDatabase is responsible for hooking the query object up to the database 
connection. The interface of TVisSQLAbs is shown below:</P><PRE>TVisSQLAbs = class( TVisPerObjAwareAbs )
private
  FDatabase: TtiDatabase;
  procedure SetDatabase(const Value: TtiDatabase);
public
  constructor Create ; override ;
  destructor Destroy ; override ;
  property Database : TtiDatabase read FDatabase write SetDatabase ;
end ;</PRE>
<P>The implementation of TVisSQLAbs.SetDatabase is shown below. Notice how there 
is protection against Value being passed as nil.</P><PRE>procedure TVisSQLAbs.SetDatabase(const Value: TtiDatabase);
begin
  FDatabase := Value;
  if FDatabase &lt;&gt; nil then
    FQuery.Database := FDatabase.DB
  else
   FQuery.Database := nil ;
end;</PRE>
<P>Thirdly we require a globally visible, single instance of the database 
connection. (This is not how we will ultimately implement a database connection 
– we will use a thread safe database connection pool, but this is sufficient to 
get us started.) I use something I call a poor man’s Singleton, a unit wide 
variable hiding behind a globally visible function. This implementation does not 
come close to providing what a GoF singleton requires, but it does the job and 
is quick to implement. The code for the single instance database connection is 
shown below:</P><PRE>interface
function gDBConnection : TtiDatabase ;
implementation
var
  uDBConnection : TtiDatabase ;
function gDBConnection : TtiDatabase ;
begin
  if uDBConnection = nil then
    uDBConnection := TtiDatabase.Create ;
  result := uDBConnection ;
end ;</PRE>
<P>The final changes we must make are to extend TVisitorMgr.Execute with some 
code to set the database property on the visitor before calling 
TVisitor.Execute. There is a complication here because not all Visitors will 
have a database property, only those that descend from TVisSQLAbs. As a work 
around we will check the type of the Visitor, and if it descends from 
TVisSQLAbs, assume it has a database property and hook it up to the default 
application database object. We also clear the Visitors database property after 
the visitor has executed. (This is a bit of a hack and is not how we solve the 
problem in the framework. We actually have another class called a 
TVisitorController that is responsible for performing tasks before and after 
visitors execute. Much more elegant, but rather more complex too.) The modified 
TVisitorMgr.Execute method is shown below:</P><PRE>procedure TVisitorMgr.Execute(const pCommand: string; const pData: TVisited);
var
  i : integer ;
  lVisitor : TVisitor ;
begin
  for i := 0 to FList.Count - 1 do
    if SameText( pCommand, TVisitorMapping( FList.Items[i] ).Command ) then
    begin
      lVisitor := TVisitorMapping( FList.Items[i] ).VisitorClass.Create ;
      try
        if ( lVisitor is TVisSQLAbs ) then
          TVisSQLAbs( lVisitor ).Database := gDBConnection ;
        pData.Iterate( lVisitor ) ;
        if ( lVisitor is TVisSQLAbs ) then
          TVisSQLAbs( lVisitor ).Database := nil ;
      finally
        lVisitor.Free ;
      end ;
  end ;
end;</PRE>
<P>The changes we have made to this section ensure that all database activity is 
channeled through a single database connection. The next step is to modify 
TVisitorMgr.Execute so all SQL statements are called within a single database 
transaction.</P>
<H2>Manage transactions</H2>
<P>To add transaction support, we must to two things:</P>
<OL>
  <LI>1. Expose transaction support through the procedures StartTransaction, 
  Commit and RollBack on the database wrapper class TtiDatabase 
  <LI>2. Extend the Visitor Manager’s Execute method with the ability to start a 
  transaction when a group of objects is passed to Execute and commit or roll 
  back the transaction when processing has either finished successfully, or 
  failed. </LI></OL>
<P>Firstly, we extended interface of TtiDatabase, with the additional methods 
StartTransaction, Commit and RollBack as in the code below:</P><PRE>TtiDatabase = class( TObject )
private
  FDB : TIBDatabase ;
  FTransaction : TIBTransaction ;
public
  constructor Create ;
  destructor Destroy ; override ;
  property DB : TIBDatabase read FDB ;
  procedure StartTransaction ;
  procedure Commit ;
  procedure Rollback ;
end ;
 </PRE>
<P>The implementation of TtiDatabase is shown next. You can see that the 
StartTransaction, Commit and RollBack methods simply delegate the call to the 
owned TIBTransaction object. The reasons for this shall be looked at in more 
detail when we study swappable persistence layers and the Adaptor pattern.</P><PRE>procedure TtiDatabase.StartTransaction;
begin
  FTransaction.StartTransaction ;
end;

procedure TtiDatabase.Commit;
begin
  FTransaction.Commit ;
end;

procedure TtiDatabase.Rollback;
begin
  FTransaction.RollBack ;
end;
</PRE>
<P>Secondly, we extend the TVisitorManager.Execute method transaction support. 
This means changes in three places. Firstly we start the transaction after 
assigning the database connection to the visitor’s Database property. Next we 
wrap the call to pData. Iterate( lVisitor ) up in a try except block and call 
RollBack if an exception is raised. We re-raise the exception after calling 
RollBack so it will bubble to the top of any exception handling code we have 
ad

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -