# Struts2 in webbasierter Anwendung



## 7bkahnt (30. Apr 2010)

Hallo Leute,

ich habe folgendes Problem:

Ich habe eine Testanwendung geschrieben, in der dem Nutzer eine Tabelle angezeigt wird und er diese Datensätze ändern,löschen und neue hinzufügen kann.
Soweit so gut. Nun liegt mein Problem in der parallelen Abarbeitung, wenn beispielsweise 2 Nutzer gleichzeitig 2 unterschiedl. Datensätze ändern wollen.
Der Nutzer kann ein Text und eine Zahl eines Datensatzes ändern.

Das Problem ist , dass die Variable text und zahl bei mir global und statisch sein müssen. Global, weil sie ja in den setter und getter Methoden verwendet werden und static weil meine Funktionen public static sein müssen um sie in der jsp verwenden zu können.
Wenn Nutzer A jetzt also auf ändern klickt werden setText und setZahl aufgerufen und die Variablen entsprechend gesetzt.
Wenn aber Nutzer A und B gleichzeit auf ändern klicken, will er ja jeweils 2 unterschiedliche Werte auf eine statische Variable speichern, was natürlich nicht funktioniert und nur der letzte Wert genommen wird.

Hier mal die java und die jsp-Datei für das Ändern:

change.jsp:

```
<body> 
   <s:form action="Eingeben">
   <s:textfield label="TEXT255" name="text" size="60"/><br/>
   <s:textfield label="ZAHL6" name="zahl" size="60"/><br/>

   <s:textfield id="webserverid" name="webserverid" size="50" label="Session-ID"/>
  		 
    <s:hidden id="auswahlid" name="auswahlid"/>        
	<s:submit action="changeEingeben"value="Ändern"/>
	</s:form>
```



EingebenAction.java

```
package Nutzer;

import java.sql.Connection;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import sun.jdbc.odbc.ee.DataSource;

import com.opensymphony.xwork2.ActionSupport;
import db.HibernateSessionFactory;
import db.Vor_anwender_t;
import db.Vor_tbspiel_t;


public  class EingebenAction extends ActionSupport {
	public static String userName,query, text,datum_a, datum_c, neutext,webserverid,sessionid;
	public static Integer id,zahl,f_id_a_sb,f_id_c_sb,neuzahl,auswahlid,UserID;
	public static Connection myConnection;
//	public static ResultSet rs;
	public static List<tbspiel_t> tabelle = new ArrayList<tbspiel_t>();
	public static Map<String,User> user = new HashMap<String,User>(); 
	public static Integer i=0,j=0,k=0,l=0,u=0;
	static Vector aenderungen=new Vector();
	
	
	

	public String DBVerbindung() throws SQLException{
		System.out.println("\nDBVerbindung()");
		Session session = null;
		Transaction tr = null;

		try {
			session = HibernateSessionFactory.getSession();
			
			//Userabfrage aus anwender_t
			String statement = "select user.id from Vor_anwender_t user where user.username=?";
		    List list = session.createQuery(statement).setString(0, userName).list();
			UserID=(Integer) list.get(0);

			user.put(webserverid, new User(UserID,userName)); 
			System.out.println("User verglichen");
			System.out.println("userName: "+userName);
			System.out.println("UserID :"+UserID);
			System.out.println("webserverid :"+webserverid);
			
		} catch (Exception ex) {
			ex.printStackTrace();
			if(tr!=null)tr.rollback();
				else{System.out.println("transaction=null");}
			throw new SQLException("Fehler in DBVerbindung()", ex);
		} finally {
			//Session freigeben
			if (session != null) {
				HibernateSessionFactory.closeSession();
			}
		}
		
		//setwebserverid und setuserName zurücksetzen und DBVerbindung() fertig
		i--;j--;
		
		if(userName.equals("Ben")){
			System.out.println("Sachbearbeiter: "+userName);
			return "Sachbearbeiter";
		}
		else{
		return "anlegen";
		}
	}
	

	
	
	
	
	public static void auslesen() throws SQLException, InterruptedException {
		System.out.println("\nauslesen()");
	
		Session session = null;
		Transaction tr = null;

		try {
			// Tabelle auslesen und anzeigen
			tabelle.clear();
			session = HibernateSessionFactory.getSession();
			session.beginTransaction();
			
			 String statement = "FROM Vor_tbspiel_t test order by test.id";
			    List list = session.createQuery(statement).list();
			    
			    for (Iterator iterator = list.iterator(); iterator.hasNext();){
			    	Vor_tbspiel_t tbspiel = (Vor_tbspiel_t) iterator.next();
					
			    	id=tbspiel.getId();
			    	text=tbspiel.getText255();
			    	zahl=tbspiel.getZahl6();
			    	f_id_a_sb=tbspiel.getF_id_a_sb();
			    	datum_a=tbspiel.getDatum_a();
			    	f_id_c_sb=tbspiel.getF_id_c_sb();
			    	datum_c=tbspiel.getDatum_c();
			    	sessionid=tbspiel.getSessionid();
			    	if (zahl == null)      zahl = 0;
					if (f_id_a_sb == null)    f_id_a_sb = 0;
					if (datum_a == null)   datum_a = "-";
					if (f_id_c_sb == null)    f_id_c_sb = 0;
					if (datum_c == null)   datum_c = "-";
					if (sessionid == null) sessionid = "-";
			    	tabelle.add(new tbspiel_t(id, text, zahl, f_id_a_sb, datum_a, f_id_c_sb,datum_c, sessionid));	
			    }
		} catch (Exception ex) {
			ex.printStackTrace();
			if(tr!=null)tr.rollback();
				else{System.out.println("transaction=null");}
			throw new SQLException("Fehler in auslesen()", ex);
		} finally {
			//Session freigeben
			if (session != null) {
				HibernateSessionFactory.closeSession();
			}
		}
		//setAuswahlid zurücksetzen
		u--;
	}

	
	
	
	
	public String aktualisieren() throws SQLException, InterruptedException{
		j--;
		if(user.get(webserverid).getUsername().equals("Ben")){
			return "Sachbearbeiter";
		}
		else{
			return "anlegen";
		}
		
	}
	
	
	public static String changeanzeige() {
		return "change_angezeigt";
	}
	
	
	
	public static Vector changeanzeige2() throws SQLException {
		try {
				System.out.println("warte changeanzeige2()");
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		System.out.println("\nchangeanezeige2()");

		Session session = null;
		Transaction tr = null;

		try {
			aenderungen.clear();
			session = HibernateSessionFactory.getSession();
			session.beginTransaction();
			
			System.out.println("auswahlid: "+auswahlid+"\n\n");
			
			String statement = "FROM Vor_tbspiel_t test where test.id=?";
			Query query = session.createQuery(statement);
	        query.setInteger(0, auswahlid);
	        List list=query.list();
	        
			    for (Iterator iterator = list.iterator(); iterator.hasNext();){
			    	Vor_tbspiel_t tbspiel = (Vor_tbspiel_t) iterator.next();
			    	text=tbspiel.getText255();
			    	zahl=tbspiel.getZahl6();
			    	f_id_a_sb=tbspiel.getF_id_a_sb();
			    	datum_a=tbspiel.getDatum_a();
			    	f_id_c_sb=tbspiel.getF_id_c_sb();
			    	datum_c=tbspiel.getDatum_c();
			    	sessionid=tbspiel.getSessionid();
			    	aenderungen.add(0, text);
					aenderungen.add(1, zahl);
			    }
		} catch (Exception ex) {
			ex.printStackTrace();
			if(tr!=null)tr.rollback();
				else{System.out.println("transaction=null");}
			throw new SQLException("Fehler in changeanzeige()", ex);
		} finally {
			//Session freigeben
			if (session != null) {
				HibernateSessionFactory.closeSession();
			}
		}

		//setwebserverid  und setAuswahlid zurücksetzen
		j--;u--;
		System.out.println("changeanz2() zurücksetzt");
		System.out.println("aenderungen: "+aenderungen.elementAt(0)+" "+aenderungen.elementAt(1));
		return aenderungen;
	}
	
	

	
	
	
	
	

	public String change() throws SQLException {
		System.out.println("\nchange()");
		Session session = null;
		Transaction tr = null;

			try {
				Integer usid=user.get(webserverid).getUserID();
				System.out.println("UserID bearbeitet: "+usid);
				System.out.println("auswahlid: "+auswahlid);
				String uname=user.get(webserverid).getUsername();
				System.out.println("Username bearbeitet: "+uname);
				
				session = HibernateSessionFactory.getSession();
				tr = session.beginTransaction();
				
				Calendar cal = Calendar.getInstance();
			    SimpleDateFormat formater = new SimpleDateFormat();
			    String zeit=formater.format(cal.getTime());
				
				Vor_tbspiel_t tbspiel = (Vor_tbspiel_t) session.get(Vor_tbspiel_t.class,auswahlid);
				tbspiel.setText255(text);
				tbspiel.setZahl6(zahl);
				tbspiel.setF_id_a_sb(usid);
				tbspiel.setDatum_a(zeit);
				session.update(tbspiel);
				tr.commit();
				
			} catch (Exception ex) {
				ex.printStackTrace();
				if(tr!=null)tr.rollback();
					else{System.out.println("transaction=null");}
				throw new SQLException("Fehler in change()", ex);
			} finally {
				//Session freigeben
				if (session != null) {
					HibernateSessionFactory.closeSession();
				}
			}
			
			System.out.println("\n\n");
			//setWebserverid setText und setZahl zurücksetzen
			j--;k--;l--;
			System.out.println("change() zurücksetzt");
			if(user.get(webserverid).getUsername().equals("Ben")){
				return "Sachbearbeiter";
			}
			else{
				return "anlegen";
			}
	}
	
	
	
	
	
	
	
	
	
	public String delete() throws SQLException {
		System.out.println("\ndelete()");
		Session session = null;
		Transaction tr = null;

		try {
			session = HibernateSessionFactory.getSession();
			tr = session.beginTransaction();

			Vor_tbspiel_t tbspiel = (Vor_tbspiel_t) session.get(Vor_tbspiel_t.class, auswahlid);
			session.delete(tbspiel);
			tr.commit();

		} catch (Exception ex) {
			ex.printStackTrace();
			if(tr!=null)tr.rollback();
				else{System.out.println("transaction=null");}
			throw new SQLException("Fehler in delete()", ex);
		} finally {
			//Session freigeben
			if (session != null) {
				HibernateSessionFactory.closeSession();
			}
		}
		//setWevserverid  zurücksetzen
		j--;
		System.out.println("delete() zurücksetzt");
		if(user.get(webserverid).getUsername().equals("Ben")){
			return "Sachbearbeiter";
		}
		else{
			return "anlegen";
		}
	}

	
	
	
	
	
	
	public String hinzuanzeige() {
		//setwebserverid und setAuswahlid zurücksetzen
		j--;u--;
		return "hinzuanzeige";
	}

	
	
	
	
	
	public static String hinzu() throws SQLException {
		System.out.println("\nhinzu()");

		Session session = null;
		Transaction tr = null;
		try {
			Calendar cal = Calendar.getInstance();
		    SimpleDateFormat formater = new SimpleDateFormat();
		    String zeit=formater.format(cal.getTime());
		    
			session = HibernateSessionFactory.getSession();
			tr = session.beginTransaction();
		    
			//audsid-abfrage
//			String statement="select v.audsid from v$session v where v.username=? and v.status=?";
//			Query query=session.createQuery(statement)
//			.setString(0,"SYSTEM" )
//			.setString(1, "ACTIVE");
//			List list=query.list();
			String audsid= webserverid;
			System.out.println("audsid"+audsid);
			
			
			
			
			
			//hinzufügen
			String uname=user.get(webserverid).getUsername();
			System.out.println("Username aus map fügt hinzu: "+uname);
			Integer usid=user.get(webserverid).getUserID();
			System.out.println("usid aus map fügt hinzu: "+usid);
			
			
			Vor_tbspiel_t tbspiel = new Vor_tbspiel_t();
			tbspiel.setText255(neutext);
			tbspiel.setZahl6(neuzahl);
			tbspiel.setF_id_c_sb(usid);
			tbspiel.setDatum_c(zeit);
			tbspiel.setSessionid(audsid);
			session.save(tbspiel);
			tr.commit();
			
			//setwebserverid zurücksetzen
			j--;
			System.out.println("hinzu() zurücksetzt");
		} catch (Exception ex) {
			ex.printStackTrace();
			if(tr!=null)tr.rollback();
				else{System.out.println("transaction=null");}
			throw new SQLException("Fehler in hinzu()", ex);
		} finally {
			//Session freigeben
			if (session != null) {
				HibernateSessionFactory.closeSession();
			}
		}
		if(user.get(webserverid).getUsername().equals("Ben")){
			return "Sachbearbeiter";
		}
		else{
			return "anlegen";
		}
	}

	
	
	
	
	
	
	
	
	
	
	// getter und setter
	public void setWebserverid(String webserverid) {
		j++;
		while(j>1){
			try {
				System.out.println("warte in setWebserverid");
				Thread.sleep(400);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("setwebserverid: "+webserverid);
		this.webserverid = webserverid;
	}
	
	public void setUserName(String userName) {
		i++;
		while(i>1){
			try {
				System.out.println("warte in setuserName");
				Thread.sleep(400);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.userName = userName;
	}
	


	
	// von anlegen.jsp
	public void setAuswahlid(Integer auswahlid) {
		u++;
		System.out.println(u);
		while(u>1){
			try {
				System.out.println("warte in setAuswahlid");
				Thread.sleep(400);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.auswahlid = auswahlid;
	}
	public Integer getAuswahlid() {
		return auswahlid;
	}

	
	
	
	// von change.jsp
	public void setText(String text) {
		k++;
		while(k>1){
			try {
				System.out.println("warte in setText");
				Thread.sleep(400);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("setText: "+text);
		this.text = text;
	}

	public void setZahl(Integer zahl) {
		l++;
		while(l>1){
			try {
				System.out.println("warte in setZahl");
				Thread.sleep(400);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		if(zahl==null){
			zahl=0;
		}
		System.out.println("setZahl: "+zahl);
		this.zahl = zahl;
	}
	
	
	// von hinzuanzeige.jsp
	public void setNeutext(String neutext) {
		this.neutext = neutext;
	}
	public void setNeuzahl(Integer neuzahl) {
		if(neuzahl==null){
			neuzahl=0;
		}
		this.neuzahl = neuzahl;
	}
	public void setUserID(Integer UserID) {
		System.out.println("setUserID :"+UserID);
		System.out.println("\n");
		this.UserID = UserID;
	}
	public List<tbspiel_t> getTabelle() {
		System.out.println("getTabelle\n");
		return tabelle;
	}
}
```

hibernate.cfg.xml:


```
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
               
<hibernate-configuration>

<session-factory>
	<property name="connection.username">system</property>
	<property name="connection.url">jdbc:oracle:thin:@localhost:1521:XE</property>
	<property name="dialect">org.hibernate.dialect.Oracle9Dialect</property>
	<property name="connection.password">adminadmin</property>
	<property name="connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
	<property name="connection.useUnicode">true</property>
	<property name="connection.characterEncoding">UTF-8</property>
	<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>

	<property name="c3p0.acquire_increment">1</property>
	<property name="c3p0.idle_test_period">27000</property>
	<property name="c3p0.max_size">10</property>
	<property name="c3p0.max_statements">0</property>
	<property name="c3p0.min_size">1</property>
	<property name="c3p0.timeout">100</property>
	
	<property name="show_sql">true</property>
    <mapping class="db.Vor_anwender_t" />
    <mapping class="db.Vor_tbspiel_t" />
</session-factory>

</hibernate-configuration>
```
Bisher habe ich es so gelöst, dass die Variablen max. von einem Nutzer geändert werden können, bis er fertig ist.
D.h. Nutzer A klickt auf ändern, geht in die setter Methode und dann in die change(). Nutzer B klickt gleichzeitig ändern und muss aber warten bis Nutzer A die change() verlassen hat.

Nur das würde ja heißen,  das später mehrere hundert Personen eine Datenbankconnection nur verwenden, weil der Eine auf den Anderen wartet.
Habt ihr vielleicht noch eine Lösung?

grüsse


----------



## gman (30. Apr 2010)

> Das Problem ist , dass die Variable text und zahl bei mir global und statisch sein müssen



Ähm, bei Struts müssen die Variablen eigentlich nur private sein und über Getter/Setter-Methoden (oder
ModelDriven, für Fortgeschrittene) erreichbar sein. Und wenn zwei Benutzer auf die Seite zugreifen
bekommt jeder seine eigene Instanz der Actionklasse durch das Framework zugewiesen.

