# Softwarearchitektur vs. Benutzer - Transaktionen NICHT DBMS



## apfelsine (16. Sep 2004)

Hallo,

ich schlage mich gedanklich mit einem Problem herum.
Ich denke über eine Softwarearchitektur nach, die einen 
ThinClient mit Anbindung an einen Server und einer
Datenverbindungsschicht hat.
Die Datenverbindungschicht ist universell einsetzbar und
stellt grundlegende Datenbankkommunikation zur Verfügung
(Mit Insert - Update - und was es alles so nützliches
gibt).  Ausserdem hat diese Schicht keine Kenntnis über die Datenbankstruktur
(daher universell)
Überhaupt holt diese SChicht nur auf Anweisung in eine Collection von
Feldern die Informationen, die sie aus der Datenbank erhält.
über eine Objektreferenz kann der Server dann auf diese Felder zugreifen.
Im Prinzip gibt es für jede Tabelle eine zugehörige Klasse, in der die
Informationen für Server/Client zugänglich gemacht sind.
So... soweit so gut...
Jetzt kommt ein Benutzer daher, und ich möchte irgendwie verhindern,
das der Datensatz (der aus mehreren Tabellen besteht) den der Benutzer
geladen hat, von einem anderen Benutzer geändert werden kann.
Immer unter der Annahme das es unter Umständen sehr viele Tabellen sein 
können, die hier unter die Bezeichnung "ein Datensatz" fallen.

Zu beachten ist:
- Der Benutzer kann den Datensatz laden und unter Umständen erst
Stunden später den "speichern" Knopf betätigen.
- Ein anderer Benutzer darf auch nicht einen Teil der geladenen Daten
ändern oder der Benutzer der den DS geladen hat, sollte darauf hingewiesen werden,
das seine Daten veraltet sind, wenn er nach stunden speichern will.
Das ganze läuft über RMI

Jetzt zu meiner Frage:
Wo oder in welcher Weise findet ihr es sinvoll eine Kontrolle einzubauen...
Das einzige was feststeht, ist die Grundstruktur, das es zu jeder Tabelle eine Klasse gibt
und das die Datenverbindungsschicht damit überhaupt nichts am Hut hat.
Ich hab mir schon ein "bisschen" den Kopf zerbrochen, bin aber noch 
zu keinem befriedigenden Ergebnis gekommen.
Vielleicht habt ihr Bock mit mir ein bisschen zu philosophieren...
vielleicht auch nicht. Auf jeden Fall würd ich mich freuen zu hören
was ihr so dazu meint. Das Thema ist IMHO nicht so trivial, wenn man ein 
bissl drüber nachdenkt...
vielleicht hat auch schon einer den Stein der Weisen gefunden ;-)


gruß
apfelsine


----------



## foobar (16. Sep 2004)

An so etwas arbeite ich seit den letzten 3 Monaten 
Über diese Kontrolle habe ich mir auch schon einwenig Gedanken gemacht. Ich werde das mit einem Rmi-Callback lösen.


----------



## Bleiglanz (17. Sep 2004)

du willst also, dass der Client einen Record "lädt" und dann die Connection zumachen (d.h. alle Datenbank-Mechanismen zum Locking greifen nicht mehr) und dann stunden später wieder Updaten?

so wie du es beschreibst gehts eigentlich nicht, ich mache in diesem Fall immer einen Notnagel, d.h. entweder 

eine extra Datenbank-Tabelle mit den Locks (die natürlich regelmässig gelöscht werden sollen)

oder jeder Tabelle zwei extra Spalten geben (locked_by,locked_since)

rein logisch musst du ja am Backend (Server) irgendwo mitprotokollieren, wer einen Datensatz "hält" - und das am RMI-Server per Java zu machen ist schon irre aufwändig


----------



## apfelsine (17. Sep 2004)

> dann die Connection zumachen



Wann eine Transaktion beginnt und anfängt kann ich selber bestimmen.
Jedenfalls brauche ich dafür keine Connection zu schließen... wieso auch, das 
bremst nur das System.
Und eine Datenbanktransaktion sollte auf keinen Fall über Stunden gehen
(Ich meine "Begin Transaction ..... Commit")

Durch das Verhalten des Benutzers, kann aber genauso ein inkonsistenter Zustand
entstehen, wie bei unkontrollierten DB-Transaktionen.
Für den Server/Client ist die Anzeige zustandslos, also braucht man dafür
einen Mechanismus.



> oder jeder Tabelle zwei extra Spalten geben (locked_by,locked_since)



hm...das ist auch eine Möglichkeit ....... wenn die Hierarchie sehr groß ist, könnte es das 
Laden eines Datensatzes vielleicht sehr ausbremsen....
vielleicht kann man zu einem "Datensatz"  einen eindeutigen Schlüssel generieren, der
in die Locktabelle geschrieben wird....irgendwie müsste es dann aber möglich sein
Teilschlüssel eindeutig zu identifizieren, sodass unterobjekte nicht beschrieben werden können..
 ???:L 

ich muß mal darüber nachdenken




> Rmi-Callback



 :###  ich muß erstma nachschlagen was das ist :-D


----------



