Java EE Daten in SessionScoped Bean

Hallo zusammen,

ich habe eine Entity Klasse “CustomerEntity” für JPA

Nun brauche ich die Daten dieses User in mehreren Views immer wieder. Wie geht man vor; Erstelle ich noch eine Bean mit Sessionscoped “Customer” welche alle Daten des Kunden hält und ich darauf immer zugreifen kann? Oder setze ich eine http Session in der ich den Usernamen speichere und immer wenn ich Daten brauche, hole ich sie mir frisch aus der DB oder aber kann ich meine Entity ( die ja schon alles enthält was ich brauche) um @SessionScoped erweitern und diese nutzen?

Nein. Deine Entity ist rein für die Datenbank da. Vermische diese nicht mit deinem Kontext. Das SessionScoped ist für deinen Controller da. Dieser kann dann die Daten vorhalten, welche er aus der Datenbank geladen hat.

Ok danke. Hast du ein kleines Code Schnipsel? Ich hole meine sessionbean mit managedproperty aber bekomme immer null als Ausgabe

Glaub ich hab ein Verständnisproblem.

folgendes: habe eine jsf Seite welche Benutzernamen und Nachrichten enthält( ist nur eine sidebar)

Nun wollte ich eine Bean erstellen, welche alle userdaten bereit hält. In die jsf Site (Sidebar) wollte ich aus der userbean nur den Namen holen obwohl sie noch weitere Daten beinhaltet, die ich aber bei anderen views brauche.

Oder muss ich pro view eine eigene Bean erstellen und die Daten neu holen? Oder speichert man solche generellen Sachen wie Name etc. In der http session?

Kannst Du dazu mal Code zeigen? Eigentlich nutzt man die Annotation „managedProperty“ nicht mehr, sondern @Inject. Dazu musst Du natürlich CDI verwenden - was in einem JEE-Container kein Problem ist.

Hallo,

genau so hab ich gemacht, habe eine Session Bean die ich über die Getter mit Werten initalisiere und in einer ManagedBean hole ich sie mit @Inject, aber leider wird dann eine neue Bean erzeugt die alle Werte “null” hat

Was für einen Scope hat denn Deine ManagedBean?

EIn Codebeispiel würde hier stark weiterhelfen. :slight_smile:

Hab das inzwischen umprogrammiert (Ich speicher die Daten in der HTTP Session)

Es gab jedenfalls eine ManagedBean mit SessionScope, welche in einer anderen Klasse @Inject wurde und dort über die Setter mit Daten versehen wurde. In einer anderen Klasse sollte diese SessionBean geholt werden um über die Getter an die Werte zu kommen.

SampleCode (sorry ich finde den button für codeformatter nicht):

ManagedBean
SessionScoped
class User(){

private String name;
private String mail;
private String.....viele mehr 


//getter und setter
}

//Klasse für die JSF Seite in welcher Username und PW eingegeben werden.
@ManagedBean
@SessionScoped
class LoginFacade() {

@Inject User;
private String name;
private String password;

//getter setter

