# Verständnisproblem mit Client/Server-Anwendung



## Alex_winf01 (14. Dez 2007)

Ich habe da ein kleines Verständnisproblem mit einer Client/Server-Anwendung:

Ich habe da eine kleine Applikation geschrieben, die eine Verbindung zur Datenbank H2 aufbaut. Derzeit liegt die Datenbank auf meinem localhost. Die auf einen Server zu installieren und dann der Anwendung zu sagen, wo die Datenbank liegt, ist kein Problem. Mein Problem ist nur folgendes:

Ich habe ja zwei Varianten, die Applikation zu installieren.

1. Ich installiere das Programm auf dem Client und über die Anwendung wird eine Verbindung zur Datenbank aufgebaut. Hier ist aber das Problem, dass ich nur eine Connection zur H2-Datenbank haben darf, ich aber mehrere Anwender gleichzeitig mit dem Programm auf ihrer lokalen Maschine auf die DB zugreifen.
2. Ich installiere auch das Programm auf dem Server und stelle es den Anwendern zur Verfügung.

Wie kann ich bei Punkt 1 sicherstellen, dass nur eine Connection zur Datenbank aufgebaut wird? Ich habe bei der H2-Datenbank die Möglichkeit, einen Server zu starten (siehe anderer Thread).

Wie kann ich Punkt 2 umsetzen? Ich weiss nicht, wie das dann mit RMI aussehen muss. Zur Info: Die Standardbeispiele mit einem Client und einem Server, die Strings austauschen oder die Chats habe ich schon gesehen. Nur wie muss das mit einer Datenbankanwendung aussehen? Ich habe nämlich mal gelesen, dass JDBC ein Problem mit RMI hat.

Noch mal zur Verdeutlichung meines Problems: Ich habe mehrere Anwender, die mit einer Software (die entweder lokal oder auf dem Server installiert ist???) sich mit einer Datenbank in Verbindung setzen.


----------



## HoaX (14. Dez 2007)

Alex_winf01 hat gesagt.:
			
		

> 1. Ich installiere das Programm auf dem Client und über die Anwendung wird eine Verbindung zur Datenbank aufgebaut. Hier ist aber das Problem, dass ich nur eine Connection zur H2-Datenbank haben darf, ich aber mehrere Anwender gleichzeitig mit dem Programm auf ihrer lokalen Maschine auf die DB zugreifen.


wieso willst du nur eine verbindung zulassen? wenn du einen dbserver startest können damit gleichzeitig soviele kommunizieren wie wollen


----------



## Alex_winf01 (14. Dez 2007)

Also, mein Verständnisproblem:

Ich habe eine Klasse Login, in der ich die Connection regel.


```
/**********************************************************************

**********************************************************************/

//-------------------------------------------------------------------------------------

/** Die Login-Klasse ist dafür zuständig, den Anwender an der Datenbank anzumelden*/

//-------------------------------------------------------------------------------------


import java.awt.*;
import javax.swing.*;
import java.sql.*;
 

/** Die Klasse Login stellt die Verbindung zur Datenbank her.*/
public class Login
{
	// Variablendeklaration für die gesamte Klasse
	public static Connection connection;
  	public static String lese, lese_dokumentation, lese_icd1, lese_icd2, lese_icd3;
  	public static Statement stmt, stmt1, stmt2, stmt3, stmt4, stmt5;
  	public static ResultSet result, result_dokumentation, result_icd1, result_icd2, result_icd3, result_kh;
        	
              
   	/******************************************************************

	Anmeldedialog mit Benutzernamen, Passort und DB-Namen

	******************************************************************/
	
	private static void login()
	{
		String[] s_options = {"Anmelden", "Abbrechen"};
		
		JPanel pnl_main = new JPanel();
		pnl_main.setLayout(new GridLayout(3,3,10,10));

                JLabel lbl_username = new JLabel("Benutzername: ", JLabel.RIGHT);
		JTextField txt_username = new JTextField();

		JLabel lbl_password = new JLabel("Passwort: ", JLabel.RIGHT);
		JPasswordField txt_password = new JPasswordField();


		pnl_main.add(lbl_username,0);
		pnl_main.add(txt_username,1);

		pnl_main.add(lbl_password,2);
		pnl_main.add(txt_password,3);

                
		if(JOptionPane.showOptionDialog(null, pnl_main,
				"DB-Anmeldung",
				JOptionPane.OK_CANCEL_OPTION,
				JOptionPane.INFORMATION_MESSAGE,
				null, s_options,
				s_options[0]) != 0)
				{
    				System.exit(0);
				}
	}


	/******************************************************************
	main-Methode

	******************************************************************/
	public static void main(String[] args)
	{
		

        // Aufruf des Dialogs zur Identifikation
            
        login();
        try
        {
        	Class.forName("org.h2.Driver");
    		connection = DriverManager.getConnection("jdbc:h2:tcp:localhost/test", "sa", "");
    		
			
		}
	    catch(Exception e)
	    {
	    	JOptionPane.showMessageDialog(null, "Es ist folgender Fehler aufgetreten: " + e,
				"DB-Meldung", JOptionPane.ERROR_MESSAGE);
	    }
        
        StartFrame frm = new StartFrame("Mein Programm");
		frm.setVisible(true);
		frm.setSize(400,400);
		frm.pack();
	}
}
```