## Bleiglanz (17. Sep 2004)

> Wann eine Transaktion beginnt und anfängt kann ich selber bestimmen.
> Jedenfalls brauche ich dafür keine Connection zu schließen... wieso auch, das
> bremst nur das System.


Wie verwaltest du eigentlich deine Connections?

Wenn in einem Pool am RMI-Server, dann musst du eine Verbindung trotzdem aufgeben, nachdem der RMI-Client was gemacht hat (du willst ja nicht stundenlang warten)

Es hilft ja auch nichts, so gehts eben nicht

TRANSACTION BEGINN
ein user macht SELECT
System sperrt Zeile in DB
user hat dann am Bildschirm einen 
Datensatz zur Ansicht und zum Ändern
//
// wart wart wart
//
COMMIT

also gleich committen und irgendwie anders speichern, dass "da was ist"


----------



## apfelsine (17. Sep 2004)

RMI Callback sieht auch ganz nett aus. Gibts da vielleicht einen Haken?
Traffic oder sowas?  ???:L 
Oder gibts da was zu beachten?


----------



## apfelsine (17. Sep 2004)

Es gibt eine Connection pro Client.
Die Connection selbst ist vom Client soweit entkoppelt,
das dieser nur am anfang ein Login an den Server losschickt.
Der Server reicht das ding weiter an die Datenverbindungsschicht,
die eine Connection herstellt, und so lange hält, bis der Client
sich beendet.
Der Server hält ein Objekt der Datenverbindungsschicht, über
das geladen wird oder was eben notwendig ist.
Jede Aktion des Clients ist zustandslos. Dh. der Client sagt
ich will DS x haben und bekommt x in Form eines gefüllten Objektes der Klasse x

Eine Transaktion mit
Laden
wart 
wart 
wart

...
Commit 
ist absolut inakzeptabel

Wenn schon läuft das beispielsweise so:

Begin Transaction
Select * From customer where id_no=123
Commit

wart wart wart
/* Benutzer ändert was im Client */ 
/* Benutzer drückt auf speichern */

Begin Transaction
Update customer Set Values...... where id_no=123
Commit

so, das ist die einfache Version ohne Verschachtelungen


----------



## Bleiglanz (17. Sep 2004)

das klassische "wer zuletzt speichert hat gewonnen" 

es hilft nix, du musst den tabellennamen (customer) und den primärschlüssel (123) und den benutzernamen irgendwo am Server speichern...


----------



## foobar (17. Sep 2004)

apfelsine hat gesagt.:
			
		

> RMI Callback sieht auch ganz nett aus. Gibts da vielleicht einen Haken?
> Traffic oder sowas?  ???:L
> Oder gibts da was zu beachten?


Der Nachteil dabei ist, daß wenn ein Benutzer ein Reihe von Änderungen vornimmt und dann seine Änderungen übernehmen möchte. Ein anderer Benutzer aber kurz vorher seine Änderungen übernimmt, bekommt der erste Benutzer ein Meldung, daß die Daten nicht mehr aktuell sind und muß seine Änderungen erneut vornehmen.
Ist aber immer noch besser, als wenn sich beide gegenseitig ihre Änderungen zerschießen.


----------



## apfelsine (19. Sep 2004)

> das klassische "wer zuletzt speichert hat gewonnen" icon_smile.gif



ja, DB-Transaktionen sind für zusammenhängende Prozesse gedacht.
Eine zustandslose Anzeige wo ein Benutzer irgendwann einmal
was ändern kann, dafür sind die nicht gedacht.
So sollte man da auch nicht ran gehen.
;-)

Was ist wenn beim Callback alle Benutzer gleichzeitig speichern drücken würden?
dann wäre eine Inkonsistenz vermutlich immernoch möglich oder?
es gibt bestimmt keine Warteschlange oder sowas.
Oder sagt das system bei einem Benutzer nix und
die anderen bekommen ein "Ihre Daten sind veraltet" ?


----------



## foobar (19. Sep 2004)

> Oder sagt das system bei einem Benutzer nix und
> die anderen bekommen ein "Ihre Daten sind veraltet" ?


Genau so stelle ich mir das im Moment vor. Vielleicht kombiniere ich das ganze dann noch mit der Möglichkeit einzelne Datensätze, die gerade von einem Benutzer bearbeitet werden, zu sperren. Damit nicht alle Daten neu eingelesen werden sondern nur solche, die durch einen anderen Benutzer verändert wurden.


----------



## apfelsine (20. Sep 2004)

also mit der eineindeutigen ID ist mir noch was eingefallen.
Ich denke man könnte auch eine Tabelle anlegen, in der 
jede "Tabelle" des Datenbankmodells eine eindeutige ID hat
so könnte man eine Datenbankweite eindeutige Id
generieren, sodass man zum sperren eines Datensatzes
nur in eine Lock - Tabelle schreiben muß, 
Die eineindeutige ID könnte so aussehen
TabellenId-DSId-TabellenId-DSId-......