Ich denke das Problem ist hier eher der Datenbankzugriff mit Hibernate & Locking-Mechanismen.

Aber um dein Beispiel ganz zu verstehen müsste man mehr vom Code sehen (Actionklasse, DB-Struktur).


----------



## 7bkahnt (1. Mai 2010)

Oh okay, ich dachte mir, es ist ein bisschen viel Code. Aber wär echt ideal, wenn sich jemand die Mühe machen könnte und mir helfen kann. 
Ich habe jetzt die komplette Actionklasse mal gepostet und die hibernate.cfg.xml, falls du das mit DB-Struktur meinst. Ich habe zwei Tabellen "Anwender" und "tbspiel".
Der Nutzer klickt Login, danach wird DBVerbindung() aufgerufen, wo geschaut wird, ob der Nutzer in der Tabelle "Anwender" existiert und es wird die id von ihm ausgelesen. Danach wird automatisch die auslesen() aufgerufen, damit er die komplette Tabelle zu sehen bekommt.
Dann klickt er z.B. auf "Bearbeiten" und es wird changeanzeige() aufgerufen welche auf die changeanzeige.jsp verlinkt. Dann kann er den Text und die Zahl, die er ändern will eingeben und klickt auf "Ändern", womit changeanzeige2() aufgerufen wird und die Werte in die DB geschrieben werden.
Und da liegt aktuell eben mein Problem, wenn zwei Nutzer gleichzeitig auf "Ändern" klicken, weil da ja die Werte gesetzt werden.
Also du meinst mit private Variablen funktioniert das? Dann wird die Variable nicht wie bei public nur einmal angelegt, sondern jeder erhält eine eigene dann oder wie?


----------



## gman (1. Mai 2010)

Also irgendwie erkenne ich nicht das das Ganze eine Struts2-Anwendung sein soll :bahnhof:

In der struts.xml legst du doch fest bei welcher Action welche Methode in welcher Klasse
aufgerufen werden soll. Diese Klasse erbt von ActionSupport (ah, ok sehe ich bei dir).
Die Variablen auf die in den JSP-Seiten auch zugegriffen werden (<sroperty value="varName"/>) 
soll machst du einfach private mit Getter/Setter-Methoden.

Wenn jetzt zwei Benutzer daher kommen und dieselbe Action ausführen bekommen die beiden
jeweils ihren eigenen "Valuestack" (so nennen die das bei Struts, glaub ich) und du brauchst
dich nicht darum zu kümmern.

Such vielleicht mal nach einem Tutorial-PDF mit dem Titel "Starting with Struts2" oder so 
ähnlich :rtfm:


----------



## 7bkahnt (1. Mai 2010)

Super Sache.
Dann werd ich das gleich mal ausprobieren.
Was mir aber grad noch einfällt, die Variablen müsste dann ja nur private sein oder?
Bei mir wären die dann private static, was ja wiederum besagt, dass sie eig nur einmal erzeugt werden.
Die müssen bei mir static sein weil verschiedene Methoden auch static bei mir sein müssen um die in der jsp aufrufen zu können. Sonst bringt er immer "cannot make a non-static to a static reference" o.ä..


----------



## gman (1. Mai 2010)

Nein, eben nicht static. Mit Struts rufst du in der Regel keine Methoden direkt aus der JSP
auf, sondern eben eine Action. In der struts.xml steht dann welche Methode in welcher Klasse
für die aufgerufene Action zuständig ist.

Aufruf der Action in der JSP-Seite:

```
<s:submit action="action_name" label="Aktion ausführen"/>
```

Mapping der Action auf eine Methode:

```
<action name="action_name" class="pkg.to.Class.ActionClass" method="zustaenigeMethode">
  <result name="success">irgendeineSeite.jsp</result>
</action>
```

Und hier gibts mehr Infos


----------



## stephanm (1. Mai 2010)

7bkahnt hat gesagt.:


> Super Sache.
> Die müssen bei mir static sein weil verschiedene Methoden auch static bei mir sein müssen um die in der jsp aufrufen zu können.



Mir schwant, dass Dir schlicht und ergreifend ganz elementare Kenntnisse zu den Themen Programmierung, Softwarearchitektur und Struts2 fehlen.

Warum *müssen* bei Dir irgendwelche Methoden statisch sein? Auch ohne die Anwendung zu kennen kann ich Dir mit 99%iger Sicherheit sagen, dass Du Dich genau bei dem "*müssen*" verhauen hast.

Schon beim kurzen drüberscrollen sind mir bei dem Sourcecode, den Du gepostet hast, folgende Sachen aufgefallen:

1. In einer strukturierten Webanwendung haben DB-Zugriffe nichts in einer Struts Action verloren. Action = Controller, nicht Action = Model+Controller!

2. In Deiner Action ist so viel Zeug (Methoden und Variablen) statisch... das gehört da absolut nicht hin! Static bedeutet doch gerade bei Methoden, dass sie ohne Instanz ihrer kapselnder Klasse auskommen. Also raus mit den statischen Methoden aus der Action und rein damit in Hilfsklassen!

3. Was sollen die Variablen i, j, k, l, u? Die sind static(!?) und werden dann in _irgendwo_ wild inkrementiert und dekrementiert. Glaubst Du ernsthaft, jemand anders (oder gar Du in zwei Monaten) versteht (noch) was da wo wie weshalb passiert?

4. Logging per System.out? Überleg Dir mal, wie viel Zukunftssicherer der Einsatz von z.B. Log4J ist und wie wenig Zeit es Dich im Vergleich dazu kostet, dass mal eben einzurichten (immerhin hast Du ja Hibernate auch in den Griff bekommen). 

5. Was willst Du eigentlich mit den ganzen Thread.sleep()s? Die haben da nichts, wirklich garnichts verloren. Wenn Du eine Anwendung ausbremsen musst, damit sie funktioniert, dann ist an anderer Stelle etwas faul, und zwar so faul, dass es zum Himmel stinkt.

6. Weg mit dem "static" bei Deinen Variablen. Es gibt mit Sicherheit andere, sinnvolle Lösungen, bei denen man "static" nicht braucht.

7. Dein Exception-Handling ist..... sagen wir mal: interessant. try { ... } catch (Exception e) { throw new SQLException(...); }... Brrrr!

Nimm mir das was ich gerade geschrieben habe und das was jetzt kommt nicht krumm, sondern siehs als Anreiz, Dich und Deine Applikation zu verbessern. Und da würde ich wie folgt vorgehen:

- Das, was bisher vorhanden ist (also zumindest das, was Du hier veröffentlicht hast), wird eingestampft.

- Du liest was über MVC und Struts2 und macht nicht weiter, bevor Du mindestens ein wenig verstanden hast, wie Struts2 funktioniert und weshalb Struts2 sich selbst als Framework bezeichnet, dass dem Entwickler dabei hilft, Webanwendungen mit MVC-Struktur zu bauen.

- Du trennst ab sofort zwischen Model und Controller. In das Model gehört dann z.B. der ganze Kram zur Interaktion mit der DB und die Geschäftslogik. In die Controller (Actions) gehört die Übersetzung der Benutzeraktionen auf Interaktionen mit dem Model und die Ablaufsteuerung (d.h. die "Führung" des Benutzers durch die Webanwendung).

- Zustandslose Hilfsmethoden (public static) kommen in eigene Helper-Klassen.

Es wäre übrigens vielleicht auch ganz hilfreich, wenn Du mal auf einer höheren Ebene beschreibst, was die Applikation können soll. Dann lassen sich ja hier vielleicht auch ein paar Tips geben, wie man das ganze konkret strukturieren kann und worauf Du achten kannst/solltest.

Und übrigens: Die Hibernate-Konfigurationsdatei verrät mir zwar, welche Klassen persistent sind, aber wie die aussehen - tja, das ist im Moment Dein Geheimnis?

Stephan


----------



## 7bkahnt (2. Mai 2010)

Ersteinmal danke für die vielen Antworten und Tipps.
Die Anwendung ist soll nur eine Testanwendung sein, weshalb ich mir da auch nicht so viel mühe zwecks Variablen genommen habe.
Das mit dem Thread.sleep und den vielen Inkrementierungen, habe ich gemacht, damit so ersteinmal mehrere Anwender scheinbar "parallel" arbeiten können, aber eben im Grunde genommen eigentlich wenige Millisekunden auf sich warten, weil ich bisdato noch keine andere Lösung hatte.

Das Programm läuft wie folgt ab.
Der Anwender öffnet die Seite und bekommt ein Textfeld zu Gesicht, wo er sein Name eintragen soll. Dann klickt er auf Login und DBVerbindung() wird aufgerufen. Die Sysetem.out.println´s sind übrigens nur für mich zum nachvollziehen. Die nehm ich natürlich später für andere Anwendungen raus zwecks Sicherheit.
Nach dem Einloggen bekommt der Anwender dann die Tabelle tbspiel zu Gesicht, welche durch die anlegen.jsp verkörpert wird, in der die auslesen()-Methode aufgerufen wird.
Und eben genau deswegen, weil ich die Funktion mit <%auslesen()%> in der jsp aufrufe, muss die Funktion auslesen  bei mir static sein, weil sonst der Fehler kommt "cannot make a non-static......".
Ich bin leider noch recht neu auf dem Gebiet Struts. Ich habe ein paar Tutorials gemacht und mir ist schon klar, wie ich Methoden aufrufe und die in der struts.xml verlinken muss.

Der Anwender kann dann wie schon erwähnt bearbeiten,löschen und hinzufügen.
Nach jedem bearbeiten,löschen und hinzufügen, wird der Anwender auf die anlegen.jsp zurück verlinkt, wo eben die auslesen() wieder aufgerufen wird, damit er die Tabelle aktualisiert zu Gesicht bekommt.
Kennt ihr vielleicht nocheinen anderen Weg nach jedem bearbeiten... die Tabelle zu aktualisieren ohne das die Fkt static sein muss?

Hier mal die anlegen.jsp auf die ich nach jeder Aktion zurückverlinke:


```
<%@ page language="java" import="java.util.*" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1" import="java.io.*,Nutzer.*,java.util.*"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@taglib uri="/struts-tags" prefix="s" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
 <%EingebenAction.auslesen();%>
<script type="text/JScript"> 

function auswahl(id) 
{
    document.getElementById('auswahlid').value = id; 
}
var serversession;
function umschreiben() 
{   serversession=document.getElementById('usernr').value;
    document.getElementById('webserverid').value = serversession; 
} 
</script>
</head>
  
  
<body>
<s:form action="Eingeben">
<table border="1">
	<tr>
		<td><b>ID</b></td>
		<td><b>TEXT255</b></td>
		<td><b>ZAHL6</b></td>
		<td><b>F_ID_A_SB</b></td>
		<td><b>DAT_A</b></td>
		<td><b>F_ID_C_SB</b></td>
		<td><b>DAT_C</b></td>
		<td><b>SESSIONID</b></td>
		<td><b>Bearbeiten</b></td>
		<td><b>Löschen</b></td>
	
	</tr>
	
<s:iterator value="tabelle" var="zeile" status="test">
		<tr>
			<td><s:property value="ID"/></td>
			<td><s:property value="TEXT255" /></td>
			<td><s:property value="ZAHL6" /></td>
			<td><s:property value="F_ID_A_SB" /></td>
			<td><s:property value="DAT_A" /></td>
			<td><s:property value="F_ID_C_SB" /></td>
			<td><s:property value="DAT_C" /></td>
			<td><s:property value="SESSIONID" /></td>
			<td><input name="action:changeanzeigeEingeben" type="submit" value="Bearbeiten" onclick="auswahl(${zeile.ID});"/></td>
			<td><input name="action:deleteEingeben" type="submit" value="Löschen" onclick="auswahl(${zeile.ID});" /></td>
			
	</s:iterator>
	<s:submit action="hinzuanzeigeEingeben" value="Hinzufügen" />
	<s:submit action="aktualisierenEingeben" value="Aktualisieren"/>
	</tr>
	<s:hidden id="auswahlid" name="auswahlid"/>
	<input type="hidden" id="usernr" name="usernr" value=<%out.println(session.getId());%>/>
	<s:textfield id="webserverid" name="webserverid" size="50" label="Session-ID"/>
			<script language=javascript>
   		         umschreiben();
            </script>
	</table>
	

	</s:form>
	
</body>
</html>
```


Hier noch die Tabelle
Vor_tbspiel_t.java:

```
package db;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;


@Entity
@Table(name="vor_tbspiel_t")
public class Vor_tbspiel_t {
	
	private Integer id;
	private String text255;
	private Integer zahl6;
	private Integer f_id_a_sb;
	private String datum_a;
	private Integer f_id_c_sb;
	private String datum_c;
	private String sessionid;
	
	
	
	public Vor_tbspiel_t() {
	}
	@Id
	@GeneratedValue
	@Column(name="ID")
	public  Integer getId() {
		return id;
	}
	public  void setId(Integer id) {
		this.id = id;
	}
	
	@Column(name="TEXT255")
	public String getText255() {
		return text255;
	}
	public void setText255(String text255) {
		this.text255 = text255;
	}
	@Column(name="ZAHL6")
	public Integer getZahl6() {
		return zahl6;
	}
	public void setZahl6(Integer zahl6) {
		this.zahl6 = zahl6;
	}
	
	@Column(name="F_ID_A_SB")
	public Integer getF_id_a_sb() {
		return f_id_a_sb;
	}
	public void setF_id_a_sb(Integer f_id_a_sb) {
		this.f_id_a_sb = f_id_a_sb;
	}
	@Column(name="DAT_A")
	public String getDatum_a() {
		return datum_a;
	}
	public void setDatum_a(String datum_a) {
		this.datum_a = datum_a;
	}
	
	@Column(name="F_ID_C_SB")
	public Integer getF_id_c_sb() {
		return f_id_c_sb;
	}
	public void setF_id_c_sb(Integer f_id_c_sb) {
		this.f_id_c_sb = f_id_c_sb;
	}
	
	@Column(name="DAT_C")
	public String getDatum_c() {
		return datum_c;
	}
	public void setDatum_c(String datum_c) {
		this.datum_c= datum_c;
	}
	
	@Column(name="SESSIONID")
	public String getSessionid() {
		return sessionid;
	}
	public void setSessionid(String sessionid) {
		this.sessionid = sessionid;
	}
}
```


Schleierhaft ist mir bisher auch, wie ich Parameter an Funktionen übergeben kann mit Struts.
Ist dies überhaupt möglich?
Ich danke euch schonmal für die Mühe, mein Problem zu analysieren und mir zu helfen.


----------



## stephanm (2. Mai 2010)

Vorneweg:



7bkahnt hat gesagt.:


> ... Ich habe ein paar Tutorials gemacht und mir ist schon klar, wie ich Methoden aufrufe ...



[x] Dir ist *nicht* klar, wie Struts2 und MVC funktionieren.



7bkahnt hat gesagt.:


> Das mit dem Thread.sleep und den vielen Inkrementierungen, habe ich gemacht, damit so ersteinmal mehrere Anwender scheinbar "parallel" arbeiten können, aber eben im Grunde genommen eigentlich wenige Millisekunden auf sich warten, weil ich bisdato noch keine andere Lösung hatte.



Falscher Ansatz, auch für einen Anfänger. Beschäftige Dich lieber intensiver mit der Materie und machs von Anfang an sauber, statt solche Workarounds einzuführen. Damit kaschierst Du Probleme, die Du dann später, wenn die Applikation etwas gewachsen ist, nur noch mühevoll bereinigen kannst. Abgesehen davon nimmst Du Dir selber die Chance, die Materie so aufzuarbeiten, dass Du sie verstehst. Dir fehlen die Kenntnisse, wie man mit Struts2 arbeitet, also hast Du alles irgendwie hin- und hergebogen, mit viel public static und Thread.sleep().



7bkahnt hat gesagt.:


> Das Programm läuft wie folgt ab.
> Der Anwender öffnet die Seite und bekommt ein Textfeld zu Gesicht, wo er sein Name eintragen soll. Dann klickt er auf Login und DBVerbindung() wird aufgerufen.



AAA (salopp gesagt ist AAA "das ganze Anmeldezeugs") überlässt man i.A. dem Container. Stichworte: Container Managed Security, Security Constraints, Realms, JAAS. 

Die ersten beiden Punkte sind auch für einen Anfänger ohne weiteres machbar, wenn man das Konzept ein bisschen verstanden hat. Schau mal in die Tomcat-Doku, da steht praktisch alles drin, was man für den Anfang wissen muss: Apache Tomcat 6.0 - Realm Configuration HOW-TO