Da alle Clients diese Login haben und sich darüber anmelden und dann auf DIESELBE Datenbank und DIESELBEN Tabellen zugreifen, dachte ich, ich darf nur eine Connections haben. Was ich noch hab, ist eine .bat, in der ich den Server von H2 starte.

@ HoaX: Wenn ich Dich richtig verstehe, muss ich diese .bat einmal ausführen und dem Server ist es dann wurscht, ob da 2 Connections sind oder 5 oder 15?


----------



## tuxedo (14. Dez 2007)

Mehrere, kongruente Zugriffe auf ein und denselben Datensatz können zu einem inkonsitenten System führen. Ich wäre da vorsichtig. Nicht dass sich die Anwender gegenseitig ihre Änderungen überschreiben und am Ende gar nix mehr stimmt.


Mit RMI wird die Sache "besser" (aber nicht einfacher ;-) ):

Du bastelst dir einen Server. Dieser Server hat als einzigster Zugriff auf die DB. Clients verbinden sich mit dem Server. Will ein CLient einen Datensatz in der DB ändern, so muss er dafür eine Methode im Server aufrufen. Der Server merkt sich diesen Editiervorgang und blockt gleichzeitige Editieranfragen von anderen Nutzern. 

Sind Server und Client bei dir dann im gleichen Netzwerk? Oder sind da Router/Firewalls dazwischen?

- Alex


----------



## Alex_winf01 (14. Dez 2007)

Ich programmiere das für verschiedene Kunden. Alle haben zumindestens eine Firewall. Einige setzen auch eine sog. Terminalserver ein.

Aber im Grundprinzip sollen Client und Server im gleichen Netzwerk liegen - bis auf die, die einen Terminalserver einsetzen.

Was mein Verständnisproblem ist: H2 kann ich bereits im Server-Modus laufen lassen. Wie muss ich diesen Server ansprechen?

Dass ich DB-mässig mich natürlich drum kümmern muss, dass weiss ich.

Aber ich glaub ich weiss, wass du meinst. Ich habe mal den Chat aus den FAQ genommen:

Der Server:


```
import java.net.*;
import java.io.*;
import java.util.*;

public class Server {

   private Hashtable clients;
   private int port;   
   
   public Server(int port) {
      this.port=port;
      clients = new Hashtable();
   }
   
   private void startServerListener() {
      ServerSocket ss ;
      try {
         ss = new ServerSocket(port);
         System.out.println("Server gestartet...");
         while (true)         
            new ServerBody(ss.accept(), this).start();   
      } catch (Exception e) {
         e.printStackTrace();
      }
   
   }   
   
   public void addClient(String name, ServerBody body) {
      clients.put(name, body);
   }
   
   public void removeClient(String name) {
      clients.remove(name);
   }
   
   public String getUsers() {
      String users;
      users="users|";
      for (Enumeration e = clients.keys();e.hasMoreElements();)
         users+=(String) e.nextElement() + "|";
      if (! users.equals("users|"))
         users = users.substring(0, users.length() - 1);
      return users;     
   }
   
   public void broadcast(String name, String msg) throws Exception {
      for (Enumeration e = clients.keys();e.hasMoreElements();)         
         ((ServerBody) clients.get((String) e.nextElement())).send(name + ": " + msg);     
   }
   
   public void send(String name, String targetname, String msg) throws Exception {
      ((ServerBody) clients.get(targetname)).send(name + ": " + msg);     
   }
   
   public boolean isClient(String name) {
      return clients.containsKey(name);
   }
   
   public static void main(String[] x) {   
      if (x.length != 1) {
         System.out.println("#java Server <port>");
         System.exit(0);
      }
      new Server(Integer.parseInt(x[0])).startServerListener();
   }
}


class ServerBody extends Thread {

   private Socket cs;
   private Server server;
   private PrintWriter out;

   public ServerBody(Socket cs, Server server) {
      this.cs=cs;
      this.server=server;
   }

   public void run() {
      BufferedReader in;
      StringTokenizer str;
      String name, msg, targetname;
      int n;
      try {
               
         in = new BufferedReader (new InputStreamReader(cs.getInputStream()));
         out = new PrintWriter(new DataOutputStream(cs.getOutputStream()));         
         name = in.readLine();         
         server.addClient(name, this);         
         server.broadcast(name, server.getUsers());
         System.out.println("+ Client " + name + " hat sich angemeldet!");         
         for (String buffer;(buffer = in.readLine()) != null;) {
            n = buffer.indexOf("|", 0);
            targetname = buffer.substring(0, n);
            msg = buffer.substring(n + 1, buffer.length());
            if (targetname.equals("all")) {
               server.broadcast(name, msg);
               System.out.println(">Client " + name + " schreibt an alle: " + msg);
            } else if (server.isClient(targetname)) {
               server.send(name, targetname, msg);
               System.out.println(">Client " + name + " schreibt an " + targetname + ": " + msg);
            } else
               this.send("Server: Client " + targetname + " existiert nicht!");
         }
         server.removeClient(name);
         System.out.println("- Client " + name + " hat sich abgemeldet!");
         in.close();
         out.close();               
      } catch (Exception e) {
         e.printStackTrace();
      }
   
   }

   public void send(String msg) throws Exception {
      out.println(msg);
      out.flush();
   }
}
```

Der Client:


```
import java.net.*;
import java.io.*;
import java.util.*;

public class Client {

   private String ip;
   private int port;

   public Client(String ip, int port) {
      this.ip=ip;
      this.port=port;
   }

   private void startClient() throws Exception {
      Socket s;
      String buffer, name, targetname, msg;
      PrintWriter out;
      BufferedReader in;
      int n;
      s = new Socket(ip, port);
      buffer = null;
      out = new PrintWriter(new DataOutputStream(s.getOutputStream()));     
      in = new BufferedReader(new InputStreamReader(System.in));           
      System.out.println("\n\nClient gestartet...\n\n");     
      name = null;     
      while (name == null || name.equals("")) {
         System.out.print("Client-Name eingeben: ");
         name = in.readLine();
         name = name.trim();
      }
      out.println(name);
      out.flush();
      new ClientBody(s.getInputStream()).start();           
      System.out.print("\nText eingeben -> <zielrechner> <message># ");     
      while (! (buffer = in.readLine().trim()).equals("stop")) {         
         if ((n = buffer.indexOf(" ", 0)) < 0) {
            System.out.print("\nUngueltige Eingabe! Text eingeben -> <zielrechner> <message># ");
            continue;
         }
         System.out.print("\nText eingeben -> <zielrechner> <message># ");         
         targetname = buffer.substring(0, n);
         msg = buffer.substring(n + 1, buffer.length());   
         out.println(targetname + "|" + msg);
         out.flush();   
      }
      System.out.println("\n\nClient gestoppt...\n\n");
      in.close();
      out.close();
   }

   public static void main(String[] x) throws Exception {
      if (x.length != 2) {
         System.out.println("#java Client <server-ip> <port>");
         System.exit(0);
      }     
      new Client(x[0], Integer.parseInt(x[1])).startClient();   
   }
}


class ClientBody extends Thread {
   private InputStream i;
   public ClientBody(InputStream i) {
      this.i=i;
   }

   public void run() {
      String buffer;
      BufferedReader in;
      int n;
      try {
         in = new BufferedReader(new InputStreamReader(i));
         while ((buffer = in.readLine()) != null) {           
            if ((n = buffer.indexOf("users|", 0)) > -1) {
               buffer = buffer.substring(n + "users|".length(), buffer.length());
               buffer = buffer.replace('|', ',');
               System.out.println("\n\n==>Angemeldete User: " + buffer);           
            } else
               System.out.println("\n\n==>Eingang von " + buffer);
            System.out.print("\nText eingeben -> <zielrechner> <message># ");     
         }
         
      } catch (Exception e) {}   
   }
}
```

