Sobald die Komponente B die Transaktion in Schritt 7 durchführt, passiert nicht alles. Komponente B erbt den Transaktionsstatus von Komponente A. Dadurch kann sie die Transaktion nicht vollständig durchführen. Die wirkliche Transaktion wird in Schritt 8 beendet, sobald das letzte Objekt mit einem Transaktionsstatus verschwindet. Zu diesem Zeitpunkt werden die Änderungen zurückgeführt, die in den Tabellen 'customer' und 'orders' durchgeführt wurden, da diese in einem SQL SERVER gespeichert sind. Die Änderungen in der VFP Datenbank, die Email-Benachrichtigung und die Einträge in der Textdatei bleiben jedoch erhalten. Bei der Ausführung von SetAbort()/SetCommit() werden nur Resource Manager (Datenbanken) beeinflußt, deren Transaktionen durch den MS DTC gesteuert werden.
Die Transaktionsunterstützung wird für jede Komponente separat eingestellt. Allerdings können Transaktionen auch über mehrere Pakete verteilt sein. Die Einstellung für die Transaktionsunterstützung einer Komponente wird bei den Eigenschaften einer Komponente eingestellt.
Es gibt 4 verschiedene Einstellungen
Einstellung | Beschreibung |
---|---|
Requires a transaction |
falls das aufrufende Objekt in einer Transaktion arbeitet, arbeitet die Komponente in der gleichen Transaktion. Falls das aufrufende Objekt keine Transaktion unterstützt, erzeugt das Objekt eine neue Transaktion |
Requires a new transaction |
es wird immer eine neue Transaktion erzeugt |
Supports transactions |
Falls die aufrufende Komponente eine Transaktion unterstützt, arbeitet die Komponente in der gleichen Transaction |
Does not support transactions |
Die Komponente unterstützt keine Transaktionen |
SetComplete bzw. SetAbort erfüllen zwei Zwecke. Einerseits führen sie Durchführung (Commit) einer Transaktion oder deren Abbruch (Rollback) herbei, andererseits deactivieren sie Objekte, um sie in den zustandslosen Modus zu überführen. Tatsächlich können diese beiden Methoden verwenden werden, um die JIT Activation zu unterstützen, ohne jegliche Gedanken an eine Transaktionsunterstützung. SetComplete ermöglicht die Freigabe von Ressourcen/Speicher, um die Skalierbarkeit der Anwendung zu erhöhen. Das Kontextobjekt stellt einige andere Methoden zur Verfügung, die für Transaktionen hilfreich sind: EnableCommit, DisableCommit und IsInTransaction. Das nachfolgende Beispiel zeigt, wie man Transaktionen in VFP verwalten kann :
LPARAMETER tcCustID
LOCAL llFound, loMTX, loContext
loMTX = CREATEOBJECT("MTXAS.APPSERVER.1")
loContext = loMTX.GetObjectContext()
USE customer AGAIN SHARED
LOCATE FOR UPPER(cust_id) == UPPER(tcCustID)
llFound = FOUND()
IF FOUND()
loContext.SetComplete()
ELSE
loContext.SetAbort()
ENDIF
RETURN llFound
In diesem Szenario kann man sich vorstellen, daß eine andere Komponente schon eine Aktualisierung auf die Tabelle 'orders' vorgenommen hat. Falls die entsprechende 'CustomerId' nicht gefunden wird, wird die Transaktion zurückgeführt.
In diesem Szenario wird mit Remote-Ansichten auf eine SQL SERVER-Tabelle gearbeitet. Transaktionen mit VFP Tabellen werden nicht unterstützt.
Tip: Die Verbindung zu den Remote Daten müssen ohne jeglichen Login-Dialog geschehen. Die Eigenschaft DispLogin muß auf 3 – DB_PROMPTNEVER stehen. Bei dem Zugriff über SPT, kann man die SQLSETPROP()-Funktion dazu verwenden :
SQLSETPROP(0, 'DispLogin', DB_PROMPTNEVER)
Transaktionen können normalerweise nur auf einen einzigen Resource Manager zugreifen. Ein Resource Manager ist einfach eine Server Resource, die in die Transaktion eingebunden ist. Normalerweise werden Resource Manager als Datenbanken betrachtet. Obwohl der am meisten anzutreffende Fall ist, ist dies nicht die einzige Art von Resource Managern.
Vorteilhaft wäre es, wenn Transaktionen sich über mehrere Datenbanken erstrecken könnten. Dies soll am Beispiel einer Bank erklärt werden. Ein Bank ist in mehrere Sparten gegliedert und jede Sparte verwaltet lokal die Daten ihrer Konten, die zu ihrer Sparte gehören.
Man öffnet zwei Konten in verschiedenen Sparten und möchte nun einen Geldbetrag von einem Konto in Sparte zu einem Konto in Sparte B transferieren. Um dies durchzuführen, muß der Geldbetrag in dem Konto in Sparte A abgezogen werden und dem Konto in Sparte B hinzugezählt werden. Ohne verteilte Transaktionen kann der Geldbetrag zwar erfolgreich in Sparte A abgezogen werden, aber der Betrag konnte dem Konto in Sparte B aus irgendwelchen Gründen nicht gutgeschrieben werden. Bei einzelnen Transaktionen in Sparte A und Sparte B ist das Geld solange verloren , bis die Transaktion in Sparte B schließlich durchgeführt werden konnte.
Mit einer verteilten Transaktion, die beide Datenbanken umfaßt, würde der Abbruch in Sparte B auch die Änderungen in Sparte A zurückführen. Dadurch wäre kein Geld kurzfristig verloren gegangen.
Verteilte Transaktionen verwenden das sogenannte Two-Phase -Commit Protokoll und einen Transaktions-koordinator. Die verschiedenen Phasen werden vom dem Distributed Transaction Coordinator (DTC) verwaltet. Der DTC benachrichtigt alle Resource Manager, die in der Transaktion enthalten sind, um die Durchführung vorzubereiten. In diesem Schritt muß der Resource Manager sicher sein, daß die Durchführung stattfindet und gibt an den DTC einen Erfolg oder einen Mißerfolg weiter. Sobald alle Resource Manager sich gemeldet haben, teilt ihnen der DTC mit, ob sie die Transaktion durchführen sollen oder zuückfahren. Sobald alle ok zurückgemeldet haben, teilt der DTC ihnen mit, daß sie die Änderungen durchführen sollen. Falls ein beteiligter Resource Manager ein negatives Ergebnis zurückgibt, teilt der DTC allen beteiligten Resource Manager mit, daß die Änderungen zurückgestzt werden sollen
Nun wird ein MTS COM-Server erzeugt. Dies ist recht einfach, wenn man schon einmal eine VFP COM-Komponente erzeugt hat.
DEFINE CLASS clsServer AS CUSTOM OLEPUBLIC
PROCEDURE Hello
RETURN "Hallo Welt!"
ENDPROC
loServer=CREATEOBJECT("prjServer.clsServer")
? loServer.Hello()
Ein Paket ist eine Auflistung von Komponenten, die in demselben Prozess laufen. Pakete definieren Grenzen für einen Serverprozess, der auf einem Servercomputer läuft. Wenn eine Sales-Komponente und eine Purchasing-Komponente in unterschiedliche Pakete exportiert werden, laufen diese in unterschiedlichen Prozessen. Sollte die Sales-Komponente unerwartet abbrechen, hat dies keine Auswirkung auf die Purchasing-Komponente.
Nachfolgend wird beschrieben, wie eine VFP COM-Komponente in die MTS-Umgebung installiert wird.
Der Zugriff auf die Komponente erfolgt mit dem gleichen Code,wie man auch die COM-Komponente aufruft
loServer = CREATEOBJECT("prjServer.clsServer")
? loServer.Hello()
Wenn man den MTS Explorer wieder öffnet, sieht man wie die Komponente rotiert. Mit der Option ‘Status View’ kann man den aktuellen Zustand der Komponente feststellen.
Wenn man das Objekt (RELEASE loServer)aus dem Speicher löscht, entfernt MTS dessen Referenz.
Nun stellt sich die Frage, was eigentlich bei einem Zugriff auf eine MTS-Komponente geschieht ?
Der einfachste Weg um zu sehen, was intern passiert, ist, den Client Request vom Anfang bis zum Ende nachzuverfolgen. Wir haben eine Anwendung, die auf einem Client C läuft, die einen Aufruf an die MTS Komponente M auf dem Server S stellt. Die Komponte M greift auf eine Datenbank auf einem anderen Server D zu.
C benutzt DCOM um eine Instanz der Komponente M anzulegen. Da M ein In-Proc-Server ist, kann dieser via DCOM nicht benutzt werden. Hier beginnt die Aufgabe des Transaction Server. Er ändert einige Einträge in der Registry. Wenn eine Komponente in einem Package registriert wird, editiert der Transaction Server die CLSID der Komponente. Der InprocServer32-Schlüssel wird entfernt uns fügt einen LocalServer32-Schlüssel hinzu, der auf den Transaction Server zeigt. Weiterhin wird die CLSID Package gespeichert, die die Komponente enthält. Dann sieht es ungefähr folgendermaßen aus :
LocalServer32 = MTX.EXE /p:{968690F2-1E2B-11D0-BEC5-00AA00A2FA25}
MTX.EXE arbeitet als eine Host-Prozeß für die Komponente. Tatsächlich arbeitet er als Host-Prozeß für alle Komponenten in dem Package.
Wenn die DCOM-Anfrage den Transaction Server erreicht, sieht es die MTX.EXE als lokalen Server und instanziert ihn. Dies bedeutet, daß die DCOM-Anfrage die Schnittstelle der MTX.EXE benutzt. Diese überprüft die CLSID und IID und benutzt ihrerseits COM, um mit der Komponente zu kommunizieren und ein Objekt daraus zu instanzieren. Danach sendet MTX.EXE einen Wrapper an den Client, der wie die Komponente aussieht, so daß der Client mit der MTX.EXE kommuniziert, diese aber wie die benötigte Komponente anspricht. In dem Wrapper befinden sich noch zusätzliche Informationen, die den sogenannten 'Kontext' darstellen. Da alle Aufrufe des Clients an die MTX.EXE gehen, hat der Transaction Server Kenntnis über alle Vorgänge.
Weiterhin wird bei einem Start des MTX-Prozesses der ODBC Resource Dispenser geladen und das 'Connection Pooling' angeschaltet. Der DTC wird ebenfalls initialisiert. Wenn die Komponente instanziert ist, beginnt in Ihrem Auftrag eine DTC Transaktion.
Jetzt haben wir einen Client, der in dem Glauben ist, er hat einen Zeiger auf ein Objekt, welches in Wirklichkeit ein Wrapper ist. Wir haben einen Host-Prozeß (Server-Prozeß), der auf dem Server läuft und eine Komponente umschließt. Schließlich haben wir eine ODBC-Umgebung mit 'Connection pooling' aktiviert und eine mit der Komponente verbundene DTC Transaktion gestartet.
Nun ruft die Anwendung auf C eine Methode in M auf. Der Transaction Server überwacht den Methodenaufruf. Der Code der Methode wird ausgeführt und erzeugt eine ODBC-Umgebungs(-kennung). Dann erzeugt es eine ODBC-Verbindung. Wenn dies geschieht, macht der ODBC Resource Dispenser 2 Dinge. Er aktiviert das connection pooling und fragt MTS nach der Transaktion Id der Komponente. Transaction Server gibt diese Informatio zurück und der ODBC Resource Dispenser setzt eine Option, so daß die Connection in die DTC-Transaktion einbezogen wird.
Auf dem Resource Manager D wird der Befehl abgehandelt und die Transaktion beginnt. Der Resource Manager kommuniziert mit dem DTC, daß er die Transaktion begonnen hat und registriert diese im DTC.
Die Komponente führt Ihre Transaktionen durch, die in D ausgeführt werden. Wenn diese beendet sind, schließt es die Verbindung und die Umgebung. Der ODBC Resource Dispenser überprüft den Status der Verbindung und hält diese in seinem Pool oder schließt diese.
Der Code der Komponente benutzt deren Kontext, ruft 'SetComplete' auf, um klarzustellen, daß alles erledigt ist und kehrt von der Methode zurück. Jetzt führt MTS die DTC-Transaktion durch und löscht die Komponente, obwohl der Client noch eine Referenz darauf hat. Der Client hat jedoch nur eine Referenz auf den Wrapper. Beim nächsten Aufruf der Methode wird intern eine neue Instanz der Komponente erzeugt.
Sobald die DTC Transaktion durchgeführt ist, informiert der DTC D die Transaktion vorzubereiten und durchzuführen. Falls die Durchführung der Transaktion scheitert, D informiert den DTC, welcher MTS informiert, MTS informiert den Client. Solange der Methodencode ausgeführt wird, kann die Rückgabe der Methode geändert werden, falls das Fehlschlagen der Transaktion vorgesehen ist. Dies sollte beim Design der Komponente vorgesehen werden.
Schließlich wird der Client die Referenz auf die Komponente entfernen. Nun kann man erwarten, daß der MTS-Prozeß ebenfalls beendet wird, da er von keiner Komponente mehr benutzt wird. Standardmäßig bleibt der Prozeß noch 3 Minuten erhalten, so daß andere Anfragen nicht erst den MTS-Prozeß wieder starten müssen. In COM kann sich ein Server selbständig registrieren, wenn er startet, so daß andere Anfragen für ein bestimmtes Objekt diesen Typs zu der bestehenden Instanz umgeleitet werden und dadurch verhindert wird, daß eine neue Instanz erstellt wird. MTX.EXE führt dies für die Objekte durch, für die sie als Host dient, so daß alle Anfragen zu dem gleichen Prozeß gelangen.
Bis jetzt wurden die Grundlagen zur Installation eines VFP Servers in MTS vorgestellt. Es wurde eine VFP Komponente in einen MTS Prozess eingehüllt, der die Zugriffsrechte, Transktionszustand und andere allgemeine Aufgaben verwaltet. Alle VFP Server, die mit MTS benutzt werden, werden in dieser Art registriert. Um die Vorteile der MTS-Laufzeitumgebung zu benutzen, kann die Komponente direkt mit dieser kommunizieren. Weiterhin kann die Registrierung komplett automatisiert werden, da MTS eine Administrationsschnittstelle als COM-Interface zur Verfügung stellt.
Das Kontextobjekt-Modell ist das Modell, das man für die Erzeugung von MTS-Anwendungen verwenden sollte. Die Komponente muß speziell für MTS entwickelt werden, kann dann aber von den Vorteilen von MTS Gebrauch machen.
Auf das Kontextobjekt kann folgendermaßen zugegriffen werden:
#DEFINE MTX_CLASS "Mtxas.AppServer.1"
LOCAL loMTX, loContext
loMtx = CREATEOBJECT(MTX_CLASS)
loContext = loMtx.GetObjectContext()
Der Shared Property Manager (SPM) ist ein MTS Resource Dispenser. Der SPM erlaubt die Erzeugung von globalen Eigenschaften und deren gemeinsamen Zugriff von unterschiedlichen Komponenten desselben Paketes. Alle Komponenten des gleichen Paketes können damit auf gemeinsame Eigenschaften zugreifen, jedoch können SharedProperties nicht zwischen unterschiedlichen Paketen ausgetauscht werden.
Der SPM stellt auch eine Möglichkeit für ein Objekt dar, seinen Zustand festzuhalten, bevor es deaktiviert(zustandslos) wird. JIT-Activation hat keine Auswirkung auf den Status des SPM.
Das nachfolgende Beispiel zeigt, wie man den SPM mit Visual FoxPro Servern benutzen kann :
#DEFINE MTX_CLASS "MTxAS.AppServer.1"
#DEFINE MTX_SHAREDPROPGRPMGR "MTxSpm.SharedPropertyGroupManager.1"
PROCEDURE GetCount (tlReset)
LOCAL loCount
LOCAL loMTX,loContext
LOCAL lnIsolationMode,lnReleaseMode,llExists
loMTX = CREATEOBJECT(MTX_CLASS)
loContext = loMTX.GetObjectContext()
loSGM = loContext.CreateInstance(MTX_SHAREDPROPGRPMGR)
lnIsolationMode = 0
lnReleaseMode = 1
* Gibt eine Referenz auf die Gruppe zurück,
* in der die Eigenschaft vorhanden ist
loSG = loSGM.CreatePropertyGroup("CounterGroup", lnIsolationMode,;
lnReleaseMode, @llExists)
* Gibt eine Referenz auf die Eigenschaft zurück
loCount = loSG.CreateProperty("nCount", @llExists)
* überprüft, ob die Eigenschaft schon existiert,
* anderenfalls wird sie zurüchgesetzt
IF tlReset OR !llExists
loCount.Value = 1
ELSE
loCount.Value = loCount.Value + 1
ENDIF
RETURN loCount.Value
ENDPROC
Die folgenden Einstellungen sind für den Isolation und Release Modus möglich:
Isolation Mode | Beschreibung |
---|---|
LockSetGet 0 |
(default) Sperrt die Eigenschaft während eines Aufrufs des Wertes, um sicherzustellen, daß jedes Lesen(ACCESS) und Schreiben(ASSIGN) eindeutig ist. Dies stellt sicher, daß nicht 2 Clients gleichzeitig auf die gleiche Eigenschaft lesen oder schreiben können, aber es verhindert nicht, daß andere Clients auf andere Eigenschaften der gleichen Gruppe zugreifen können. |
LockMethod 1 |
Sperrt alle Eigenschaften in der Shared Property Group für den exklusiven Zugriff für die aufrufende Komponente, solange die aktuelle Methode der aufrufenden Komponente ausgeführt wird. Dies ist sinnvoll, wenn bestimmte Abhängigkeiten zwischen den einzelnen Eigenschaften einer Gruppe existieren, oder in Fällen, wo ein Client sofort nach dem Lesen einer Eigenschaft diese Aktualisieren muß, bevor auf diese von einem anderen Client zugegriffen werden kann. |
|
|
Release Mode: |
|
Standard 0 |
(default) Sobald alle Komponenten Ihre Referenz zu der SPG gelöst haben, wird die SPG automatisch gelöscht. |
Process 1 |
Die SPG wird erst dann zerstört, wenn der MTS-Prozess endet. Man muß die SPG-Objekte separat zerstören, indem man Sie auf NULL setzt. |
Hier wird der Zugriff auf die Datenbanken programmiert. Im Normalfall wird eine Komponente für eine Tabelle entwickelt, die standardmäßig die folgenden Eigenschaften hat :
Name | Aufgabe |
---|---|
Insert() |
Hinzufügen eines neuen Datensatzes |
Delete() |
Löschen eines Datensatzes |
Update() |
Ändern eines Datensatzes |
GetData() |
Rückgabe der Referenz eines ADO-Recordsets |
Nachfolgend Beispiele, wie eine 'Update' bzw. 'GetData'-Methode aussehen kann.
DEFINE CLASS accounts AS CUSTOM OLEPUBLIC
oMTX = NULL
oContext = NULL
PROCEDURE Accounts.GetData
LPARAMETERS tnAccountNr
LOCAL loMTX, loContext
* Parameter überprüfen
IF VARTYPE(tnAccountNr) == "N" THEN
lcSQL = "SELECT * FROM accounts WHERE account_nr = " + ;
ALLTRIM(STR(tnAccountNr))
ELSE
lcSQL = "SELECT * FROM accounts"
ENDIF
* ObjektContext zuweisen
THIS.oMTX = CREATEOBJECT(MTX_CLASS)
THIS.oContext = THIS.oMTX.GetObjectContext()
IF ISNULL(THIS.oContext) THEN
RETURN NULL
ENDIF
* Verbindung zur Datenbank herstellen
loAdoConn = CREATEOBJECT("ADODB.Connection")
loAdoConn.Open("dsWest", "sa", "")
* Recordset öffnen
loAdoRs = CREATEOBJECT("ADODB.Recordset")
loAdoRs.Open(lcSQL, loAdoConn)
IF loAdoRs.Eof And loAdoRs.Bof THEN
RETURN NULL
ELSE
RETURN loAdoRs
ENDIF
ENDPROC
PROCEDURE Accounts.Update
LPARAMETERS tnAccountNr, tyAmount
LOCAL lnHnd
LOCAL loMTX, loContext
THIS.oMTX = CREATEOBJECT(MTX_CLASS)
THIS.oContext = THIS.oMTX.GetObjectContext()
lnHnd = SQLCONNECT("dsWest", "sa", "")
IF lnHnd < 0 THEN
RETURN ERROR_NO_CONNECTION
ENDIF
lnExec = SQLEXEC(lnHnd, "EXEC sp_update(?tnAccountNr, ?tyAmount)")
IF lnExec != ERROR_SUCCESS THEN
loConText.SetAbort()
RETURN ERROR_EXECUTE
ENDIF
SQLDISCONNECT(lnHnd)
RETURN ERROR_SUCCESS
ENDPROC
PROCEDURE Error
THIS.oContext.SetAbort()
ENDPROC
ENDDEFINE