dann Benutzer; Locktime und so weiter, sodass eine einzige abfrage reichen
würde um für einen ganzen DS festzustellen ob die Zeile gesperrt
ist.
Umgekehrt beim sperren brauche ich nur in eine Tabelle 1 mal zu schreiben.
Während ich bei lock in mehreren Tabellen x mal schreiben müsste...
Die Abfrage nach locking und die sperre selbst wird in eine Transaktion
gepackt die ausnahmsweise serializable oder read committed ist und dann kann nix schiefgehen.
oder?

@Bleiglanz: vielleicht hast du es die ganze zeit schon so gemeint.
und ich hab einfach in einer falschen Bahn gedacht


----------



## foobar (20. Sep 2004)

> lso mit der eineindeutigen ID ist mir noch was eingefallen.
> Ich denke man könnte auch eine Tabelle anlegen, in der
> jede "Tabelle" des Datenbankmodells eine eindeutige ID hat
> so könnte man eine Datenbankweite eindeutige Id
> ...


Gute Idee, man könnte das ganze dann auch noch etwas ausweiten, indem man gesperrte Datensätze für andere Benutzer nur noch zum lesen zur Verfügung stellt. Falls der Benutzer der die Sperrung erzeugt hat, dann seine Daten speichert. Könnten die anderen Benutzer darüber benachrichtigt(Rmi-Callback) werden und würden dann eine aktulle Version des vorher gesperrten Datensatzes bekommen.


----------



## Bleiglanz (21. Sep 2004)

so ähnlich dachte ich mir das, da musst du aber noch einiges investieren:

Benutzer meldet sich nicht ab - was dann (du musst am Server regelmässig alle "uralten" Locks löschen)? Dazu braucht man einen Timeout ("wie lange soll der die Sperre haben")

Also musst du bei jeder "späteren" Aktion nochmal schauen, ob der Benutzer wirklich noch den Lock besitzt und wenn nicht einen ROLLBACK machen. Wenn ein Benutzer also 5 Stunden braucht, um ein Formular mit kompliziertem Text usw. auszufüllen dann ist das auch nicht nett, wenn er erfährt "Leider wurde Ihre Sperre gelöscht"


Du musst bei alle User-Requests die keinen Lock erzeugen sollen immer alle(!) Locks des Users löschen (die sollen so früh wie möglich freigegeben werden)

Das ganze geht nur programmatisch, wenn aber noch andere Fremd-Anwendungen auf die DB zugreifen kanns trotzdem Probleme geben

Dazu gibts auch ein EAI Pattern "offline concurrency", schmeiss mal den google an


----------



## apfelsine (22. Okt 2004)

Nachdem meine Pläne fast schon so weit waren,
habe ich jetzt erfahren, das es für dieses Problem
angeblich von den gängigen Datenbansystemen
bereits Lösungen für dieses Problem gibt.

Der einzige Hinweis den ich hatte, war daß das  bei
MySQL "Lock" heißt.
Ich habe ein bisschen bei Oracle und MS gestöbert.
Bis jetzt mit dem Ergebnis, das wohl beide Firmen eine
Lösung haben.
Bei MS gibt es ein Property, das sich "Lock Mode" nennt
DBPROPVAL_LM_SINGLEROW ist das Strichwort.
Der Haken ist, ich weiß noch nicht ob das in Java auch
irgendwie gesetzt werden kann, bzw.
ob es über JDBC irgendeine Unterstützung gibt.

In Oracle habe ich zu dem Thema ein Stichwort namens
DatabaseSession gefunden.
Natürlich ist das eine proprietäre Lösung und so weit
ich sehe erst ab Oracle 10g implementiert.
Meine Informationsquelle hat aber behauptet, es gäbe
schon seit Oracle 7 dafür eine Lösung.
Ich werde weitersuchen. Wie dem auch sei,
hat einer von euch davon schonmal was gehört?

[EDIT]"Optimistic Locking" im Zusammenhang mit TopLink
ist scheinbar das relevante Stichwort für Oracle [/EDIT]

gruß
apfelsine


----------



## apfelsine (26. Okt 2004)

hat noch keiner davon gehört ? Keiner hats verwendet oder zumindest ausprobiert?
???:L


----------



## gest01 (31. Okt 2004)

Wie wärs mit einem Trigger auf den entsprechenden SQL-Statements.
Nach einem Select soll der Trigger einen Zähler um 1 erhöhen.
Beim Update MUSS dann der Zähler um 1 höher sein als beim Select, falls nicht wurde der Record während dieser Zeit verändert.


cu


----------



## apfelsine (1. Nov 2004)

lohokla hat gesagt.:
			
		

> Wie wärs mit einem Trigger auf den entsprechenden SQL-Statements.
> Nach einem Select soll der Trigger einen Zähler um 1 erhöhen.
> Beim Update MUSS dann der Zähler um 1 höher sein als beim Select, falls nicht wurde der Record während dieser Zeit verändert.
> 
> ...


Das bedeutet wieder, das du erst eine Abfrage machen mußt und vergleichen mußt.
Wenn der Datensatz entsprechend verschachtelt ist, sodaß viele Records abgeglichen werden müssen,
dann bedeutet das wieder viele Abfragen vor dem eigentlichen ausführen,
die den ganzen Vorgang verlangsamen.


----------