7bkahnt hat gesagt.:


> Die Sysetem.out.println´s sind übrigens nur für mich zum nachvollziehen. Die nehm ich natürlich später für andere Anwendungen raus zwecks Sicherheit.



Neeee!  Log4J zur Anwendung hinzufügen und von vorne herein verwenden. Beim Entwickeln alle Logger auf DEBUG-Level, im Produktionseinsatz dann per Konfiguration(!) ggf. runterschrauben auf INFO oder WARNING.



7bkahnt hat gesagt.:


> Nach dem Einloggen bekommt der Anwender dann die Tabelle tbspiel zu Gesicht, welche durch die anlegen.jsp verkörpert wird, in der die auslesen()-Methode aufgerufen wird.



Der direkte Aufruf von Methoden aus einer JSP heraus geht - wenn man Struts einsetzt - höchstens noch als schlechter Stil durch. Ich würde mit sowas garnicht erst anfangen. Es gibt bessere Lösungen.



7bkahnt hat gesagt.:


> Und eben genau deswegen, weil ich die Funktion mit <%auslesen()%> in der jsp aufrufe, muss die Funktion auslesen  bei mir static sein, weil sonst der Fehler kommt "cannot make a non-static......".



Dir fehlen die Grundlagen im Umgang mit/Verständnis von Servlet/JSP und Struts2.



7bkahnt hat gesagt.:


> Der Anwender kann dann wie schon erwähnt bearbeiten,löschen und hinzufügen.
> Nach jedem bearbeiten,löschen und hinzufügen, wird der Anwender auf die anlegen.jsp zurück verlinkt, wo eben die auslesen() wieder aufgerufen wird, damit er die Tabelle aktualisiert zu Gesicht bekommt.
> Kennt ihr vielleicht nocheinen anderen Weg nach jedem bearbeiten... die Tabelle zu aktualisieren ohne das die Fkt static sein muss?



Jaaaaaa!

Wenn man den Code an die richtige Stelle schreibt, ist das alles überhaupt kein Problem mehr. Google mal nach "Struts2 CRUD Example" oder "Struts2 CRUD Demo" oder "Struts2 CRUD Tutorial". Du machst mit Deinen Datensätzen nichts anderes als CRUD, d.h. Aktionen wie Create, Read, Update, Delete. Und Du bist definitiv nicht der erste, der sowas macht. Es gibt mit Sicherheit mehrere gute Anleitungen (eines davon sogar in der Doku zu Struts), in denen schön gezeigt wird, wie man genau solche Softwarekomponenten mit Struts2 baut.



7bkahnt hat gesagt.:


> Hier mal die anlegen.jsp auf die ich nach jeder Aktion zurückverlinke:
> 
> 
> ```
> ...



Klassenimports mit "*" am Ende sind nicht nur in Java-Klassen schlechter Stil, sondern auch in JSPs. Explizite Imports a la import="com.yoyodyne.webapp.utils.MyHelperClass" kosten keinen Cent mehr und sind einfach 100% eindeutig, welche Klasse tatsächlich verwendet wird.



7bkahnt hat gesagt.:


> Schleierhaft ist mir bisher auch, wie ich Parameter an Funktionen übergeben kann mit Struts.
> Ist dies überhaupt möglich?



Ja, OGNL sei dank. Aber das geht in eine völlig andere Richtung als Du Dir das im Moment vermutlich vorstelltst. Man ruft halt im Normalfall einfach keine Methoden direkt aus einer JSP auf. 

Mein Rat: Versuche entweder, mit Struts2 vernünftig umzugehen oder spar Dir den ganzen Struts/MVC-Käse von Anfang an und mach das ganz klassisch mit Servlet+JSP oder einfach nur mit JSPs. Aber bitte nichts irgendwo irgendwie irgendwas in der Mitte, das ist schlicht und ergreifend Vergewaltigung von Frameworks mit Null Lerneffekt.

Stephan


----------



## stephanm (2. Mai 2010)

So, und damit von mir nicht nur Kritik kommt, hier mal grob ein Abriss wie ich das ganze gestalten würde:

1. Wir brauchen eine Entity. Ok, hast Du schon, auch wenn die Felder - mit verlaub gesagt - sehr seltsame Namen haben. Ich mach da immer sowas in der Art:


```
@Entity(table = "table_name")
public class MyEntity {
  /** Maximum length of usernames, in characters */
  public final static int MAX_USERNAME_LENGTH = 40;

  @Id
  @GeneratedValue
  @Column(name = "id")
  private long id;

  @Column(name = "username", length = MAX_USERNAME_LENGTH, nullable = false, ...)
  private String username;

  @Column(name = "foo_bar_blubb", ...)
  private Type fooBarBlubb;

  ...

  ... getter/setter ...
}
```

Vorteil: JavaBean-seitig hast Du ordentliche Namen für die Properties (fooBarBlubb), Datenbankseitig hast Du ordentliche Spaltennamen (foo_bar_blubb). Ist aber Geschmackssache (ersteres weniger, zweiteres mehr).

2. Wir brauchen eine EAO-Klasse (EAO = Entity Access Object), in der die Zugriffe auf die DB gekapselt sind:


```
pubic class MyEntityEAO {
  public Collection<MyEntity> getAllEntities() throws ... { ... }
  public MyEntity getEntityById(long id) throws ... { ... }
  public MyEntity saveOrUpdate(MyEntity entity) throws ... { ... };
  ...
}
```

Vorteil: Du sprenkelst nicht die gesamte Applikation mit Codefragmenten, die mit Hibernate rumwurschteln. Dem Rest des Codes ist es dann möglicherweise sogar egal, ob Dein EAO intern Hibernate, JDBC oder ein CSV-Backend verwendet. Wilkommen in der Welt der Modularität!

Hinweis: Noch besser ist es, das EAO hinter einem Interface (z.B. EntityEAO) zu verstecken und mittels Dependency Injection zu verwenden. Das kannst Du via Struts machen oder Du nimmst zusätzlich den Spring IoC-Container und verwendest das Struts2-Spring-Plugin.

3. Für den Teil des Webfrontends, dass die Liste anzeigt, brauchen wir zunächst eine Struts Action und die dazu gehörende JSP-Seite:


```
public class ShowTableAction extends ActionSupport implements ... {
  // Automatically setup via Struts2's DI container (or Spring's Bean Factory, if we're using Struts2/Spring integration)
  private EntityEAO entityEAO;

  private Collection<MyEntity> entityCollection;

  public String execute() {
    // Fetch the collection of entities from the persistence layer
    entityCollection = entityEAO.getAllEntities();

    // Done :-)
    return SUCCESS;
  }

  ... getter/setter/sonstwas ...
}
```

Das war's. Die Action hat ein Feld "entityCollection", das beim Aufruf von execute() mit Hilfe des EAO befüllt wird. In der JSP-Seite machst Du dann ein <s:iterate var="entityCollection" ...> und erzeugst die Tabelle. Es gibt hier *KEINE* Notwendigkeit, in der JSP-Seite irgendwelche Methoden zum holen der Datenbankeinträge aufzurufen. Hier entsteht NICHT das Problem, dass irgendetas static sein müsste!

Bemerkung: Ohne DI wird das entityEAO halt einfach inline initialisiert:


```
private EntityEAO entityEAO = new MyEntityEAO();
```

So, jetzt gehe ich davon aus, dass Du in der JSP-Seite, die die Tabelle anzeigt, irgendwo draufclicken kannst, um die einzelnen Datensätze zu bearbeiten. Die Action zum bearbeiten der Datensätze könnte dann in Etwa so aussehen:


```
public class EditEnityAction extends ActionSupport implements ... {
  // Automatically setup via Struts2's DI container (or Spring's Bean Factory, if we're using Struts2/Spring integration)
  private EntityEAO entityEAO;

  /** The ID of the entity to edit */
  private long id;

  /** The entity being edited */
  private MyEntity entity;

  public String execute() {
    // Load the entity
    entity = entityEAO.getEntityById(id);

    // Check if the entity was found
    if (entity == null) {
       throw new MyCustomExceptionThatIsThrownWhenAnObjectIsNotPresentInTheDatabase(MyEntity.class, id);
    };

    // Done
    return SUCCESS; 
  }

  ... getter/setter/sonstwas ...
}
```

Auch hier brauchst Du für die JSP-Seite für das Bearbeiten des Datensatzes mit Sicherheit keine statischen Methoden mehr.

Ist doch eigentlich ganz einfach, oder? 

Stephan


----------



## 7bkahnt (3. Mai 2010)

Danke euch für die vielen Tipps. .
Wenn ich das richtig sehe, fehlt mir ja an sich nur die EAO-Klasse und die Action in mehrere Actions zu unterteilen.
Da ich aber jetzt nur eine Testanwendung schreibe und ich möglichst schnell zur richtigen Anwendung kommen möchte, verzichte ich jetzt mal auf die Klassen und lasse ersteinmal die System.out.println´s drin.
Es geht ja jetzt eigentlich nur noch um den Mehrbenutzerbetrieb, ansonsten funktioniert ja alles.

Ich habe die Action-Klasse jetzt nocheinmal aktualsiert, die static Methoden entfernt und die Variablen alle private deklariert.
Allerdings funktioniert es immernoch nicht.

Für einen Benutzer kein Problem. Sobald jedoch 2 Nutzer gleichzeitig ändern klicken, wird wahrscheinlich wieder versucht gleichzeitig zwei unterschiedliche Werte auf eine Variable zu schreiben.
Habt ihr eine Idee woran es liegen könnte?
Die auslesen()-Funtkion wird jetzt nicht mehr in der jsp aufgerufen, sondern nach jedem Bearbeiten oder hinzufügen.

Hier mal die aktualisierte Action.

```
package Nutzer;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import sun.jdbc.odbc.ee.DataSource;

import com.opensymphony.xwork2.ActionSupport;
import db.HibernateSessionFactory;
import db.Vor_anwender_t;
import db.Vor_tbspiel_t;


public  class EingebenAction extends ActionSupport {
	private  String userName,text,datum_a, datum_c, neutext,webserverid,sessionid;
	private  Integer id,zahl,f_id_a_sb,f_id_c_sb,neuzahl,auswahlid,UserID;
	private  Connection myConnection;
	private List<tbspiel_t> tabelle = new ArrayList<tbspiel_t>();
	public static  Map<String,User> user = new HashMap<String,User>(); 
	private Vector aenderungen=new Vector();
	
	
	

	public String DBVerbindung() throws SQLException{
		System.out.println("\nDBVerbindung()");
		Session session = null;
		Transaction tr = null;

		try {
			session = HibernateSessionFactory.getSession();
			
			//Userabfrage aus anwender_t
			String statement = "select user.id from Vor_anwender_t user where user.username=?";
		    List list = session.createQuery(statement).setString(0, userName).list();
			UserID=(Integer) list.get(0);

			user.put(webserverid, new User(UserID,userName)); 
			System.out.println("User verglichen");
			System.out.println("userName: "+userName);
			System.out.println("UserID :"+UserID);
			System.out.println("webserverid :"+webserverid);
			
		} catch (Exception ex) {
			ex.printStackTrace();
			if(tr!=null)tr.rollback();
				else{System.out.println("transaction=null");}
			throw new SQLException("Fehler in DBVerbindung()", ex);
		} finally {
			//Session freigeben
			if (session != null) {
				HibernateSessionFactory.closeSession();
			}
		}
		
		try {
			auslesen();
		} catch (InterruptedException e) {
			System.out.println("Fehler in DBVerbindung() durch auslesen()");
			e.printStackTrace();
		}
		return "anlegen";
		
	}
	
	
	
	
	public void auslesen() throws SQLException, InterruptedException {
		System.out.println("\nauslesen()");
	
		Session session = null;
		Transaction tr = null;

		try {
			// Tabelle auslesen und anzeigen
			tabelle.clear();
			session = HibernateSessionFactory.getSession();
			session.beginTransaction();
			
			 String statement = "FROM Vor_tbspiel_t test order by test.id";
			    List list = session.createQuery(statement).list();
			    
			    for (Iterator iterator = list.iterator(); iterator.hasNext();){
			    	Vor_tbspiel_t tbspiel = (Vor_tbspiel_t) iterator.next();
					
			    	id=tbspiel.getId();
			    	text=tbspiel.getText255();
			    	zahl=tbspiel.getZahl6();
			    	f_id_a_sb=tbspiel.getF_id_a_sb();
			    	datum_a=tbspiel.getDatum_a();
			    	f_id_c_sb=tbspiel.getF_id_c_sb();
			    	datum_c=tbspiel.getDatum_c();
			    	sessionid=tbspiel.getSessionid();
			    	if (zahl == null)      zahl = 0;
					if (f_id_a_sb == null)    f_id_a_sb = 0;
					if (datum_a == null)   datum_a = "-";
					if (f_id_c_sb == null)    f_id_c_sb = 0;
					if (datum_c == null)   datum_c = "-";
					if (sessionid == null) sessionid = "-";
			    	tabelle.add(new tbspiel_t(id, text, zahl, f_id_a_sb, datum_a, f_id_c_sb,datum_c, sessionid));	
			    }
		} catch (Exception ex) {
			ex.printStackTrace();
			if(tr!=null)tr.rollback();
				else{System.out.println("transaction=null");}
			throw new SQLException("Fehler in auslesen()", ex);
		} finally {
			//Session freigeben
			if (session != null) {
				HibernateSessionFactory.closeSession();
			}
		}
	}

	
	
	
	
	public String aktualisieren() throws SQLException, InterruptedException{
		auslesen();
			return "anlegen";
		
		
	}
	
	
	public String changeanzeige() {
		return "change_angezeigt";
	}
	
	
	