Müsste hier dann auch die Connection über den Server laufen? Also folgender Part


```
try
        {
           Class.forName("org.h2.Driver");
          connection = DriverManager.getConnection("jdbc:h2:tcp:localhost/test", "sa", "");
          
         
      }
```

Dann der ganze Login und dann die verschiedenen Klassen für Einfügen.java, Loeschen.java und Update.java müssten dann über den Client laufen?

Also in der Klasse 
	
	
	
	





```
public class Client
```
 muss ich also z. B. die einzelnen Methoden zum Einfügen in der Klasse Einfügen.java aufrufen?


----------



## robertpic71 (15. Dez 2007)

Alex_winf01 hat gesagt.:
			
		

> Alle haben zumindestens eine Firewall.


Man müsste die Frage genauer Stellen: Hast du eine Firewall zwischen Client und Server?



			
				Alex_winf01 hat gesagt.:
			
		

> Aber im Grundprinzip sollen Client und Server im gleichen Netzwerk liegen - bis auf die, die einen Terminalserver einsetzen.


Wo die Clients bei einem Terminalserver (RDP, Citrix, X11) sind (innerhalb oder außerhalb der Firewall, VPN..) ist für dein Programm egal. Die Programme laufen direkt am Server, der Client erhält nur GUI Daten und liefert Maus- und Tastatureingaben zurück.

Um ein, am Terminalserver lauffähige Programme zu schreiben, muss man ein paar Sachen berücksichtigen. Die Programme laufen ja am gleichen System - nur an unterschiedlichen Desktop's. Da sind Resourcenkollisionen vorprogrammiert.



			
				Alex_winf01 hat gesagt.:
			
		

> Was mein Verständnisproblem ist: H2 kann ich bereits im Server-Modus laufen lassen. Wie muss ich diesen Server ansprechen?


Wie gehabt: 


```
getConnection("jdbc:h2:tcp:localhost/test", "sa", "");
```

Wobei localhost durch die IP-Adresse des Servers zu ersetzen ist.

Zuerst solltest du dir einmal klar werden, ob du RMI-Funktionen oder einer Serverdatenbank arbeitest. Wenn du mit RMI arbeitest braucht die Datenbank nicht im Serverbetrieb zu arbeiten. Zu den konkurrierenden Zugriffen: Das ist tägliches Brot für die Datenbank und keine Grund nicht mit Datenbanken zu arbeiten. Auch beim Terminalserver hat man keine Vorteile RMI statt Datenbank: in beiden Fällen darf es nur einen Serverjob am System geben.

Im Falle einer Serveranwendung ist eine eigene Instanz (JVM) für die Datenbank zu empfehlen. Für Standaloneanwendungen würde ich mit "jdbc:h2:~/test" arbeiten. Wobei das "~" für das Arbeitsverzeichnis des Users steht, also bei Windows "Dokumente und Einstellungen\user\". Damit könnten auch auf einem Terminalserver mehrere Standalone-DB's laufen (1x je Benutzer).

/Robert


----------



## Alex_winf01 (15. Dez 2007)

@ robertpic71

vielen Dank schon mal für Deine Antwort. Ich wollte gerne die Datenbank im Server-Modus von H2 laufen lassen. Und da ist mir einfach noch nicht klar, wenn ich jetzt verschiedene Anwender habe und jeder  über die Login.java eine Connections zur Datenbank aufbaut, ob das ein Problem darstellt und wie ich dann den Server von H2 ansteuern muss?

Wie sieht das denn aus, mit den Terminalservern? Wie muss ich das Problem lösen? Was ist da zu beachten?

Ach ja noch was: es ist natürlich nicht gewünscht, dass jeder Anwender eine eigene lokale DB hat. Es soll EINE DB sein mit mehreren Clients.


----------



## Alex_winf01 (17. Dez 2007)

Kann mir keiner helfen?


----------



## tuxedo (17. Dez 2007)

Nicht böse sein, aber mir scheint du hast im Moment keine richtigen Schimmer von der Sache?