 public void login() {
//Daten aus DB prüfen und abgleichen ob logindaten stimmen

Hier möchte ich die Userdaten in einer Bean speichern um sie in einer anderen View abholen zu können
user.setName(entity.getloginName());
user.setMail(entity.getMail());
user.set...weitere Attribute

}

Dann gibt es eine xhtml Seite, welche auch eine eigene “Facade”.java hat.
in dieser xhtml rufe ich in einem label zb auf:

<span class="username">#{sidebarFacade.user.userName}</span>

Die SidebarFacade ist wie folgt:

class SidebarFacade(){
private String userName;
//weitere

@Inject
User user;

//getter und setter

}

Und hier wird mir als Name immer “” ausgegeben bzw Null

Für CDI müsstest du @ManagedBean durch @Named ersetzen.

Dann kannst du das @SessionScoped von User entfernen. Es ist ausreichend das für LoginFacade zu haben.

Ahhh ok, danke! da hab ich quasi JSF Annotaionen mit CDI gemixt?

Das versuch ich nochmal, danke vielmals!

Ist es richtig, dass ich die Logik für den Login in eine separate Klasse auslagere?

Die Managed Bean ist ja quasi nur dazu da, die Daten an die xhtml zu senden und entgegen zu nehmen oder?

Ich würde eine Klasse “AccessControl” schreiben (static) und in der Managedbean AccessControl.login aufrufen und darin die Loginlogik abarbeiten, die gespeicherten Userdaten holen etc.

Spricht da was dagegen?

Hab jetzt User mit @Named annotiert, kann ihn aber in einer Klasse über @Inject nicht injizieren, zumindest kann ich die Setter in der injizierenden Klasse nicht aufrufen

Edit: Musste den Customer mit static deklarieren, da ich ihn in einer static Methode nutze

Afair ja.

Also generell hab ich das Gefühl, dass du noch sehr viele Verständnisprobleme hast. JavaEE ist da imho auch recht wenig Einsteigerfreundlich. Deswegen mein erster Rat: wenn dich der Bereich interessiert, dann wäre der Start mit Sprint Boot sicherlich ein angenehmerer (eine Sache mit der ich mich gerade sehr Stark im Betrieb und privat beschäftige).

Aber mal ungeachtet dessen sollte das Nachfolgende abstrakt genug sein, dass es für beides funktioniert:

Du musst dir klar machen, welche Rollen deine Klassen haben. Dann wird dir auch recht schnell klar werden, dass eine Model klasse keinen Scope definieren muss.

Wenn ich das ganze Gliedern müsste, dann käme jetzt erstmal sowas bei raus:

Controller
Arbeitet zusammen mit deinem Template (JSF). Da er die Daten verarbeitet/beschafft besitzt er einen Scope (request, session, whatever). Ums mal anders zu sagen: der Controller kann Statefull sein.

Model-Klasse
Wäre hier jetzt dein User. Annotation die hier ok wären, wären z.B. JPA-Annotation wenn du ein orm verwendest. Es dient rein zur Datenhaltung und ist nicht an irgendwelche Scopes gebunden.

Trivia: Oftmals ist es sogar so, dass man solche Klassen in ein extra-Projekt auslagern kann und als api nutzt. Wenn du diese z.B. in einem Android-Projekt verwenden wolltest, dann müsstest du in dem Android-Projekt auch die JavaEE-Annotationen drin haben. Das ist ein gutes Beispiel dafür, warum Klassen definierte Aufgaben haben sollte.

Service-Klasse für Login
Wie der Login abläuft hat im Controller nichts zu suchen. Stell dir vor, du wolltest die gleiche Logik an anderer Stelle verwenden. Stell dir einfach vor, du würdest noch einen Controller für einen RESTService und WebSockets anbieten. Dann dürftest du das ganze 3x neu schreiben. Also packst du was mit dem User zusammen hängt in einen user-Service. Diesen könntest du dann im Controller verwenden.

Optional: Gateway
In meinen (kleineren) Projekten haben die Controller doch recht viel logik mit sich gebracht, die ich aber an anderer Stelle (RestController, …) exakt genauso gebraucht hab. Dafür hab ich dann Gateways welche dann praktisch nur noch als Delegate von den Controllern verwendet wurden. (Kennt keinen Scope/Stateless)

Trivia: Ein Gateway ist ähnlich der Facade. Unterschied: Facade verbirgt was nach außen hin, ein Gateway etwas nach innen.

Zusammenfassend hat man dann nur den Controller welcher SessionScoped sein kann. Er kann dann den User (welchen er vom Service bekommt) einfach in einem Feld vorhalten und dadurch brauchst du den nicht jedes mal neu anzufragen.

Ok, danke

In meiner Sideboard.xhtml möchte ich wie folgt den Namen haben, bekomme aber imm null #{sidebarController.customerSessionBean.customerName}

ich habe es jetzt wie folgt, magst du dir das mal ansehen ob das so halberwegs ok ist? Vielleicht erkennst du das Problem warum immer null ausgegeben wird

Habe neben diesen hier noch die CustomerEntity mit JPA Annotationen und einen CustomerService mit CRUD Methoden

Meine JSF Fassade

@SessionScoped
@ManagedBean
public class LoginController {
	@Inject
	GrowlMessage growlMessage;
	@Inject
	AccessControl accessControl;

	private String loginName;
	private String password;

	public String doLogin() {
		if (accessControl.checkPasswordValid(loginName, password)) {
			return "private/dashboard?faces-redirect=true";
		} else {
			GrowlMessage.sendFatalMessage("Da stimmt was nicht :(", "Bitte kontrollieren Sie Ihre Logindaten");
			return "login";
		}
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getLoginName() {
		return loginName;
	}

	public void setLoginName(String userName) {
		this.loginName = userName;
	}
}

Mein AccessController

@Named
public class AccessControl {

	@Inject
	CustomerService customerService;
	@Inject
	CustomerSessionBean customerSessionBean;
	@Inject
	Customer customer;

	/**
	 * This method checks if the saved password hash equals the hash given in the
	 * parameter
	 * 
	 * @param userName
	 * @return
	 */
	public boolean checkPasswordValid(String loginName, String password) {
		customer = customerService.getCustomerByName(loginName);
		String passwordHashFromDB = customer.getCustomerPasswordHash();
		String passwordHashFromView = PasswordGenerator.hashPassword(password);
		customerSessionBean.setCustomerName(customer.getCustomerName()); // brauch ich in einer anderen View zb
		if (passwordHashFromDB.equals(passwordHashFromView)) {
			return true;
		}
		return false;
	}
}

CustomerSessionBean, welche immer mal wieder in der View benötigt wird um Daten daraus zu holen und um nicht jedesmal auf die DB greifen zu müssen, wenn der UserNAme angezeigt werden soll o.ä

@Named(value="customerSessionBean")
public class CustomerSessionBean implements Serializable {
	private static final long serialVersionUID = 1L;

	private int customerId;
	private String customerDbHost;
	private String customerDbPass;
	private String customerDbUser;
	private String customerDomain;

//getter Setter

@ManagedBean
@SessionScoped
public class SidebarController {
	@Inject
	CustomerSessionBean customerSessionBean;
	@Inject
	AccessControl accessControl;