	public  Vector changeanzeige2() throws SQLException {
		try {
				System.out.println("warte changeanzeige2()");
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		System.out.println("\nchangeanezeige2()");

		Session session = null;
		Transaction tr = null;

		try {
			aenderungen.clear();
			session = HibernateSessionFactory.getSession();
			session.beginTransaction();
			
			System.out.println("auswahlid: "+auswahlid+"\n\n");
			
			String statement = "FROM Vor_tbspiel_t test where test.id=?";
			Query query = session.createQuery(statement);
	        query.setInteger(0, auswahlid);
	        List list=query.list();
	        
			    for (Iterator iterator = list.iterator(); iterator.hasNext();){
			    	Vor_tbspiel_t tbspiel = (Vor_tbspiel_t) iterator.next();
			    	text=tbspiel.getText255().toString();
			    	text=text.replace(" ", "_");
			    	zahl=tbspiel.getZahl6();
			    	f_id_a_sb=tbspiel.getF_id_a_sb();
			    	datum_a=tbspiel.getDatum_a();
			    	f_id_c_sb=tbspiel.getF_id_c_sb();
			    	datum_c=tbspiel.getDatum_c();
			    	sessionid=tbspiel.getSessionid();
			    	aenderungen.add(0, text);
					aenderungen.add(1, zahl);
			    }
		} catch (Exception ex) {
			ex.printStackTrace();
			if(tr!=null)tr.rollback();
				else{System.out.println("transaction=null");}
			throw new SQLException("Fehler in changeanzeige()", ex);
		} finally {
			//Session freigeben
			if (session != null) {
				HibernateSessionFactory.closeSession();
			}
		}
		System.out.println("aenderungen: "+aenderungen.elementAt(0)+" "+aenderungen.elementAt(1));
		
		return aenderungen;
	}
	
	

	
	
	
	
	

	public String change() throws SQLException {
		Calendar cal = Calendar.getInstance();
	    SimpleDateFormat formater = new SimpleDateFormat();
	    String zeit=formater.format(cal.getTime());
		
		System.out.println("\nchange()");
		Session session = null;
		Transaction tr = null;

			try {
				Integer usid=user.get(webserverid).getUserID();
				System.out.println("UserID bearbeitet: "+usid);
				System.out.println("auswahlid: "+auswahlid);
				String uname=user.get(webserverid).getUsername();
				System.out.println("Username bearbeitet: "+uname);
				
				session = HibernateSessionFactory.getSession();
				tr = session.beginTransaction();
				
			
				Vor_tbspiel_t tbspiel = (Vor_tbspiel_t) session.get(Vor_tbspiel_t.class,auswahlid);
				tbspiel.setText255(text);
				tbspiel.setZahl6(zahl);
				tbspiel.setF_id_a_sb(usid);
				tbspiel.setDatum_a(zeit);
				session.update(tbspiel);
				tr.commit();
				text=text.replace("_", " ");
				
				
			} catch (Exception ex) {
				ex.printStackTrace();
				if(tr!=null)tr.rollback();
					else{System.out.println("transaction=null");}
				throw new SQLException("Fehler in change()", ex);
			} finally {
				//Session freigeben
				if (session != null) {
					HibernateSessionFactory.closeSession();
				}
			}
			System.out.println("\n\n");
			try {
				auslesen();
			} catch (InterruptedException e) {
				System.out.println("Fehler in aktualisieren durch auslesen()");
				e.printStackTrace();
			}
				return "anlegen";
			
	}
	
	
	
	
	
	
	
	
	
	public String delete() throws SQLException {
		System.out.println("\ndelete()");
		Session session = null;
		Transaction tr = null;

		try {
			session = HibernateSessionFactory.getSession();
			tr = session.beginTransaction();

			Vor_tbspiel_t tbspiel = (Vor_tbspiel_t) session.get(Vor_tbspiel_t.class, auswahlid);
			session.delete(tbspiel);
			tr.commit();

		} catch (Exception ex) {
			ex.printStackTrace();
			if(tr!=null)tr.rollback();
				else{System.out.println("transaction=null");}
			throw new SQLException("Fehler in delete()", ex);
		} finally {
			//Session freigeben
			if (session != null) {
				HibernateSessionFactory.closeSession();
			}
		}
		try {
			auslesen();
		} catch (InterruptedException e) {
			System.out.println("Fehler in delete() durch auslesen()");
			e.printStackTrace();
		}
			return "anlegen";
	}

	
	
	
	
	
	
	public String hinzuanzeige() {
		return "hinzuanzeige";
	}

	
	
	
	public  String hinzu() throws SQLException {
		System.out.println("\nhinzu()");

		Session session = null;
		Transaction tr = null;
		try {
			Calendar cal = Calendar.getInstance();
		    SimpleDateFormat formater = new SimpleDateFormat();
		    String zeit=formater.format(cal.getTime());
		    
			session = HibernateSessionFactory.getSession();
			tr = session.beginTransaction();
		    
			//audsid-abfrage
//			String statement="select v.audsid from v$session v where v.username=? and v.status=?";
//			Query query=session.createQuery(statement)
//			.setString(0,"SYSTEM" )
//			.setString(1, "ACTIVE");
//			List list=query.list();
			String audsid= webserverid;
			System.out.println("audsid"+audsid);
			
			
			
			
			
			//hinzufügen
			String uname=user.get(webserverid).getUsername();
			System.out.println("Username aus map fügt hinzu: "+uname);
			Integer usid=user.get(webserverid).getUserID();
			System.out.println("usid aus map fügt hinzu: "+usid);
			
			
			Vor_tbspiel_t tbspiel = new Vor_tbspiel_t();
			tbspiel.setText255(neutext);
			tbspiel.setZahl6(neuzahl);
			tbspiel.setF_id_c_sb(usid);
			tbspiel.setDatum_c(zeit);
			tbspiel.setSessionid(audsid);
			session.save(tbspiel);
			tr.commit();
         tr.commit();
			
		} catch (Exception ex) {
			ex.printStackTrace();
			if(tr!=null)tr.rollback();
				else{System.out.println("transaction=null");}
			throw new SQLException("Fehler in hinzu()", ex);
		} finally {
			//Session freigeben
			if (session != null) {
				HibernateSessionFactory.closeSession();
			}
		}
		try {
			auslesen();
		} catch (InterruptedException e) {
			System.out.println("Fehler in hinzu() durch auslesen()");
			e.printStackTrace();
		}
			return "anlegen";
	}

	
	
	
	
	
	
	
	
	
	// getter und setter
	public void setWebserverid(String webserverid) {
		System.out.println("setwebserverid: "+webserverid);
		this.webserverid = webserverid;
	}
	
	public void setUserName(String userName) {
		this.userName = userName;
	}
	

	
	// von anlegen.jsp
	public void setAuswahlid(Integer auswahlid) {
		this.auswahlid = auswahlid;
	}
	public Integer getAuswahlid() {
		return auswahlid;
	}

	
	
	// von change.jsp
	public void setText(String text) {
		System.out.println("setText: "+text);
		this.text = text;
	}

	public void setZahl(Integer zahl) {
		if(zahl==null){
			zahl=0;
		}
		System.out.println("setZahl: "+zahl);
		this.zahl = zahl;
	}
	
	
	// von hinzuanzeige.jsp
	public void setNeutext(String neutext) {
		this.neutext = neutext;
	}
	public void setNeuzahl(Integer neuzahl) {
		if(neuzahl==null){
			neuzahl=0;
		}
		this.neuzahl = neuzahl;
	}
	public void setUserID(Integer UserID) {
		System.out.println("setUserID :"+UserID);
		System.out.println("\n");
		this.UserID = UserID;
	}
	public List<tbspiel_t> getTabelle() {
		System.out.println("getTabelle\n");
		return tabelle;
	}
}
```


----------



## stephanm (3. Mai 2010)

Hast Du die Möglichkeit, mal die *ganze* Anwendung incl. aller Konfigurationsdateien, JSP-Seiten, Klassen, etc. irgendwo hochzuladen? Ich schau gerne mal drüber, aber erwarte nicht, dass Du die Anwendung wiedererkennst, nachdem ich das gemacht habe...


----------



## 7bkahnt (3. Mai 2010)

Das wär ja genial.
Danke schonmal im voraus.
Hier die Anwendung: Download testuser.zip, upload your files and earn money.


----------



## stephanm (3. Mai 2010)

Danke, habs grad runtergeladen...


----------



## stephanm (3. Mai 2010)

Also... Bei mir läuft das ganze jetzt mal prinzipiell auf Apache Tomcat 6.0.18 mit einer MySQL-Datenbank.

Allerdings musste ich einige Bibliotheken hinzufügen. Ist das ganze zufällig für den Einsatz auf einem Applikationsserver gedacht, bei dem Hibernate et al. schon "vorinstalliert" ist?!?

Datensätze anlegen kann ich und sehe mal großzügig darüber hinweg, dass die Applikation bei falschen Eingaben mit Exceptions um sich wirft 

Wenn ich die Liste sehe und bei einem Eintrag auf "Bearbeiten" oder "Löschen" clicke, dann kommt auch eine Exception, nämlich:

java.sql.SQLException: Fehler in delete()
        Nutzer.EingebenAction.delete(EingebenAction.java:273)
        .....

Ist das jetzt das Problem, dass Du meinst? Wenn nein, dann hätte ich bitte noch gerne eine Anleitung zum Reproduzieren des Fehlers, von dem Du hier sprichst.

Stephan


----------



## 7bkahnt (3. Mai 2010)

> Ist das ganze zufällig für den Einsatz auf einem Applikationsserver gedacht, bei dem Hibernate et al. schon "vorinstalliert" ist?!?


Genau dafür ist es gedacht.
Ich soll ein Post-und Verwaltungsprogramm webbasiert entwickeln, sodass der Benutzer nur noch sein Browser aufmachen muss und fertig. Damit man nicht ständig vorbeikommen muss um etwas zu installieren zwecks Updates usw.

Mhm komisch, also eigentlich funktioniert bei mir alles, aber eben nur mit einem Benutzer.
Also funktionieren tut es bei mir mit dem IE (aber eig. nur zwecks ActiveX zum Benutzernamen auslesen).
Die Tabellen hast du ja wahrscheinlich auch alle richtig angelegt und die Spaltennamen richtig deklariert, sonst würde ja das hinzufügen nicht funktionieren.
Mhmm..also ich verwende MyEclipse und damit auch den MyEclipse Tomcat.

Es würde mir aber auch helfen, wenn du mir ein kleines Beispiel zeigen könntest, wo 2 Nutzer gleichzeitig Datensätze hinzufügen können.
Das lässt sich dann ja für den Rest problemlos übernehmen denk ich.





> dann hätte ich bitte noch gerne eine Anleitung zum Reproduzieren des Fehlers, von dem Du hier sprichst.



Also du klickst auf Hinzufügen, dann erscheint ja die Maske. Dann gebe ich bei beiden Rechnern (Desktop und Laptop) irgentwelche Werte ein und klicke bei beiden gleichzeitig auf Hinzufügen.
Dann erscheint bei einem ein Fehler, bei dem zweiten klappt es, weil dort eben wahrscheinlich der Wert bei setText... genommen wurde


----------



## 7bkahnt (3. Mai 2010)

Jetzt habe ich es bei mir zuhause mal getestet, da funktioniert es nun auf einmal!
Ist nur die Frage ob es wirklich gleichzeitig abarbeitet, wenn ich gleichzeitig klicke.
Denn auf Arbeit hatte ich es mit Kollegen ausprobiert, da kam bei einem immer ein Fehler.
Wie sieht es bei dir aus mit gleichzeitigem hinzufügen?

Edit: Selbst mit 3 Rechnern funktioniert es jetzt, wenn ich "gleichzeitig" klicke.
Das einzige was ich jetzt hier anders gemacht habe, ist dass ich den Quellcode für dich ein wenig bereinigt und aufgeräumt habe und ein paar Actions (die zu Testzwecken noch existierten) entfernt habe.
Sehr komisch...


----------



## stephanm (3. Mai 2010)

Jetzt wo ich Deine Beschreibung gelesen habe, was die Anwendung können soll, blicke ich noch weniger durch als vorher. Was ist beispielsweise "webserverid"? Wozu die Eingabezeilen mit dem Namen 'Session-ID'? Warum packst Du die (Http)Session-ID in der anlegen.jsp in ein Hidden Input Field mit dem Namen 'usernr'? Warum haben die Tabellenspalten keine aussagekräftigen Namen? Warum haben die Properties der Klasse db.Vor_tbspiel_t keine aussagekräftigen Namen? Warum sehen bei Dir manche Klassen- und Methodennamen aus wie Aliens, die zufällig mit ihrem Raumschiff auf dem Planeten Java eine Bruchlandung hatten? Warum verwendest Du eine Wildcard in Deiner Struts-Konfiguration, wo es doch auch übersichtlicher und damit einfacher ginge?

Da das ganze ja auf einem Applikationsserver laufen soll, stellt sich auch noch die Frage: Warum keine EJBs? Warum nicht JPA mit Container Managed Persistence? (Ok, dass ist für den Anfang dann möglicherweise wirklich etwas viel, aber die Frage ist berechtigt!)

Zum Thema Gleichzeitigkeit: Du machst nirgends in Deiner Anwendung langwierige Operationen. Das bedeutet, dass zwischen "Gleichzeitig" und "Nacheinander" nur Zeiten im Millisekunden-Bereich liegen. Glaubst Du wirklich, man könne das gleichzeitige Absenden zweier HTTP-Requests zuverlässig mit zwei menschlichen Usern simulieren, die "gleichzeitig" auf einen Button clicken? Die Antwort heisst: Nein, kann man nicht. Daher sage ich Dir an dieser Stelle nochmal, dass Dein Problem nichts mit Gleichzeitigkeit zu tun hat, sondern dass in Deinem Code schlicht und ergreifend etwas viel elementareres faul ist.

Möglicherweise hat das was damit zu tun: Du hast immernoch eine statische Variable in Deiner Action, nämlich in Zeile 27:


```
private static Map<String,User> user = new HashMap<String,User>();
```

Da packst Du bei der Anmeldung (Methode DBVerbindung() in Deiner Action) ein "User"-Objekt hinein. Auch hier frage ich mich: Warum? Wozu? Weshalb?

Es macht leider wirklich überhaupt keine Freude, den Code nach dem Fehler (oder den Fehler*n*) durchzuschauen. Das ist alles ziemlich unzusammenhängend und zusammengewürfelt. Du solltest lernen, Code zu strukturieren und Frameworks sinnvoll und bestimmungsgemäß einzusetzen.



> Es würde mir aber auch helfen, wenn du mir ein kleines Beispiel zeigen könntest, wo 2 Nutzer gleichzeitig Datensätze hinzufügen können.



Nochmal: Google -> "Struts2 CRUD Example". Als drittes Suchergebnis kriege ich learntechnology.net - Struts2 CRUD Example - sieht für mich ganz passabel und auch anfängergeeignet aus. Allerdings würde ich selber nicht alles in eine Action packen (so wie z.B. Du das ja auch gemacht hast). Ich würde mindestens zwei Actions bauen: Eine für die Anzeige der Tabelle und eine für die CRUD-Operationen auf einzelne Datensätze.

Übrigens: Mach doch mal folgendes... Anmelden -> Neuen Datensatz hinzufügen -> so lange durchclicken, biss Du die Liste mit dem neu hinzugefügten Datensatz siehst. Dann drück mal F5, auch gerne mehrmals. Was passiert?!? Das Problem ist ein Klassiker, der von Anfängern im Bereich Webapplikationen sehr gerne übersehen wird.

Stephan


----------



## 7bkahnt (4. Mai 2010)

Die Textfelder der SessionID in den jsp´s sind zur eindeutigen Identifikation der Nutzer.(die später natürlich hidden sein würden)
Bsp.: Nutzer A loggt sich ein-->auf UserID wird seine ID geschrieben.
Nutzer B loggt sich ein-->auf UserID wird seine ID geschrieben.
Nutzer A bearbeitet nun einen DS und arbeitet mit der UserID von Nutzer B, da diese noch drauf steht.
Selbst jetzt wo ich private Variablen habe, kann ich nachdem sich Nutzer B eingeloggt hat, nicht weiterarbeiten, da auf UserID und userName dann der Wert null steht.

Deswegen arbeite ich mit der SessionID von jedem Nutzer (wusste nicht wie ich in der Actionklasse darauf zugreifen kann, deswegen in der jsp).
Da ich out.println(session.getId() nicht in einem struts-textfeld schreiben kann, nehm ich ein normales html-textfeld (usernr) und schreibe durch eine Funktion, das was im Textfeld "usernr" steht auf das struts-textfeld "webserverid".

Diese webserverid verwende ich dann in der Actionklasse als key für meine Hashmap "user" und speichere darin die UserID und den Usernamen.

Wenn jetzt sich also Nutzer A einloggt schreibt er in die Hashmap  zu der webserverid, die UserID und den Usernamen.
Beim bearbeiten fragt er dann ab, welche UserID zu der webserverid (in der Hashmap "user") gehört.
Kann sein, dass das etwas kompliziert gelöst ist, aber für mich war es so am logischsten.

Wildcard verwende ich, weil es für mich so aussieht, als wenn es mir einige Schreibarbeit abnimmt, indem ich nicht jeden einzelnen Methodenaufruf in der struts.xml protokollieren muss.
Jetzt sehe ich aber gerade in deinem Beispiel: "redirect-action" >getAllEmployees" in der struts.xml, was ich natürlich verwenden könnte, um nicht nach jeder Funktion die auslesen() aufrufen zu müssen.
Also werde ich wahrscheinlich doch das normale action-mapping verwenden.

Danke, dann werde ich mich mal mit Struts CRUD beschäftigen müssen um das Problem zu lösen.
Ich dachte nur, dass es jetzt funktionieren müsste, weil "gman" ja meinte, dass ich private Variablen verwenden soll und somit jeder seine "eigene" Variable bekommt, was ja eigentlich genau die Lösung des Problems wäre.
Aber es scheint so, als ob doch nicht jeder seine eigene bekommt, da nachdem sich Nutzer B eingeloggt hat und Nutzer A danach etwas bearbeiten möchte, auf UserID und userName der Wert null steht, obwohl dann dort ja eigentlich die Werte von Nutzer A stehen müssten.


----------



## stephanm (4. Mai 2010)

7bkahnt hat gesagt.:


> Die Textfelder der SessionID in den jsp´s sind zur eindeutigen Identifikation der Nutzer.(die später natürlich hidden sein würden)
> Bsp.: Nutzer A loggt sich ein-->auf UserID wird seine ID geschrieben.
> Nutzer B loggt sich ein-->auf UserID wird seine ID geschrieben.



Fangen wir mal vorne an. Eine Session-ID wird i.A. vom Webserver vergeben und identifiziert eine Benutzersitzung eindeutig. Bei Webanwendungen, die auf Tomcat laufen, trägt das Cookie den Namen "JSESSIONID" (schon mal gesehen oder gehört?). Funktionieren tut das so: Der Benutzer ruft eine Seite auf -> Server erzeugt Session mit zugehöriger ID und teilt dem Client per Cookie diese ID mit -> Client empfängt das Cookie und speichert es -> Client sendet bei jedem weiteren Request das Cookie (d.h. die Session-ID) zum Server wieder zurück, so dass der Server den Request der Benutzersitzung zuordnen kann.

Die Session lebt so lange, bis sie verfällt. Dazu stellt man eine Lebensdauer ein (z.B. 1 Stunde). Ist die Session verfallen, so sieht der Server zwar eine (nämlich die alte) Session-ID, hat aber keine Session-Daten mehr. In so einem Fall muss sich der Benutzer dann typischerweise neu anmelden.

Der Grund dafür ist, dass die Benutzerinformationen (d.h. die Antwort auf die Frage "Welcher Benutzer kommuniziert da mit mir?") serverseitig mit der Session zusammen oder gelegentlich sogar direkt in der Session gespeichert werden. Meldet sich der Benutzer an, wird auf dem Webserver eine Session erzeugt und dort in der Session werden Informationen zur Identifikation des Benutzers gespeichert, z.B. die ID (der Primary Key) des dem Benutzer zugeordneten Datensatzes in der Datenbank, vielleicht noch sein Username, Nachname, Vorname, etc. Je nachdem, was halt für die konkrete Anwendung von Bedeutung ist.

Die Alternative und bevorzugte Variante ist, containerseitiges AAA zu verwenden. Dann läuft das ganze nicht (oder nur teilweise) über die Session. Containerseitig (=serverseitig) werden dem Benutzer (dem "Subject" wie es dann in der Fachsprache heisst) dann ein oder mehrere Principals zugeordnet, wobei ein Principal den Benutzer identifiziert (z.B. über seinen eindeutigen Usernamen) und ggf. weitere Principals seine Zugehörigkeiten zu bestimmten Rollen.

Serverseitig hast Du über eine Instanz von "HttpSession" die Möglichkeit, beliebige Daten in der Session zu speichern (session.getAttribute(), session.setAttribute()). Die HttpSession-Instanz wird vom Container verwaltet und steht in JSP-Seiten über die implizite Variable "session" zur Verfügung. Um in einer Struts Action an die Session zu kommen muss die Action eines der Interfaces SessionAware oder ServletRequestAware implementieren, siehe Doku.

Das Konzept der Session im Bereich von Webanwendungen ist von grundlegender Bedeutung und wird nicht nur im Java-Bereich, sondern z.B. auch bei PHP-basierten Webanwendungen verwendet. Für einen Webanwendungsentwickler ist es ein *MUSS*, diese Konzepte zu kennen und im Rahmen der von ihm verwendeten Technologie anwenden zu können!



7bkahnt hat gesagt.:


> Nutzer A bearbeitet nun einen DS und arbeitet mit der UserID von Nutzer B, da diese noch drauf steht.
> Selbst jetzt wo ich private Variablen habe, kann ich nachdem sich Nutzer B eingeloggt hat, nicht weiterarbeiten, da auf UserID und userName dann der Wert null steht.



Ich verstehe nicht, was Du damit sagen willst.



7bkahnt hat gesagt.:


> Deswegen arbeite ich mit der SessionID von jedem Nutzer (wusste nicht wie ich in der Actionklasse darauf zugreifen kann, deswegen in der jsp).



Leider scheinst Du auch Begriffe an mehreren Stellen wild durcheinanderzuwürfeln. Welche Session-ID meinst Du jetzt? Die ID der (vom Container verwalteten) HttpSession?



7bkahnt hat gesagt.:


> Da ich out.println(session.getId() nicht in einem struts-textfeld schreiben kann, nehm ich ein normales html-textfeld (usernr) und schreibe durch eine Funktion, das was im Textfeld "usernr" steht auf das struts-textfeld "webserverid".



Noch so ein Workaround um fehlende Grundlagenkenntnisse zu kaschieren :-(



7bkahnt hat gesagt.:


> Diese webserverid verwende ich dann in der Actionklasse als key für meine Hashmap "user" und speichere darin die UserID und den Usernamen.



Dafür würde ich die HttpSession bevorzugen.



7bkahnt hat gesagt.:


> Wenn jetzt sich also Nutzer A einloggt schreibt er in die Hashmap  zu der webserverid, die UserID und den Usernamen.
> Beim bearbeiten fragt er dann ab, welche UserID zu der webserverid (in der Hashmap "user") gehört.



Genau dafür ist die HttpSession da.



7bkahnt hat gesagt.:


> Kann sein, dass das etwas kompliziert gelöst ist, aber für mich war es so am logischsten.



Ja, es ist kompliziert und es ist vor allem nochwas: Es ist total unsinnig.



7bkahnt hat gesagt.:


> Wildcard verwende ich, weil es für mich so aussieht, als wenn es mir einige Schreibarbeit abnimmt, indem ich nicht jeden einzelnen Methodenaufruf in der struts.xml protokollieren muss.



Du sparst am falschen Ende. Die paar Zeilen XML-Konfiguration, die Du ohne Wildcard brauchst, sind nicht der Rede wert. Dafür ist aber dann sogar schon in der struts.xml sauber aufgelistet, welche Actions mit welchen URLs verknüpft sind und welche Methoden welcher Action wann genau aufgerufen werden.

Es ist typisch für einen Anfänger, sowas zu machen. Ich habs ja auch nicht anders gemacht. Bis ich irgendwann mal festgestellt habe, dass solche Sachen ("Cool, Schreibarbeit sparen!" und "Cool, das sieht so ja voll Profigeil aus!") nur dazu führen, dass man spätestens sechs Wochen danach selber nicht mehr weiß, was man da eigentlich genau gemacht hat.



7bkahnt hat gesagt.:


> Jetzt sehe ich aber gerade in deinem Beispiel: "redirect-action" >getAllEmployees" in der struts.xml, was ich natürlich verwenden könnte, um nicht nach jeder Funktion die auslesen() aufrufen zu müssen.



Aha, jetzt kommen wir der Sache doch langsam einen Schritt näher, wie ich sehe 



7bkahnt hat gesagt.:


> Also werde ich wahrscheinlich doch das normale action-mapping verwenden.



Ja, und schreib doch bitteschön diesmal alles einfach sauber hin. Am Ende hast Du vielleicht 50-100 Zeilen XML. Das ist aber tausendmal übersichtlicher als irgendwelche Getrickse mit Wildcards.



7bkahnt hat gesagt.:


> Danke, dann werde ich mich mal mit Struts CRUD beschäftigen müssen um das Problem zu lösen.
> Ich dachte nur, dass es jetzt funktionieren müsste, weil "gman" ja meinte, dass ich private Variablen verwenden soll und somit jeder seine "eigene" Variable bekommt, was ja eigentlich genau die Lösung des Problems wäre.



Die Lösung für Deine Probleme liegt darin, Dir mehr Grundlagen- und Fachwissen anzueignen. Nochmal mein Tip: Arbeite Beispiele durch und schau dass Du verstehst, was dort im Einzelnen passiert. Mir hat es immer geholfen, die Beispiele schrittweise abzutippen und dabei parallel die Doku zu lesen, um so den zwangsläufig auftretenden Fragen schnellstmöglich auf den Grund zu gehen. So gewinnt man auch einen guten Eindruck davon, wie man mit den ganzen eingesetzten Frameworks, Toolkits und Komponenten umgehen soll - man hat ja selber schon mal Code hingeschrieben. Auch wenns am Anfang mehr abschreiben als selbst entwickeln ist, stellt sich dabei doch auch mit der Zeit eine gewisse Routine ein, die dann, wenn man anfängt, eigene Ideen umzusetzen, ausgesprochen nützlich ist.



7bkahnt hat gesagt.:


> Aber es scheint so, als ob doch nicht jeder seine eigene bekommt, da nachdem sich Nutzer B eingeloggt hat und Nutzer A danach etwas bearbeiten möchte, auf UserID und userName der Wert null steht, obwohl dann dort ja eigentlich die Werte von Nutzer A stehen müssten.



"Es scheint", "müsste", ... Fällt Dir was auf? 

Stephan


----------



## 7bkahnt (4. Mai 2010)

> Zitat: 7bkahnt
> Deswegen arbeite ich mit der SessionID von jedem Nutzer (wusste nicht wie ich in der Actionklasse darauf zugreifen kann, deswegen in der jsp).
> 
> 
> > Leider scheinst Du auch Begriffe an mehreren Stellen wild durcheinanderzuwürfeln. Welche Session-ID meinst Du jetzt? Die ID der (vom Container verwalteten) HttpSession?



Genau die! Die rufe ich ja immer in der jsp mit out.println(getsessionId()); auf und setze die dann auf die webserverid in der Actionklasse, weil ich eben nicht wusste ob bzw wie ich in der Actionklasse direkt darauf zugreifen kann über request.... o.ä.

Das CRUD-Beispiel kann ich nicht so richtig nachvollziehen. Eigentlich hab ich doch genau dasselbe, außer eine form.jsp und die einzelne Unterteilung in Klassen, die ich später in der richtigen Anwendung dann auch vornehmen will.


Soll es wirklich daran liegen, dass der Mehrbenutzerbetrieb nicht funktioniert, weil ich keine DAO-Klasse habe und ich die Methoden darin in der Action aufrufe?

Hast du vielleicht irgendwo ein funktionierendes Beispiel noch rumliegen, an dem ich die Materie vielleicht etwas besser verstehen kann?


----------



## stephanm (4. Mai 2010)

7bkahnt hat gesagt.:


> Das CRUD-Beispiel kann ich nicht so richtig nachvollziehen. Eigentlich hab ich doch genau dasselbe, außer eine form.jsp und die einzelne Unterteilung in Klassen, die ich später in der richtigen Anwendung dann auch vornehmen will.



Im Grunde ist es schon das selbe, aber man muss natürlich auch sagen, dass die meisten CRUD-Beispiele das Thema Anmeldung aussen vor lassen.



7bkahnt hat gesagt.:


> Ich habe noch ein anderes Tutorial dazu gefunden:
> Struts 2 CRUD Example ,welches eigentlich gut erklärt ist, jedoch es bei mir nicht funktioniert.
> Fehler"Cannot open connection". Bin aber noch am rumbasteln bzwl jdbc treiber etc.
> Irgendwie kommt man sich dann doch etwas dumm vor, wenn man nicht einmal ein einfaches Tutorialprogramm zum laufen bekommt



Du kannst nicht erwarten, dass ein x-beliebiges Beispiel sofort auf Deinem Rechner läuft. Da sind einfach zu viele Komponenten im Spiel (Applikationsserver/Servlet Container, Datenbank) die an vielen Stellen konfiguriert werden müssen.



7bkahnt hat gesagt.:


> Soll es wirklich daran liegen, dass der Mehrbenutzerbetrieb nicht funktioniert, weil ich keine DAO-Klasse habe und ich die Methoden darin in der Action aufrufe?



Nein, natürlich nicht. Das ist ein rein strukturelles Problem.

Bei Dir hingegen kommen mehrere unschöne Sachen zusammen, z.B.

- Du hast alles in eine Action gepackt
- Du hast vieles ziemlich kompliziert, umständlich und völlig fernab der Norm gelöst.
- Dir fehlen Grundlagenkenntnisse

Es ist immer schön, wenn der Code eine gewisse Struktur hat, denn dann hat man üblicherweise kleine Häppchen, die man anschauen kann und wo man sehr schnell sehen kann, was schief läuft. Packst Du hingegen alles irgendwo in eine Klasse und "kürzt" dann auch noch die Konfiguration zusammen, dann wird das Nachvollziehen von Aktionen im Sourcecode und damit auch die Analyse und Fehlersuche erheblich erschwert.

Es spricht doch nichts dagegen, sich gerade als Anfänger an offensichtlich gängige Muster zu halten: EAOs, Actions aufteilen, Actions sauber und explizit (ohne Wildcard) in der struts.xml zu konfigurieren. Wenn etwas nicht funktioniert hättest Du *dann* die Möglichkeit, Deine kleinen Häppchen mit den kleinen Häppchen des Beispiels zu vergleichen.

Im jetzigen Zustand musst Du ein strukturiertes Beispiel mit dem vergleichen, was Du erschaffen hast: Ein wüstes Monster! ;-) Das dieser Vergleich und damit die Fehlersuche nicht so recht hinhaut verstehe ich gut.

Ich erneuere damit an dieser Stelle meinen Vorschlag: Fang nochmal von vorne an. Orientiere Dich an den Beispielen und versuche, dem ganzen von Anfang an eine Struktur zu geben. Lass Dich nicht auf umständliche Eigenkonstrukte ein, nur weil Du nicht weist, wie Du z.B. Zugriff auf die HttpSession bekommst. Für sowas gibt es Lösungen und Beispiele. Und nicht zu letzt auch Foren wie dieses hier, wo Dir bei solchen konkreten Fragen mit Sicherheit jemand helfen kann.



7bkahnt hat gesagt.:


> Hast du vielleicht irgendwo ein funktionierendes Beispiel noch rumliegen, an dem ich die Materie vielleicht etwas besser verstehen kann?



Das Problem ist: Ich habe keine Ahnung, auf welchem Application Server Deine Anwendung laufen soll. Ich selber arbeite privat mit Apache Geronimo oder einfach nur mit Tomcat. Und statt Oracle schuftet bei mir eine MySQL-Datenbank. Ein Beispiel, dass bei Dir out-of-the-box läuft, kann ich Dir nicht liefern.

Warum fängst Du eigentlich überhaupt mit einem Application Server im Rücken an und nicht mit was einfacherem wie z.B. einfach nur einem Tomcat? Du benutzt doch eh keine EJBs und kein JPA/CMP...

Ich bin Dir gerne behilflich dabei, das ganze nochmal von vorne aufzurollen. Das Problem ist nur die fehlende gemeinsame Platform (Welcher Servlet Container/Application Server? Welche Datenbank? Welche Entwicklungsumgebung?).

Stephan


----------



## 7bkahnt (4. Mai 2010)

Also erst nocheinmal DANKE für die tolle Unterstützung.
Ich werde heute noch das CRUD-Tutorial durchgehen und morgen alles neu strukturiert aufsetzen.



> Lass Dich nicht auf umständliche Eigenkonstrukte ein, nur weil Du nicht weist, wie Du z.B. Zugriff auf die HttpSession bekommst. Für sowas gibt es Lösungen und Beispiele. Und nicht zu letzt auch Foren wie dieses hier, wo Dir bei solchen konkreten Fragen mit Sicherheit jemand helfen kann.




Hast du vielleicht ein solches Beispiel, wie ich auf die session zugreifen kann?
Da seh ich als einzigstes noch das Problem, wie ich das anders lösen könnte.
Weil es ist ja  so, dass sich jeder Nutzer einloggt und am Ende z.B. beim Bearbeiten erkannt werden muss, welcher Nutzer welche UserID hat.

Ich benutze übrigens den Tomcat von MyEclipse, demzufolge auch die Entwicklungsumgebung MyEclipse und eine Oracle-Datenbank.

grüsse


----------



## stephanm (4. Mai 2010)

7bkahnt hat gesagt.:


> Hast du vielleicht ein solches Beispiel, wie ich auf die session zugreifen kann?
> Da seh ich als einzigstes noch das Problem, wie ich das anders lösen könnte.



Prinzipiell geht das z.B. mit dem SessionAware-Interface, das sieht dann grob so aus:


```
import java.util.Map;

import org.apache.struts2.interceptor.SessionAware;

import com.opensymphony.xwork2.ActionSupport;

public class MyTestAction extends ActionSupport implements SessionAware {
	private static final long serialVersionUID = 1L;
	