Aaaalso. Setze den H2 DB-Server auf wie jeden anderen DB-Server auch. Sprich: Er muss für Clients im Netzwerk erreichbar sein. Dann kannst du mit deinen Clients im Netzwerk, welche jeweils JDBC benutzen, auf den DB-Server zugreifen. Dafür brauchst du die IP-Adresse/Hostnamem des Servers. Statt "127.0.0.1" wie bei einer lokalen Verbindung, benutzt du einfach die IP des Servers. Das war's schon.

So, und jetzt zu der Frage:

>> Und da ist mir einfach noch nicht klar, wenn ich jetzt verschiedene Anwender habe und jeder über die Login.java eine Connections zur Datenbank aufbaut, ob das ein Problem darstellt und wie ich dann den Server von H2 ansteuern muss? 

Das WIE hab ich ja gerade erklärt. 

Probleme gibts dann, wenn zwei oder mehr Clients auf ein und denselben Datensatz zugreifen. Nehmen wir mal an du hast eine Kundendatenbank mit dem Datensatz "Max Mustermann", und du hast 2 Clients.

So, Client 1 öffnet jetzt den Max Mustermann eintrag und ändert die Hausnummer von Musterstraße 1 auf Musterstraße 2. Er klickt aber noch nicht speichern, weil er sich schnell nen Kaffee holen geht oder Papier im Drucker nachfüllen muss um die Max Mustermann Kundenkarte auszudrucken.

So, jetzt kommt Client 2. Der sitzt am anderen Ende des Flur's in seinem Büro und hat keinen blassen schimmer was Client 1 gerade treibt. Er hat jedoch eine neue Emailadresse von Max Mustermann die er im System eintragen will. Also öffnet er den Eintrag von Max Mustermann, ändert die Emailadresse und klickt speicher.

Jetzt kommt Client 1 wieder zurück und will seine Änderung auch speichern. Was passiert jetzt? Genau: Client 1 überschreibt mit seinem Speicher-Vorgang die Emailadressenänderung die Client 2 gemacht hat...

DAS ist das Problem das ich weiter oben schon angesprochen hab.
Du solltest also, je nachdem was dein Programm im Detail macht, darauf achten, dass nicht 2 oder mehr Clients ein und denselben Datensatz gleichzeitig "zum schreiben" öffnen. Du solltest sicherstellen, dass wenn ein Client den Datendatz geöffnet hat, andere Clients den Datensatz nur "schreibgeschützt" öffnen können. 

Das erreichst du, indem du vor die DB eine Serveranwendung platzierst und die Clients nur durch die Serveranwendung mit der DB kommunizieren (dann sollten die CLients mit dem Server z.B. über RMI sprechen und gar keine JDBC Verbindung zur DB selbst aufbauen). Die Serveranwendung ist dann diejenige die eine Verbindung zur DB unterhält.

ODER du du erweiterst deine Tabellen so, dass es überall eine Spalte "writeprotect" gibt welche auf "true"/"false" gesetzt werden kann. Öffnet ein Client nun einen Datensatz, muss er diese neue Spalte auf "true" setzen. Andere Cliients müssen dann vor dem öffnen darauf achten, wie der Wert dieser neuen Spalte ist, und ggf. den Eintrag nur "read-only" öffnen, d.h. keine speicher/update Funktion benutzen.

Sind jetzt alle klarheiten beseitigt ;-) ???

- Alex


----------



## Alex_winf01 (17. Dez 2007)

@ alex0801

Warum sollte ich Böse sein? Ich hab gelernt: Man kann noch so unwissend sein, man muss sich nur zu helfen wissen und sich das fehlende Wissen aneignen können. Und wenn es über fragen geht.

Danke schon mal auf jeden Fall für Deine Ausführung. Das hat mir auf jeden Fall geholfen.

Ich habe dann nur noch eine Frage: Was muss ich denn beachten, wenn das Programm auf einem Terminalserver läuft?


----------



## tuxedo (17. Dez 2007)

Was willst du denn mit dem Terminalserver bezwecken?

Sofern da immer noch mehrere Personen den Client gleichzeitig benutzen können, hast du nichts in Sachen Datenkonsistenz gewonnen. 