	/**
	 * Logout and remove session
	 */
	public void logout() {
		accessControl.logout();
	}

	public CustomerSessionBean getCustomerSessionBean() {
		return customerSessionBean;
	}

	public void setCustomerSessionBean(CustomerSessionBean customerSessionBean) {
		this.customerSessionBean = customerSessionBean;
	}
}

AccessControl wirkt auf mich seltsam. Auf der einen Seite injectest du customer - aber überschreibst es beim login. Wie der customer bereit gestellt wird ist mir auch nicht wirklich klar. Vom Code her würde ich auch sagen, dass customer nicht wirklich ein eigenes Feld sein muss. Dann wäre AccessControl statless und überschaubar. Momentan hast du den statefull (und wenn ichs richtig in Erinnerung hab: RequestScoped).

Ansonsten müsste das (glaube ich) passen. Vllt hat ja jemand noch eine Meinung dazu, der mehr Erfahrung mit JavaEE hat als ich.

Einen weiteren Tipp hab ich aber noch: Nutze keine Field-injection, sonder Constructor-Injection. Hat ein paar Vorteile:

  • Deine Klasse ist außerhalb eines IoC-Containers nutzbar
  • Erhöht die Testbarkeit
  • Es ist klar definiert, welche Objekte zum Bau eines gültigen/stabilen Objektes von der Klasse notwendig sind.
  • Spätestens ab dem 3te Parameter fängt man an sich Gedanken darüber zu machen, ob die Abhängigkeiten wirklich alle Notwendig sind oder man gerade einen Designfehler begeht/begangen hat.

Danke, AccessControl hab ich nochmal etwas geändert.

CustomerEntity ist mein DAO (oder DTO?) verwechel ich immer. Der CustomerService holt mir den Kunden aus der DB mit entsprechendem Namen aus der View und der CustomerSessionBean sollen wir im Code, der Name zugefügt werden, damit ich es in der SidebarControllerView holen kann

@Named
public class AccessControl {

	@Inject
	CustomerService customerService;
	@Inject
	CustomerSessionBean customerSessionBean;
	@Inject
	Customer customerEnitity;

	/**
	 * This method checks if the saved password hash equals the hash given in the
	 * parameter
	 * 
	 * @param userName
	 * @return
	 */
	public boolean checkPasswordValid(String loginName, String password) {
		customerEnitity = customerService.getCustomerByName(loginName);
		String passwordHashFromDB = customerEnitity.getCustomerPasswordHash();
		String passwordHashFromView = PasswordGenerator.hashPassword(password);
		customerSessionBean.setCustomerName(customerEnitity.getCustomerName());
		SessionUtil.getSession().setAttribute("loggedIn", true);
		if (passwordHashFromDB.equals(passwordHashFromView)) {
			return true;
		}
		return false;
	}
	
	public void logout() {
		System.out.println(customerSessionBean.getCustomerName());
		FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
		FacesContext context = FacesContext.getCurrentInstance();
		ExternalContext externalContext = context.getExternalContext();
		try {
			externalContext.redirect("/login.xhtml?faces-redirect=true");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

Hier die Zeile aus der sidebar.xhtml
#{sidebarController.customerSessionBean.customerName}

Und der dazugehörige Controller
@ManagedBean
@SessionScoped
public class SidebarController {
@Inject
CustomerSessionBean customerSessionBean;
@Inject
AccessControl accessControl;

	/**
	 * Logout and remove session
	 */
	public void logout() {
		accessControl.logout();
	}

	public CustomerSessionBean getCustomerSessionBean() {
		return customerSessionBean;
	}

	public void setCustomerSessionBean(CustomerSessionBean customerSessionBean) {
		this.customerSessionBean = customerSessionBean;
	}
}

Wie geagt in der Sidebar.xhtml bekomm ich immer null statt dem namen.

Der Name wird aber korrekt gesetzt wenn ich den im AccessControl prüfe.

Wir denn per @Inject nicht immer ein neues Objekt geholt?

Ahh ich mixed cdi wieder wieder jsf sessionScoped

Wann nutzt man denn import javax.faces.bean.SessionScoped und wann import javax.enterprise.context.SessionScoped;

Wie könnte ich das kapieren, ohne dass immer zu vertauschen?

Sorry, ja macht auch Sinn dass das null ist. Hast du mal die Instanzen von CustomerSessionBean geprüft welche AccessControl und Sidebar… bekommen? Ich tippe mal drauf die sind Unterschiedlich. CustomerSessionBean benötigt momentan die @SessionScoped-Annotation. Tatsächlich müsste (laut dem Code den ich hier sehe) CustomerSessionBean auch die einzige sein, welche diesen expliziten Scope benötigt.

Keine Ahnung. Lang ists her das ich das letzte mal vor einem JavaEE-Projekt gesessen hab. Aber im worst-case: google die Lösung und stell deine IDE so ein, dass nur noch einer der beiden imports möglich ist.