	private Map<String, Object> session;
	
	@Override
	public String execute() throws Exception {
		...
		Type var = (Type)session.get(SESSION_KEY);
		...
		session.put(OTHER_KEY, otherValue);
		...
	}
	
	@Override
	public void setSession(Map<String, Object> session) {
		this.session = session;
	}

}
```

Struts hat einen Interceptor, der dafür sorgt, dass die setSession()-Methode einer Action, die das SessionAware-Interface implementiert, aufgerufen wird, bevor execute() o.ä. ausgeführt wird. Das bedeutet: Wenn execute() läuft, ist die Membervariable 'session' initialisiert.

Eine andere Möglichkeit ist mittels des Interfaces ServletRequestAware. Dann kriegst Du das HttpServletRequest-Objekt in Deine Action und hast direkten Zugriff auf die HttpSession-Instanz mittels request.getSession().



7bkahnt hat gesagt.:


> Weil es ist ja  so, dass sich jeder Nutzer einloggt und am Ende z.B. beim Bearbeiten erkannt werden muss, welcher Nutzer welche UserID hat.



Ja, da sind wir wieder bei dem Problem mit der Anmeldung. Vielleicht solltest Du als allererstes versuchen, Deinem Tomcat beizubringen, Deine User mittels Container Managed Security zu authentifizieren und zu authorisieren. Auch da helf ich gerne, lediglich die Oracle-spezifischen Sachen musst Du selber hinbiegen. Ich denk wir kriegen das hin 

Hast Du eigentlich das Datenbankschema in der Hand oder ist das fest vorgegeben?

Stephan


----------



## 7bkahnt (4. Mai 2010)

Ich bekomme das Datenmodell vorgegeben. 5 6 Tabellen die in Beziehung zueinander stehen.
Danke dir für das Beispiel. Hat mir sehr geholfen. Das Crud-Tutorial hab ich jetzt auch verstanden.
Mal sehen was morgen dann die Umsetzung mit sich bringt .
Schönen Abend noch!

grüsse


----------



## 7bkahnt (5. Mai 2010)

So also jetzt hab ich es fast fertig. 
Hier mal das Programm: Download testuser_neu.zip, upload your files and earn money.

Es funktioniert bisher alles bis auf die UserID auslesen und eintragen beim Bearbeiten.
Mein Problem ist, dass ich mit 
User user = (User) session.get(User.class, userName);
eigentlich das Objekt/den Datensatz bekommen will, indem der userName steht.
Aber ich glaube, das funktioniert nur mit der id, ein Objekt so zu bekommen.
Nur die nützt mir hier ja leider nix. 
Eigentlich habe ich das Problem dann anders gelöst, wie du sehen wirst (in der UserDAOImpl), nur ich kann dann in der struts.xml nicht auf die listCrud() zurück verlinken , weil ein redirect ja nicht funktioniert,da vergleichUser() in der Useraction ist und listCrud in der Crudaction.(letzte Zeile in der struts.xml)
Weißt du da zufällig eine Lösung wie ich aus einer action in eine Andere redirecten kann?

Um das Programm ohne das auslesen der UserID auszuprobieren musst du in der web.xml auf index2.jsp umändern.
Ansonsten startet er mit index.jsp, wo ich dann den Login schon mit mache.



Ansonsten sind mir noch ein paar Schönheitsfehler aufgefallen, die dir bestimmt als Erstes ins Auge fallen:

1.
in changeanzeige.jsp


```
<s:hidden name="id" />
<s:hidden name="datum_c" />
```

damit beim ändern diese Werte beibehalten werden.
Wenn ich das hiddenfeld datum_c weglasse, schreibt er beim ändern nichts in die Spalte datum_c (vom Erstellen) rein, weil er kein Wert übergeben bekommt.
Wenn ich das hiddenfeld id weglasse, legt er einen neuen Datensatz an, anstatt den zu ändern, weil er wahrscheinlich sieht, dass keine id mit übergeben wurde und er somit die sequence einsetzt und neu erstellt.
Hab ich da irgendeine Alternative?
Ich mein zur Not lass ich es drin, aber das könnte ziemlich umfangreich werden auf Dauer.



2.
	
	
	
	





```
HttpServletRequest request = (HttpServletRequest) ActionContext.getContext().get(ServletActionContext.HTTP_REQUEST);
```
Ich benutze es zwar, aber so richtig verstanden habe ich es ehrlich gesagt nicht, wann er überhaupt etwas in das ActionContext schreibt
und was er in dieser Zeile überhaupt rausholt.
Liege ich richtig mit der Vermutung, dass immer wenn ein Benutzer eine Aktion durchführt z.B. hinzufügen klickt, dass er seine Werte (sessionid,die ganzen parameter wie id,text255 und zahl6...) 
in ein request packt, und dieses dann in den ActionContext schreibt??

3.
Wozu ist das @Override?
Da wird ja irgendwie immer die Methode wieder überschrieben oder wie kann ich das verstehen?
Ich hatte auch gelesen, dass es kein Muss ist, override zu verwenden.

Genau wie @SuppressWarnings("unchecked").
Warum nehm ich das manchmal und warum manchmal nicht?

4.
Was macht denn in den Actions die 

@Override
	public User getModel() {
		return user;
	}
??
Die muss auch vorhanden sein, damit kein Fehler bei der Definition der Klassen UserAction bzw CrudAction erscheint!


Sorry für die vielen Fragen, du musst natürlich nicht alle beantworten


----------



## stephanm (6. Mai 2010)

7bkahnt hat gesagt.:


> So also jetzt hab ich es fast fertig.
> Hier mal das Programm: ...



Hey, Respekt! Das sieht auf den ersten Blick schon viel besser aus als die vorherige Version. Super! :toll:



7bkahnt hat gesagt.:


> Es funktioniert bisher alles bis auf die UserID auslesen und eintragen beim Bearbeiten.
> Mein Problem ist, dass ich mit
> User user = (User) session.get(User.class, userName);
> eigentlich das Objekt/den Datensatz bekommen will, indem der userName steht.
> ...



So, ich mach das mal der Reihe nach:

*UserAction:*

1. Statt über den ActionContext auf den Request zuzugreifen sollte die Action das Interface RequestAware implementieren.
2. ModelDriven<User> kannst Du Dir sparen. Die Action kriegt aus dem Formular aus der index.jsp lediglich den Benutzernamen (Parameter username). Dass Du den über HttpServletRequest.getParameter() abfragst zeigt mir, dass Du nicht weißt, dass Struts2 Request-Parameter automatisch und JavaBeans-konform auf Actions abbildet. Ohne die index.jsp zu ändern sollte Deine UserAction in Etwa so aussehen:


```
public class UserAction extends ActionSupport implements SessionAware {
	private static final long serialVersionUID = 1L;