Ob der Client auf einem Terminalserver läuft, oder ob er lokal installiert ist spielt dabei eigentlich keine Rolle, solange die Clients nicht alle versuchen ein und dieselbe lokale Ressource zu verwenden (bspw. öffnen eines lokalen Serverports für irgendein Callback oder so...).

Würde die Terminalserver-Sache weglassen. 

a) bringt es dich nicht weiter
b) wird es dadurch nicht einfacher
c) hast du damit nicht wirklich weniger Probleme bei der Entwicklung

Ich würde mich an deiner Stelle ein wenig mit RMI befassen und den einzelnen CLients einen Server vorschalten, der als einziger eine direkte Verbindung zur DB hat. Damit hast du dann keine Probleme mit gleichzeitigen Zugriffen auf die DB (das kann ja der Server managen).

- Alex


----------



## ms (17. Dez 2007)

alex0801 hat gesagt.:
			
		

> Nicht böse sein, aber mir scheint du hast im Moment keine richtigen Schimmer von der Sache?
> ...
> 
> Das erreichst du, indem du vor die DB eine Serveranwendung platzierst und die Clients nur durch die Serveranwendung mit der DB kommunizieren (dann sollten die CLients mit dem Server z.B. über RMI sprechen und gar keine JDBC Verbindung zur DB selbst aufbauen). Die Serveranwendung ist dann diejenige die eine Verbindung zur DB unterhält.
> ...


Es gibt sowas, dass nennt sich Transaktion.
Wurde das schon erwähnt?

ms


----------



## tuxedo (17. Dez 2007)

Ne, wurde noch nicht erwähnt. Hab ich auch noch nicht mit gearbeitet. Kannst du kurz 2..3 Sätze dazu schreiben (is vielleicht besser als wikipedia ...)? Würd mich auch interessieren.


----------



## Alex_winf01 (17. Dez 2007)

@ alex0801

Jetzt sei Du bitte nicht böse. Viele unserer Kunden arbeiten mit Terminalservern. Daher muss ich Rücksicht nehmen (wohl oder übel).

Zum Thema Transaktionen: Bei einer Transaktion werden mehrere Befehle so gebündelt, dass diese eine logische Einheit ergeben und zusammen ausgeführt wird. Entweder wird die ganze Transaktion erfolgreich durchgeführt oder es wird wieder der Ursprungszustand hergestellt.

Hier ist der Link zu Wikipedia:

de.wikipedia.org/wiki/Transaktion_%28Informatik%29

Könntest Du mir mit den Terminalservern helfen? Worauf muss ich achten?


----------



## tuxedo (17. Dez 2007)

Naja, hättest ja gleich sagen können dass es mit Terminalservern laufen MUSS, da Kundenvorgabe ... ;-)

Das hab ich zu Transaktionen auch gelesen. Ganz doof bin ich nicht ;-) Aber ich seh da noch keinen Zusammenhang zum DB Zugriff, vor allem nicht wenn mehrere Clients gleichzeitig dran arbeiten und die gefahr besteht dass der eine die Änderung des anderen überschreibt. . Vielleicht kann ms ja noch ein paar Worte dazu sagen.

Wie bereits schon 3 mal hier erwähnt wurde:

Ob das Client-Programm jetzt auf dem eigenen Rechner läuft, oder auf einem entfernten, der via Remote-Session ferngesteuert wird, ist prinzipiell wurscht. Du darfst nur nicht so Sachen machen wie "x mal ein und denselben lokale Serverport öffnen" oder "x mal parallel in ein und dieselbe Datei schreiben".

Also, erst lesen, dann denken, dann fragen ;-)


----------



## ms (18. Dez 2007)

Der Vollständigkeit halber:
http://de.wikipedia.org/wiki/Transaktion_(Informatik)
http://www.h2database.com/h2.pdf

Ich sehe eigentlich auch kein spezielles Problem bei Terminalserver.
Aus Datenbanksicht ist es doch das selbe wie mehrere Rich Clients, oder?

ms


----------



## tuxedo (18. Dez 2007)

Entweder steh ich auf dem Schlauch oder ich seh das "Problem" aus einer ganz anderen Sicht:

Wenn ich eine Zeile aus einer Tabelle hole und diese "updaten" möchte, dann hole ich mir in der Regel erst die Zeile (SELECT xyz FROM abc WHERE def) um die Daten in der GUI anzuzeigen, dann ändert der User die Daten entsprechend und drückt auf speichern (UPDATE blablabla).