	private UserDAO userDAO = new UserDAOImpl();
	private Map<String, Object> session;

	private String userName;
	
	// TODO Umbenennen in execute()
	public String vergleich()
	{	
		// User authentifizieren
		User user = userDAO.getUserByName(userName);
		
		// Gültiger Username?
		if (user == null) {
			// z.B. sowas wie:
			throw new AuthenticationFailedException();
		}
		
		// User-Objekt in Session speichern
		session.put("userObject", user);
		
		// Statt dem Literal "SUCCESS" hier eine String-Konstante (aus ActionSupport) verwenden!
		return SUCCESS;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}
	
	@Override
	public void setSession(Map<String, Object> session) {
		this.session = session;
	}
}
```

Wird das Formular in der index.jsp zum Server gesandt ("submitted"), so sieht einer der Interceptors von Struts2, dass der Client einen Parameter namens "userName" mitgesandt hat. Dieser Interceptor ruft dann deineActionInstanz.setUserName(wertDesParameters) auf - so kriegt Deine Action den Benutzernamen mittels eines Setters übergeben.

Das mit dem Interceptor der setSession() auf die Action aufruft hatten wir schon geklärt. (Oder lies doch endlich mal die olle Doku *bittebittebitte*) 

Was passiert nun bei meinem Vorschlag in execute() - äh, vergleich(), wie Du es nanntest: Das UserDAO wird gebeten, das User-Objekt anhand des Benutzernamens zu laden (die Methode hiess bei Dir "vergleichUser" - das passt halt leider mal garnicht, wenn Du Dir die Implementierung im UserDAOImpl ansiehst...!). Wenns den User nicht gibt, dann fliegt er raus - hier mangels eines echten, harten Container-seitigem Logins halt per geschmissener Exception. Das dient aber nur als Beispiel für diesen Fall und muss nicht so umgesetzt werden.

Dann kommt der große Moment: Das User-Objekt wird in die session gespeichert. Und hier hast Du glaub ich auch noch ein Verständnisproblem. Aaaaalso. Das "session"-Objekt (also hier die Map<String, Object> in Deiner Action, die Du per SessionAware über den Setter setSession() kriegst) ist für jeweils eine Benutzersitzung eindeutig. Das bedeutet im Klartext:

Für die Sitzung mit der JSESSIONID=1234... gibt es ein eigenes Session-Objekt
Für die Sitzung mit der JSESSIONID=5678... gibt es ein eigenes Session-Objekt
Für die Sitzung mit der JSESSIONID=9ABC... gibt es ein eigenes Session-Objekt
und so fort...

Der Container sorgt automatisch dafür, dass Du genau das Session-Objekt erhältst, das zur jeweiligen Benutzersitzung gehört. Bitte schau Dir dieses Konzept an, das ist wichtig!

Genau so wie Du in Deiner UserAction auf die Session zugreifst und dort ein Key-Value-Paar - nämlich in meinem Code das KV-Paar ("userObject", instanzDesUserObjects) - mit session.put(key, value) hinzufügen kannst, kannst Du es in anderen Actions auch auslesen - mit session.get()!

Die von Struts zur Verfügung gestellte Map<String, Object> ist übrigens im Wesentlichen eine Fassade für HttpSession.getAttribute() und HttpSession.setAttribute(). Alles was ich hier sage gilt also für "das Struts-Session-Objekt" (d.h. die Map<String, Object>) genauso wie für das HttpSession-Objekt, dem man im nativen Servlet-Umfeld öfters begegnet.

Es fällt übrigens an meinem Vorschlag für die UserAction auf, dass ich dort keine Servlet- oder JSP-spezifischen Klassen verwende: Ich benutze aus guten Gründen Struts' Map<String, Object> statt der HttpSession oder dem HttpServletRequest. Der Grund ist einfach: Meine Action-Implementierung basiert auf "gängigen" Java-Klassen. Sie lässt sich auch in einer Umgebung verwenden, die nichts mit einer Webanwendung zu tun hat. Ein Unit-Test wäre ein schönes Beispiel für eine Verwendung der Klasse in einer nicht-Web-Umgebung!

Es ist zwar nicht zwingend notwendig, dass so zu machen wie im letzten Abschnitt gesagt, dieses Vorgehen hat aber gewisse Vorteile und kaum Nachteile. Daher macht man sowas gerne. Du hast ja z.B. auch - und das finde ich sehr schön! - Interfaces implementiert und diese auch konsequent verwendet. Da ist's ist ähnlich. Also: Super gemacht, weiter so!



7bkahnt hat gesagt.:


> Eigentlich habe ich das Problem dann anders gelöst, wie du sehen wirst (in der UserDAOImpl), nur ich kann dann in der struts.xml nicht auf die listCrud() zurück verlinken , weil ein redirect ja nicht funktioniert,da vergleichUser() in der Useraction ist und listCrud in der Crudaction.(letzte Zeile in der struts.xml)
> Weißt du da zufällig eine Lösung wie ich aus einer action in eine Andere redirecten kann?



Das geht mit dem "redirectAction"-Result von Struts2:

[XML]
<action ...>
    <result name="success" type="redirectAction">
        <param name="namespace">/addressbook</param>
        <param name="actionName">search</param>
    </result>
</action>
[/XML]

Den Parameter mit dem Namespace kannst Du im vorliegenden Fall weglassen, weil Du keine Namespaces benutzt. (Steht übrigens alles in der Struts Doku...)

So, jetzt zum *UserDAOImpl:*

Wenn ich jetzt sagen würde, ich wär fast vom Stuhl gefallen, als ich gesehen habe, was da drin steht, dann ist das nicht etwas übertieben, sondern - ES WAR TATSÄCHLICH FAST SO! ARGH! :shock:

Bis auf drei Zeilen ist dort alles ok. Aber die drei Zeilen hier:


```
HttpServletRequest request = (HttpServletRequest)ActionContext.getContext().get(ServletActionContext.HTTP_REQUEST);
String sid=request.getSession().getId();
...			
request.setAttribute(request.getSession().getId(), userid);
```

die habens in sich.

Wir haben hier ein DAO, d.h. ein Data Access Object. Schon der Name sagt, was hier Programm ist: Datenbankzugriffe kapseln. Nichts mehr und nichts weniger. Komponenten aus dem Frontend - z.B. HttpServletRequest, HttpSession - haben dort nicht, wirklich gaaaarnichts verloren!

In meiner Version von der UserAction oben hab ichs doch schon angedeutet: Lass das DAO nichts, wirklich nichts mehr machen als "Datensatz holen" und verwandte Operationen. Der Rest passiert wo anders. Dazu gibst Du einfach den geladenen User-Datensatz als Rückgabewert zurück.

Auf diese Art sind die Zuständigkeiten sauber verteilt: Das DAO macht was es soll und die Action kümmert sich um die Interaktion mit dem Benutzer. Und so soll es auch sein: Wir wollen Zuständigkeiten verteilen und saubere Strukturen, weil wir nur so wiederverwendbare Komponenten haben!

Hypothetisches Beispiel in einer gewachsenen und komplexer gewordenen Nachflgerapplikation, die auf Deinem Code basiert: User1 meldet sich an. User1 möchte die Detailinformationen zu dem Benutzer (nennen wir ihn User2) sehen, der einen Datensatz bearbeitet hat. Frage: Kannst Du mit DEINER Implementierung den Datensatz für User2 aus der DB laden? Antwort: Ja, Nein, Ja doch, ne eher nicht, ja was denn jetzt? Deine Methode im DAO hat Seiteneffekte, sie verändert die Attribute des HttpServletRequest, jedesmal wenn sie aufgerufen wird. Je nachdem wie die Applikation aussieht kann das schlimme Auswirkungen haben. Das zu prüfen ist dann an dieser Stelle der Entwicklung der Applikation möglicherweise eine Scheiss langweilige und Scheiss langwierige Aufgabe.

Meine Implementierung hingegen ist sicher. Sie hat keine solchen Seiteneffekte. Sie ist - hier kommt das Buzzword, auf dass ihr alle sicherlich schon gewartet habe - *wiederverwendbar!*

(Ich will hier mein Graffel nicht über den Morgen loben, siehs einfach so: Ich erlaub mir hier mal diese ganzen abgedroschenen Scherze, in der Hoffnung, sie zeigen - und erklären - Dir ein paar Verbesserungsmöglichkeiten. Mein Hauptargument an dieser Stelle, warum Du es so machen solltest, ist: Mein Vorschlag ist nicht komplizierter und auch nicht länger als Deiner, aber er ist einfach ein stückchen strukturierter, ein bisschen sauberer, zwar ein klein wenig abstrakter, dafür aber auch ein winziges Stückchen wiederverwendbarer...)

Puh, so viel Text... Hilfts Dir wenigstens etwas?

Egal, weiter. Ab jetzt aber etwas weniger ausführlich...



7bkahnt hat gesagt.:


> in changeanzeige.jsp
> 
> 
> ```
> ...



Erwartest Du wirklich ein anderes Verhalten?  Wo kein Wert übergeben wird, kann auch ein so mächtiges Toolkit wie Hibernate nichts aus dem Hut zaubern.



7bkahnt hat gesagt.:


> Wenn ich das hiddenfeld id weglasse, legt er einen neuen Datensatz an, anstatt den zu ändern, weil er wahrscheinlich sieht, dass keine id mit übergeben wurde und er somit die sequence einsetzt und neu erstellt.
> Hab ich da irgendeine Alternative?



Ja, bestimmt gibt es eine Alternative, und zwar getreu dem Motto der Programmiersprache Perl: There's more than one way to do it.

Aber zurück zur Deinem Problem. In den vielen einfachen Beispielen zu CRUD erkennt man meist ein ganz bestimmtes Schema: Die Aktionen, die beim Anlegen eines Datensatzes passieren, ähneln den Aktionen, die beim Ändern des Datensatzes passieren, fast aufs Haar: In beiden Fällen passiert mit dem Objekt, das in die Datenbank geschrieben wird, am Ende ein session.saveOrUpdate(). (Deine Implementierung von CrudDAOImpl.saveCrud und CrudDAOImpl.updateCrud ähneln sich beispielsweise auch sehr stark  - man könnte beide Methoden leicht zu einer CrudDAOImpl.saveOrUpdate()-Methode zusammenfassen, wenn man die Leerzeilen bereinigt.... *g*)

Woher weiß Hibernate nun, ob ein INSERT- oder ein UPDATE-Statement ausgeführt werden soll? Sehr vereinfacht (und leider auch nicht ganz korrekt) gesagt ist der Unterschied zwischen Neuanlage und Bearbeiten doch der: Bei der Neuanlage hat das Objekt noch keinen gültigen Primärschlüssel (woher auch, den lassen wir wegen @GeneratedValue ja die DB erzeugen, und dort war das Objekt noch nie!) während beim editieren ein gültiger Primärschlüssel bereits vorhanden ist (das Objekt war ja schon mal in der DB!).

Meist bedeutet gültig dann id > 0 und ungültig id == 0.

Also läuft die Sache nun so ab (hier am Beispiel eines Adressbucheintrags):

Neuanlage: Link "Neuer Eintrag anlegen" geht auf /addressbook/edit.action?id=0 -> EditAction läuft und zeigt edit.jsp mit HTML-FORM mit <input type="hidden" name="id" value="0" /> an -> FORM Submit auf /addressbook/save.action?id=0&displayName=Herrman%20Maier&phoneNumber=0-555-666 -> SaveAction läuft -> session.saveOrUpdate() sieht Objekt mit id=0 -> INSERT ...

Bearbeiten eines vorhandenen Datensatzes: Link "Bearbeiten >>" zeigt auf /addressbook/edit.action?id=1234 -> EditAction und zeigt edit.jsp mit HTML-FORM mit <input type="hidden" name="id" value="1234" /> an -> FORM Submit auf /addressbook/save.action?id=1234&Herrman%20Müller&phoneNumber=0-555-666 -> session.saveOrUpdate() sieht Objekt mit id=1234 -> UPDATE ... WHERE id=1234



7bkahnt hat gesagt.:


> Ich mein zur Not lass ich es drin, aber das könnte ziemlich umfangreich werden auf Dauer.



Tja, so ist das halt. Gewöhn Dich an das Hin und Her von Daten zwischen Server und Client.




7bkahnt hat gesagt.:


> ```
> HttpServletRequest request = (HttpServletRequest) ActionContext.getContext().get(ServletActionContext.HTTP_REQUEST);
> ```
> Ich benutze es zwar, aber so richtig verstanden habe ich es ehrlich gesagt nicht, wann er überhaupt etwas in das ActionContext schreibt und was er in dieser Zeile überhaupt rausholt.



Wenn eine Action Zugriff auf den HttpServletRequest benötigt, dann sollte sie das RequestAware-Interface implementieren. DAOs und Klassen aus der Logikschicht sollten NIE Zugriff auf solche Objekte benötigen (s. oben), daher stellt sich die Frage nicht.

Zu Deinem Verständnisproblem: Das Geheimnis dürfte ein ThreadLocal<T> sein, irgendwo schön versteckt und gaaanz weit unten drinnen. An Deiner Stelle würde ich mir diese Frage(n) für später aufheben und mich jetzt erst mal um andere Sachen kümmern.

Bitte nimm die Vorschläge aus den letzten beiden Absätzen ernst. Wie Du siehst, es gibt andere - strukturelle und programmiertechnische - Vorschläge, mit denen man den Zugriff über den ActionContext umgehen kann. Bring an dieser Stelle nicht wieder eine unnötige Sauerei mit an Board, das hatten wir schon mal. 



7bkahnt hat gesagt.:


> Liege ich richtig mit der Vermutung, dass immer wenn ein Benutzer eine Aktion durchführt z.B. hinzufügen klickt, dass er seine Werte (sessionid,die ganzen parameter wie id,text255 und zahl6...)
> in ein request packt, und dieses dann in den ActionContext schreibt??



Puh, die ganzen Internas von Struts kenne ich nicht, aber es ist definitiv so, dass das Request-Objekt (genauer: Das HttpServletRequest-Objekt) am ANFANG steht, denn das wird vom Servlet Container erzeugt, wenn ein HTTP-Request vom Client (Browser) empfangen wurde. Struts kriegt dann dieses Objekt (in den StrutsPrepareAndExecuteFilter, vgl. web.xml) und baut anhand der Daten aus dem HttpServletRequest-Objekt seine internen Datenstrukturen so zusammen, dass die Maschine rund läuft und am Schluss Deine Action aufgerufen wird.

Übrigens hat das vom Container erzeugte HttpServletRequest-Objekt auch bereits eine Referenz auf die HttpSession - auch das wird vom Container automatisch verwaltet. Zu solchen Themen wirst Du wenig in der Struts Doku finden, da dass Bestandteil der Servlet API ist. 



7bkahnt hat gesagt.:


> Wozu ist das @Override?
> Da wird ja irgendwie immer die Methode wieder überschrieben oder wie kann ich das verstehen?
> Ich hatte auch gelesen, dass es kein Muss ist, override zu verwenden.



Google -> Erstes Ergebnis: Override (Java 2 Platform SE 5.0)



7bkahnt hat gesagt.:


> Genau wie @SuppressWarnings("unchecked").
> Warum nehm ich das manchmal und warum manchmal nicht?



Diesmal musst Du selber googlen 



7bkahnt hat gesagt.:


> Was macht denn in den Actions die
> 
> @Override
> public User getModel() {
> ...



Auch hinter ModelDriven<T> steckt ein Konzept. Welches das ist verrät Dir die Struts-Doku. An dieser Stelle sei vielleicht von mir folgendes gesagt: Spätestens wenn Du mit Dependency Injection arbeitest, brauchst Du für alle möglichen Sachen Setter-Methoden in den Actions. Und zwar auch für solche Sachen, die keine Request-Parameter sind. Damit man schneller sieht, welche Attribute einer Klasse Request-Parameter abbilden kann man ein Model einführen und die Action ModelDriven machen. Dann sieht man schneller, was als Request Parameter von aussen (vom Client) in die Action kommt und was über andere Wege: Im Model fasst man dann alle Request-Parameter (die ja z.B. nichts anderes als die Formularfelder eines HTML-FORMs sind) zusammen, in der Action selbst den Rest.

Man kann eine ModelDriven-Action auch "normal" bauen, dazu muss man halt das Model und die Action zusammenfassen. ModelDriven ist keine Pflicht, sondern eine Option. Und schon garnicht ist ModelDriven eine Sache für "Fortgeschrittene" wie hier mal irgendwo jemand gemeint hat. Die Idee ist so einfach wie ein Besenstiel.

So, dann hoffe ich, Dir ein bisschen weitergeholfen zu haben. Ich schau mit den Rest des Codes gerne noch weiter an, aber ich will Dich nicht davon abhalten, die Sachen selber in die Hand zu nehmen. Ich denke Du bist schon auf dem richtigen Weg!

Liebe Grüße,

Stephan


----------



## stephanm (6. Mai 2010)

Mir ist noch eine Sache eingefallen, die Dich verwirren könnte(?)



7bkahnt hat gesagt.:


> So also jetzt hab ich es fast fertig.
> Es funktioniert bisher alles bis auf die UserID auslesen und eintragen beim Bearbeiten.
> Mein Problem ist, dass ich mit
> User user = (User) session.get(User.class, userName);
> ...



In meinem vorhergehenden Beitrag hatte ich vorgeschlagen, das User-Objekt in die "Map<String, Object> session "mit


```
session.put("userObject", user);
```

zu speichern. Dabei ist folgendes zu beachten: "userObject" ist hier ein Key, der nur dazu da ist, dem Objekt user *innerhalb der Map* einem Namen zu geben. Du kannst das user-Objekt dann konkret mit


```
User user = (User)session.get("userObject");
```

abfragen.

Der Key "userObject" hat keinerlei besondere Bedeutung! Hier steckt keine Magie dahinter. Statt "userObject" könntest Du auch "Hotzenplotz" oder "yldkjyklfd" oder "Gurkensalat" als Key nehmen. Insbesondere hat der Key zum speichern des Objekts in der Map NICHTS mit der User-ID, dem Usernamen oder ähnlichem zu tun. Es ist nur ein Name, der zum wiederauffinden des Objekts in den (mehr oder weniger) unendlichen Weiten der Map dient, die der Servlet Container in den Sessiondaten für Dich speichert.

Ist es zufällig das, was Dich verwirrt hat?

Stephan


----------



## 7bkahnt (6. Mai 2010)

> Puh, so viel Text... Hilfts Dir wenigstens etwas?



TOP Sache! Hilft mir echt mehr als genug. Danke dir!:applaus:




> man könnte beide Methoden leicht zu einer CrudDAOImpl.saveOrUpdate()-Methode zusammenfassen



Naja ich dachte mir, ich will ja beim Anlegen eines Datensatzes die f_id_c_sb und dat_c anlegen und beim bearbeiten die f_id_a_sb und dat_a.
Deswegen dacht ich mir trenn ich es voneinander.




> session.put("userObject", user);
> Ist es zufällig das, was Dich verwirrt hat?



Ja das hat mich anfangs verwirrt, bis ich deine Antwort dazu gelesen hatte
Aber stimmt, das ist ja eigentlich einleuchtend, wenn sowieso jeder seine eigene session bekommt, dann ist der Key ja egal.
Was mir grad so auffällt. Warum hole ich mir eigentlich das komplette User-Objekt/den kompletten DS und nicht nur die UserID und speichere die in die session?
Hat das irgendeinen Vorteil?

Ich habe jetzt deine Tipps mit in mein Programm aufgenommen zwecks SessionAware usw.
Jetzt habe ich das User-Objekt zurück in der UserAction und möchte jetzt eigentlich die listCrud() aufrufen, damit ich nun nach dem Einloggen ersteinmal die Tabelle angezeigt bekomme.
Die listCrud() steht aber in einer anderen Action (CrudAction). Das hab ich aber noch nicht hinbekommen.

struts.xml:
[XML]<struts>
	<package name="default" extends="hibernate-default">
		<action name="saveCrud" method="save" class="actions.CrudAction">
			<result name="success" type="redirect">listCrud</result>
		</action>
		<action name="UpdateCrud" method="Update" class="actions.CrudAction">
			<result name="success" type="redirect">listCrud</result>
		</action>
		<action name="listCrud" method="list" class="actions.CrudAction">
			<result name="success">/register.jsp</result>
		</action>
		<action name="editCrud" method="edit" class="actions.CrudAction">
			<result name="success">/changeanzeige.jsp</result>
		</action>
		<action name="deleteCrud" method="delete" class="actions.CrudAction">
			<result name="success" type="redirect">listCrud</result>
		</action>
		<action name="hinzuCrud" method="hinzu" class="actions.CrudAction">
			<result name="success">hinzuanzeige.jsp</result>
		</action>

		<action name="vergleichUser" method="vergleich" class="actions.UserAction">
			<result name="success">/index2.jsp</result>
		</action>
	</package>
</struts>[/XML]

Die Letzten 3 Zeilen. Da bin ich ja in der UserAction. Ich hatte es mit dem redirectAction nicht hinbekommen, deswegen bin ich jetzt einen Umweg gegangen. In der index2.jsp steht einfach jetzt nur ein Refresh auf die Methode listCrud.action.

Oder weißt du wie ich das mit redirectAction lösen könnte? In der Doku und auf anderen Seiten steht immer nichts davon wie ich eine bestimmte Methode aufrufen kann. Das müsste ja irgendwie so aussehen:

[XML]
<action name="vergleichUser" method="vergleich" class="actions.UserAction">
  <result type="redirectAction">CrudAction</result>
  <result name="success">/register.jsp</result>
</action>
[/XML]

Fehlt eben nur noch der Teil, wie ich die Methode listCrud aufrufe.
In der register.jsp wird dann die Tabelle angezeigt.

Viele Grüsse
ben


----------



## stephanm (6. Mai 2010)

7bkahnt hat gesagt.:


> Naja ich dachte mir, ich will ja beim Anlegen eines Datensatzes die f_id_c_sb und dat_c anlegen und beim bearbeiten die f_id_a_sb und dat_a.
> Deswegen dacht ich mir trenn ich es voneinander.



Das ist prinzipiell nicht ganz verkehrt. Es gibt aber wiederum eine andere Möglichkeit, die sich hier anbietet: Lass das DAO nur das reine laden, speichern, ... der Objekte machen. Für das ausfüllen von Datenfeldern ist es zweckmässig, das in die Logikschicht zu verlagern. Das würde in Deinem Fall bedeuten, dass eine weitere Klasse ins Spiel kommt.

Als Beispiel hier mal der hypothetische Fall einer Adressbuchverwaltung, die folgende Anforderungen erfüllt:
- Ein Adressbucheintrag kann mehrere E-Mail-Adressen referenzieren, aber maximal 3.
- E-Mail-Adressen müssen eindeutig sein (eine E-Mail-Adresse darf nur einmal in der Datenbank auftreten und kann nur einem einzigen Adressbucheintrag zugeordnet sein), so dass z.B. zwei Adressbucheinträge können nicht die selbe E-Mail-Adresse enthalten
- Beim Hinzufügen einer E-Mail-Adresse wird gespeichert, welcher Bearbeiter die E-Mail-Adresse zum Adressbucheintrag hinzugefügt hat und zu welchem Zeitpunkt das geschehen ist (damit man es z.B. in einem Unternehmensaddressbuch nachvollziehen kann, wer wann welchen Datensatz bearbeitet hat)


```
public class AddressbookManagerImpl implements AddressbookManager {
    public final static int MAX_EMAIL_ADDRESSES_PER_ADDRESSBOOK_ENTRY = 3;

    public void addEmailAddressAndSave(User authenticatedUser, AddressbookEntry entry, EmailAddress emailAddress) throws ... {
        if (entry.getEmailAddressCount() >= MAX_EMAIL_ADDRESSES_PER_ADDRESSBOOK_ENTRY) {
            throw new AddressbookManagerException("Cannot add another email address to addressbook entry");
        }

        // Wir prüfen hier, ob die E-Mail-Adresse bereits vorhanden ist
        Collection<AddressbookEntry> existingEntry = addressbookEntryDAO.getAddressbookEntryByEmailAddress(emailAddresss);

        if (existingEntry != null) {
            throw new EmailAddressAlreadyPresentException("A (probably different) addressbook entry already contains the specified email address", existingEntrty);
        }

        // E-Mail-Adresseintrag vervollständigen
        emailAddress.setAddedBy(authenticatedUser);
        emailAddress.setAddedWhen(TimestampUtils.getNow());

        // Alles ok, wir können die E-Mail-Adresse zum Addressbucheintrag hinzufügen
        entry.addEmailAddress(emailAddress);
        
        // Änderungen speichern
		addressbookDAO.save(entry);

		return;
    }

    ... und so weiter ...
}}
```

Dieses Beispiel für eine "Manager"-Klasse benutzt mehrere DAOs (nämlich addressbookEntryDAO und addressbookDAO) und überlässt die Details, wie Datensätze geladen und gespeichert werden, voll und ganz diesen. Sie prüft ein letztes oder weiteres mal, ob alle formalen Kriterien (die im oben kurz dargestellten Pflichtenheft festgelegt wurden) erfüllt sind, um die Aktion ausführen zu können und stellt wie angefordert wurde sicher, dass vermerkt wird, wer (authenticatedUser) und wann (TimestampUtils.now()) die Änderung des Datensatzes bewirkt hat.

Ich habe hier absichtlich ein etwas komplexeres Beispiel gebracht, da man so schön zeigen kann, wie diese Manager-Klasse als Teil der Geschäftslogik mit verschiedenen DAOs interagiert und welche Aufgaben sie erfüllen kann.

In Deinem Kontext dürfte eine entsprechende "Manager-Klasse" vermutlich sehr mager ausfallen:


```
datensatz.setA(...);
datensatz.setB(...);
datensatzDAO.save(datensatz);
```

Es ist völlig klar, das ein Anfänger hinter sowas keinen Sinn sieht - _Warum soll ich eine eigene Klasse erstellen mit einer Methode, die nichts anderes macht als zwei Setter aufzurufen und dann das Objekt zu speichern? Das kann ich doch auch anderswo hinschreiben und spar mir dabei eine Extra Klasse!_

Genau deswegen das geringfügig aufgeblasene Beispiel von mir: Ich denke Du kannst erahnen, dass in anspruchsvollen Applikationen die Geschäftslogik einen großen Raum einnimmt - dutzende oder gar hunderte von Klassen mit möglicherweise jeweils hunderten oder tausenden von Zeilen von Code. Spätestens dann ist es absolut wichtig, dass man Sachen wie DB-Zugriffe (die dann ihrerseits möglicherweise wieder sehr kompliziert und lang sind) da nicht auch noch mit reinpackt. Sonst verliert man schnell den Überblick und hat Code, den niemand mehr versteht und den keiner warten oder testen kann.

Es geht dabei immer um Trennung von Zuständigkeiten. Nur so erzeugt man strukturierte Anwendungen. Auch wenn man klein anfängt - niemand weiß, was für Erweiterungen der Chef morgen gerne in der Anwendung sehen möchte. In dieser Hinsicht sind diese Menschen völlig unberechenbar. In einer strukturierten Anwendung, wo jede Komponente (z.B. jedes Modul, jede Klasse, jede Metode) eine klar definierte Rolle einnimmt, lassen sich Erweiterungen oder Änderungen meist leichter einpflegen als wenn man eh schon auf hunderten, tausenden oder gar zehntausenden Zeilen von Spaghetticode rumsitzt.

Das von mir gegebene Beispiel wird dann komplett, wenn man bedenkt, dass über der Geschäftslogik ja auch noch das Frontend sitzt. Das sind dann die drei Schichten - Persistenzschicht, Geschäftslogik, Präsentationsschicht - die man im "Three Tier"-Modell (Dreischichtenmodell) für gewöhnlich antrifft. Wilkommen in der Welt der "Enterprise Applications"!



7bkahnt hat gesagt.:


> Was mir grad so auffällt. Warum hole ich mir eigentlich das komplette User-Objekt/den kompletten DS und nicht nur die UserID und speichere die in die session?
> Hat das irgendeinen Vorteil?



Lass uns dass doch ganz einfach mal durchdiskutieren.

Möglichkeit 1: Du speicherst nur die User-ID in der Session.
Vorteil: Nur wenige Daten in der Session (wenig Speicherverbrauch, das wird wichtig, wenn sich 10000 User gleichzeitig anmelden)
Nachteil: Jedes mal wenn weitere Felder des User-Objekts benötigt werden muss dieses aus der DB geladen werden -> Belastung der DB

Möglichkeit 2: Du speicherst das User-Objekt in der Session.
Vorteil: Kein DB-Zugriff, wenn User-Objekt benötigt wird. Die HttpSession ist hier wie ein Cache für das geladene Objekt.
Nachteil: Möglichwerweise hoher serverseitiger Resourcenverbrauch (Speicher!)

Dabei gehe ich jetzt aber davon aus, dass Du a) eine Anwendung für massiven Multiusereinsatz hast und b) das User-Objekt schwergewichtig ist (oder werden könnte). Ein Objekt mit ein paar "Feldchen" und ohne dranhängende Objektgraphgiganten in einer Anwendung, die maximal ein paar dutzend oder ein paar hundert Anwender gleichzeitig verwenden ist i.A. kein großes Problem.

(Es gibt allerdings, und das muss an dieser Stelle auch gesagt werden, noch eine Reihe anderer Vor- und Nachteile. Speicher vs. Anzahl der DB-Zugriffe ist hier bei weitem nicht alles.)



7bkahnt hat gesagt.:


> ... und möchte jetzt eigentlich die listCrud() aufrufen, damit ich nun nach dem Einloggen ersteinmal die Tabelle angezeigt bekomme.
> Die listCrud() steht aber in einer anderen Action (CrudAction). Das hab ich aber noch nicht hinbekommen.
> 
> struts.xml:
> ...



In meinem Beitrag vom 6. Mai, 0:28 Uhr steht wies geht.



7bkahnt hat gesagt.:


> In der Doku und auf anderen Seiten steht immer nichts davon wie ich eine bestimmte Methode aufrufen kann. Das müsste ja irgendwie so aussehen:
> 
> [XML]
> <action name="vergleichUser" method="vergleich" class="actions.UserAction">
> ...



Nee, das ist auch verkehrt  Guckst Du weiter oben - oder halt endlich mal in die Struts-Doku - dann siehtst Du wies geht.

Die Idee ist doch:

[XML]
<action ...>
    <result name="success" type="redirectAction">
        <param name="action">listCrud</param>
    </result>