Wie soll ich jetzt mit Transaktionen verhindern, dass 2 Clients den gleichen Datensatz lesen und der eine speichern drückt, wobei der andere seine änderungen noch nicht eingecheckt hat? Kann mir das jemand erklären?

Table Locking ist mir bekannt. Aber ich will ja vielleicht nicht gleich die ganze Tabelle sperren ?!

- Alex


----------



## ms (2. Jan 2008)

alex0801 hat gesagt.:
			
		

> Wie soll ich jetzt mit Transaktionen verhindern, dass 2 Clients den gleichen Datensatz lesen und der eine speichern drückt, wobei der andere seine änderungen noch nicht eingecheckt hat?


Verhindern kannst du das nicht, aber zumindest sicherstellen, dass die Datenbank konsistent bleibt.
Das was du beschreibst fällt unter "pesimistic locking" - den Datensatz solange sperren, bis er geschrieben wurde. Das kann bei vielen gleichzeitigen Benutzern zu Problemen und einer schlechten Performance führen.
Optimistic Locking hingegen wäre, wenn du den Datensatz ohne Transaktion anzeigst und erst wenn der User auf Speichern klickt eine Transaktion gestartet wird. In dieser Transaktion wird das Update-Statement mit einer Where-Bedingung auf die alten Daten ausgeführt. Es wird also sichergestellt, dass wirklich nur der Datensatz mit den zuvor angezeigten Daten aktualisiert wird. Wenn diese aber in der Zwischenzeit von jemand anderem überschrieben wurden, dann liefert das Statement 0 zurück => es wurde kein Datensatz aktualisiert.
Dass Programm entscheidet dann was weiter passiert. Zb dem User anzeigen, dass der Datensatz mittlerweile von einem anderen User geändert wurde.

In Hibernate zB. gibt es dafür eine eigene Versions-Spalte, die man jeder Tabelle hinzufügen soll die bei jedem Update hochgezählt wird.
Tritt oben geschildeter Fall auf wird eine StaleObjectException geworfen.

ms


----------



## tuxedo (2. Jan 2008)

Ah, danke. Wieder was dazu gelernt. Ist ja recht easy zu bewerkstelligen. Muss man ja einfach im worst-case alle Spalte als Where-Bedingung stellen. Ist einfach und DB-unabhängig. 

Der Transaktion (Informatik) Artikel bei Wikipedia beschreibt irgendwie was anderes. Passender fand ich Isolation (Datenbank) (Das hier ist auch gut)

Aber auch hier wird das einfache Verfahren mit der Where-Bedingung nicht aufgeführt. Okay, ist vielleicht auch nur für einfache Strukturen möglich, z.B. wenn ein Datensatz nur aus einer Zeile einer Tabelle besteht. 

Einen einzelnen Datzsatz von der Serversoftware als "locked" zu markieren find ich jedoch noch performanter als gleich die ganze Tabelle zu "lock"en.

Gibts vielleicht irgendwas DMBS-seitiges (jetzt mal egal für welches DBMS, eben mal allgemein gefragt) für die Isolation bzw. das  "lock"en eines einzelnen Datensatzes (nicht die ganze Tabelle)?

- Alex


----------



## ms (2. Jan 2008)

http://www.geocities.com/mailsoftware42/db/ liefert dazu einiges nur leider nicht über H2.
Wie man hier sieht, können die meisten Datenbanken Row-level-locking.
Oracle, Db2, MS SQL sollten das auch beherrschen. Hab jetzt keinen Link dazu.

ms


----------



## Thomas Müller (11. Jan 2008)

Leider unterstützt H2 bisher kein Row Level Locking. Jedoch MVCC (Multi-Version Concurrency Control, allerdings im Moment Beta).

Für den angegebenen Fall würde ich allerdings auch kein Row Level Locking verwenden. Besser ist es wahrscheinlich, bei einer Spalte einen 'Update Counter' zu verwenden (z.B. einen Zähler, oder eine Sequenz). Ein Update könnte dann so aussehen:

update ... set ... where ... and modcount=40

Wenn jemand anderes den Datensatz verändert hat, ist der modcount geändert worden, und die Applikation merkts (weil updateCount 0 ist). Bin jetzt nicht sicher ob man das 'Optimistic Locking' nennt...


----------