</action>
[/XML]

1. name=success korrespondiert mit dem "return SUCCESS;" in der Action-Klasse
2. Du hast keine Action mit dem Namen "CrudAction". Schau doch mal in Deine struts.xml! Deine Actions heissen "saveCrud", "UpdateCrud", "listCrud", "editCrud" und so weiter. Dämmerts? 

Stephan


----------



## stephanm (6. Mai 2010)

stephanm hat gesagt.:


> Das ist prinzipiell nicht ganz verkehrt. Es gibt aber wiederum eine andere Möglichkeit, die sich hier anbietet: ...



Autsch, da war ich geistig völlig wo anders. Mein danach folgendes Geschwafel hat nichts mit Deinem Programm zu tun. Siehs als allgemeingültigen Hinweis. Sorry. Du machst das, worum es Dir da am Anfang ging, schon richtig.


----------



## 7bkahnt (6. Mai 2010)

Is doch ideal, dass du auch mal ein paar andere Beispiele noch zeigst. Zumal mein richtiges Programm,  eine Post-und Vorgangsverwaltung wird. Also dein Beispiel passt eigentlich perfekt 

Jetzt habe ich es hinbekommen und es funktioniert alles.
Zwei Sachen sind allerdings noch
Mit redirectAction funktioniert es noch nicht. 
Wie gesagt ich habe die Struts Doku usw. ja schon danach durchforstet und normalerweise müsste es doch mit folgendem funktionieren:
[XML]<action name="vergleichUser" method="vergleich" class="actions.UserAction">
			   <result name="success" type="redirectAction">
        			<param name="action">listCrud</param>
    			</result>
		</action>[/XML]
Aber es funktioniert nicht^^.
Deswegen verlinke ich weiterhin auf die index2.jsp, wo die listCrud() aufgerufen wird.

Und das Zweite ist, ich muss das ModelDriven<Crud>-Interface in der CrudAction weiterhin mit einbinden inkl der getModel()-Methode, weil ansonsten das Einfügen eines neuen Datensatzes nicht mehr funktioniert bzw. die Werte aus der hinzuanzeige.jsp einfach nicht mit reingeschrieben werden in die Tabelle.
In der hinzuanzeige.jsp ändere ich ja die Werte text und zahl in dem crud-Objekt und übergebe es dann zur save()-Methode.
Das scheint aber ohne das ModelDriven dann nicht mehr zu funktionieren.

Achja, wozu ist eigentlich die serialVersionUID. Die verwende ich doch nie oder?

grüsse


----------



## stephanm (6. Mai 2010)

7bkahnt hat gesagt.:


> Wie gesagt ich habe die Struts Doku usw. ja schon danach durchforstet und normalerweise müsste es doch mit folgendem funktionieren: ...
> Aber es funktioniert nicht^^.



Wir haben _beide_ ein Problem mit dem abtippen wie ich sehe... :lol:

Der Parameter heißt "action*Name*", nicht "action", siehe: Redirect Action Result

Also müsste das hier
[XML]
<action name="vergleichUser" method="vergleich" class="actions.UserAction">
    <result name="success" type="redirectAction">
        <param name="actionName">listCrud</param>
    </result>
</action>
[/XML]
richtig sein.



7bkahnt hat gesagt.:


> Und das Zweite ist, ich muss das ModelDriven<Crud>-Interface in der CrudAction weiterhin mit einbinden inkl der getModel()-Methode, weil ansonsten das Einfügen eines neuen Datensatzes nicht mehr funktioniert bzw. die Werte aus der hinzuanzeige.jsp einfach nicht mit reingeschrieben werden in die Tabelle.
> In der hinzuanzeige.jsp ändere ich ja die Werte text und zahl in dem crud-Objekt und übergebe es dann zur save()-Methode.
> Das scheint aber ohne das ModelDriven dann nicht mehr zu funktionieren.



Du kannst nicht einfach das implements ModelDriven<T> wegnehmen und erwarten dass dann schon alles funktioniert. Lass halt die Action ModelDriven sein, warum auch nicht? Viel wichtiger ist doch, dass Du verstehst, was das bewirkt.



7bkahnt hat gesagt.:


> Achja, wozu ist eigentlich die serialVersionUID. Die verwende ich doch nie oder?



Let me google that for you


----------



## 7bkahnt (6. Mai 2010)

> Wir haben beide ein Problem mit dem abtippen wie ich sehe...


 Oh man, nicht wahr. 
Jetzt funktioniert es.



> Let me google that for you


Oh man, sonst reg ich mich immer über Leute auf, die zu faul sind mal schnell nach Etwas zu googlen.
Man lässt sich aber auch schnell verleiten, hier eine Frage  hinzuschreiben, anstatt mal schnell selbst nachzuschauen.

Also nochmal nen richtig großes Danke, für die unglaubliche Unterstützung!
Nun werd ich mich dann mal dem "großem mächtigen" Programm wittmen^^.


----------



## stephanm (6. Mai 2010)

7bkahnt hat gesagt.:


> Oh man, nicht wahr.
> Nun werd ich mich dann mal dem "großem mächtigen" Programm wittmen^^.



Hier noch ein paar Tips für eine mögliche Agenda:

- Sinnvolle Namen an allen Ecken und Enden. Beispiele: Die Action, die sich um die Benutzeranmeldung kümmert, sollte LoginAction heissen. Ihre URL sollte möglichst irgendwas mit /login.action enthalten. Die dahinterstehende JSP-Seite heisst vielleicht auch lieber login.jsp. CRUD ist übrigens eine Abkürzung für ein bestimmtes Schema von häufig wiederkehrenden Aktionen mit Datensätzen - das ist nichts, womit ich jetzt irgendwelche Bezeichner zupflastern würde. Kurzum: Nimm sprechende Namen.

- Benutze Container Managed Security. Das erspart Dir eine Menge ärger.

- Logging. Und zwar nicht mit System.out.

- Dokumentation, mindestens auf Klassen/Methoden-Ebene ist nie verkehrt.

Und noch eine Bitte an Dich für die Zukunft: Du bist sehr zurückhaltend, was Fehlerbeschreibungen angeht. Hier kam oft nur ein "funktioniert nicht" von Dir. Sehr häufig kriegt man aber Exceptions oder Fehlermeldungen. Die sollte man nicht nur selber genau lesen sondern sie auch im Zweifelsfall weitergeben.

Na dann mal ran an die Sache. Viel Spaß und Erfolg!

Stephan


----------



## 7bkahnt (6. Mai 2010)

Jetzt überleg ich doch noch nach einer Aktualisierung, wie ich es hinbekommen könnte, wenn 2 oder mehr gleichzeitig den selben DS bearbeiten möchten, dass sie dann einen Hinweis bekommen, dass der DS grad in Bearbeitung ist.:rtfm:


----------



## stephanm (6. Mai 2010)

7bkahnt hat gesagt.:


> Jetzt überleg ich doch noch nach einer Aktualisierung, wie ich es hinbekommen könnte, wenn 2 oder mehr gleichzeitig den selben DS bearbeiten möchten, dass sie dann einen Hinweis bekommen, dass der DS grad in Bearbeitung ist.:rtfm:



Dazu gibt es verschiedene Ansätze, z.B. Optimistic Locking und Pessimistic Locking. Das dürfte ein erster Ansatzpunkt sein, um Dein Google zu füttern.

Du musst aber bedenken, dass Webapplikationen eine Besonderheit aufweisen: Du kannst den Client (den Browser) nicht dazu zwingen, dem Server bescheid zu geben, wenn der Client geschlossen wird. Dann passiert im ungünstigsten Fall folgendes:

1. Benutzer öffnet Datensatz zum bearbeiten
2. Der Server (der Pessimistic Locking macht) sperrt den Datensatz in der Datenbank, z.B. durch ein Flag oder einen Eintrag in einer "lock"-Tabelle
3. Der Benutzer hat keinen Bock mehr und schliesst den Browser

Tja, der in 2. erstellte Lock bleibt dann für immer und ewig in der Datenbank. Kein Mensch kann den Datensatz je wieder bearbeiten.

Diesen Worst Case und all seine denkbaren Varianten, bei denen der Datensatz z.B. bis zum Ende der Mittagspause desjenigen Bearbeiters gesperrt ist, der vergessen hat, dass da noch ein wichtiges Browserfenster offen ist, muss man natürlich verhindern. Ich würde stets zu Optimistic Locking raten, außer wenn harte Anforderungen der Fachseite dagegensprechen und die Fachseite lässt sich nicht dazu überreden, von ihrer Position abzukommen.

Wie Du sehen wirst bist Du auch hier mit Deinen Problemen nicht alleine. Lies Dir mal folgendes durch: Slashdot Comments | Data Locking In a Web Application? und die Antworten darauf. 

Das hier hab ich auch noch auf die Schnelle gefunden: Datensatz zum bearbeiten sperren Es geht zwar um PHP, aber damit letztlich auch nur um eine Webapplikation. Die technischen Fragen dort zu PostgreSQL kannst Du überlesen, da packt ein anderer Anfänger sein Unwissen aus ;-)

Stephan


----------



## 7bkahnt (6. Mai 2010)

Ich dachte da an sowas wie
"select .... for update nowait"
Klickt Nutzer A dann auf Bearbeiten, dann wird dieser DS gelockt.Will dann ein zweiter Nutzer diesen DS bearbeiten, kommt doch dann eine Fehlermeldung, die ich dann eben so auswerte, dass er einen Hinweis bekommt, dass der DS gerade "in Benutzung" ist.
Müsste doch auch eigentlich funktionieren oder? Das will ich nachher mal ausprobieren.
Von Hibernate gibt es da auch bei der transaction irgendwie was mit NOWAIT.
Das Problem könnte nur sein, dass nachdem ich auf Bearbeiten geklickt habe, die Datenbankconnection ja beendet wird und erst wenn ich auf Ändern klicke das NOWAIT dann gesetzt werden würde.
Währen dessen könnte aber ein anderer Nutzer schon den Bearbeiten.

Edit: Okay, da ist aber genau dasselbe Problem, wenn der Client sein Browser schliesst.
Wenn es nicht sogar Pessimistic Locking ist, was ich da geschrieben habe.
Ich beles mich lieber erstmal


----------



## stephanm (6. Mai 2010)

7bkahnt hat gesagt.:


> Ich dachte da an sowas wie
> "select .... for update nowait"



Ich kenn leider die Funktion eines SELECT ... FOR UPDATE NOWAIT nicht (ich nix Orahkel, ich Mei-Esskuhell), habe aber das Gefühl, dass Du da in eine sehr gefährliche Richtung denkst.

Potenziell langlebige Locks auf Datenbanklevel sind in Webapplikationen ein Spiel mit dem Feuer. Lass es bleiben und schau Dir das Konzept von Optimistic Locking an und wie man dort das "Concurrent Editing"-Problem löst.

Stephan


----------



## 7bkahnt (7. Mai 2010)

Weißt du zufällig wie ich eine Methode aufrufen kann, wenn eine Exceptipn ausgelöst wurde?
Eigentlich müsste das doch in den catch-Block oder? Aber da funktioniert bei mir aktuell nicht mal ein System.out.println();

CrudAction:


```
public String edit()
	{
		 Integer userid=(Integer)session.get("userObject");   
		 HttpServletRequest request = (HttpServletRequest) ActionContext.getContext().get(ServletActionContext.HTTP_REQUEST);
		 crud = crudDAO.listCrudById(Long.parseLong(request.getParameter("id")));
		try {
			lockDAO.lockCrud(Long.parseLong(request.getParameter("id")),userid); 
		} catch (Exception e) {

			System.out.println("FEEHLER");
			
		}
		   
		return SUCCESS;
	}
```


CrudDAOImpl:


```
@Override
	public void lockCrud(Long auswahlid,Integer userid) throws Exception {
		Lock lock = new Lock();
		System.out.println("lockCrud()");
		Calendar cal = Calendar.getInstance();
		DateFormat df = DateFormat.getDateTimeInstance();
		String zeit=df.format(cal.getTime());
		System.out.println(zeit);
		
		lock.setId(auswahlid);
		lock.setZeit(zeit);
		lock.setId_sb(userid);
		session.save(lock);
	}
```

Es geht darum wenn 2 Nutzer den gleichen Datensatz bearbeiten wollen, dann wird nach dem Nutzer A auf Bearbeiten geklickt hat,  die edit()-Methode aufgerufen, welche  in die Locktabelle ein Eintrag macht, welcher Nutzer welchen DS gerade bearbeitet.
In die Spalte ID kommt die ID des Datensatzes.
Wenn jetzt Nutzer B ebenfalls auf bearbeiten klickt, geht er wieder in die edit-Methode und will in die Locktabelle wieder sich und seinen DS eintragen. Die Spalte ID ist aber Unique, da immer nur einer den DS bearbeiten soll.
Somit wird jetzt ein Fehler ausgeworfen, den ich in der CrudAction auffangen will.
Aber irgendwie geht er da niemals rein in den catch-Block.

Ziel ist es dann anstatt System.out.println eben eine Methode aufzurufen welche dem Benutzer dann einen Hinweis gibt, dass der DS gerade bearbeitet wird.
Der Fehler der in der console ausgeschrieben wird:
Hibernate: insert into Lock_t (ID_SB, ID) values (?, ?)
Error! Please, check your JDBC/JDNI Configurations and Database Server avaliability. 
 Could not commit the Hibernate Transaction: Could not execute JDBC batch update
org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update

Caused by: java.sql.BatchUpdateException: ORA-00001: Unique Constraint (SYSTEM.LOCK_T_UK2) verletzt
org.apache.catalina.core.StandardWrapperValve invoke
SCHWERWIEGEND: Servlet.service() for servlet default threw exception
java.lang.IllegalStateException

Ich hatte es auch mit mehreren Exceptions probiert, wie IOException oder auch die IllegalStateException, die ja laut Fehlermeldung (s. oben) sogar geworfen wird.
Aber irgendwie lässt sich nichts abfangen.


----------

