1:n Beziehung ralisieren

markai

Aktives Mitglied
Ich hätte gerne dass die User Entities in meiner Applikation Rezepte anlegen können. Es handelt sich also um eine 1:n-Beziehung. Dazu habe ich folgendes gemacht:

Java:
@Entity @Table(name="Users")
public class User implements Serializable {
    
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String firstname;
    @Column(nullable=false) @NotBlank @Pattern(regexp="\\D+", message="no numbers allowed")
    private String lastname;
  
    private List<Recipe> recipes = new ArrayList<>();
    @Column(nullable=false) @NotBlank

Java:
@Entity
public class Recipe implements Serializable, Comparable<Recipe> {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Column(nullable=false) @NotBlank
    private String name;
    @Column(nullable=false) @NotBlank
    @Lob
    private String description;
    @ManyToMany(fetch= FetchType.EAGER) @Valid
    private List<Ingredients> ingredients = new ArrayList<>();
    @ElementCollection @Valid
    private List<Comment> comments = new ArrayList<>();

Ich hab jetzt zwar einen RecipeController und einen UserController mit dem ich User und auch Recipes anlegen kann, aber wie funktioniert die Zuordnung? Ich hätte gerne
Code:
recipes.add(new Recipe)
in meinem UserController gemacht, aber das funktioniert ja nicht.
Ich verwende die derby db und jsf.
 

markai

Aktives Mitglied
Sry hab versehentlich die Annotation beim kopieren rausgelöscht. Hatte

Java:
    @OneToMany
    private List<Recipe> recipes = new ArrayList<>();

(Ein User soll ja mehrere Rezepte anlegen können.) Hab dann zwar eine Zwischentabelle "USER_RECIPE" mit den Attributen "USER_ID" und "RECIPE_ID" aber keinen Plan wie ich die Zuordnen soll. Gibt doch sicher eine Möglichkeit dass das JPA das für mich macht?
 
C

cljk

Gast
Du hast eine 1:n und keine n:n Zuordnung. Daher ist eine Zwischentabelle nicht notwendig.

Du wirst User haben (1), die jew. mehrere Rezepte zugeordnet bekommen (n).

Erweitere deine Recipe-Klasse um das Feld User z.B.
Java:
@ManyToOne
@JoinColumn(name="CREATED_BY_USER")
User createdByUser;
und in deiner User-Klasse beziehst du dich in umgek. Notation auf die Receipe-Klasse
Java:
@OneToMandy(mappedBy="createdByUser")
List<Receipe> receipesOfUser;
Eine Initialisierung mit = new ArrayList... solltest du ggf. sein lassen, außer du brauchst das. Beim Laden aus der DB bekommst du eh nur Objekte mit initialisierten (ggf. leeren) Listen zurück.


Gruß

Marcel
 

markai

Aktives Mitglied
Ok die Zwischentabelle ist wohl von einem früheren Versuch übrig geblieben. Danke für den Tipp! Zuordnung funktioniert bestens :toll:

Jetzt hätte ich noch gerne dass auf meiner "home" Seite ALLE Rezepte angezeigt werden.
Java:
@ManagedBean(name = "recipeController")
@SessionScoped
public class RecipeController implements Serializable {
    ...
    public List<Recipe> getAllRecipes() {
        List result = getFacade().getEntityManager().createQuery("select r from Recipe r").getResultList();
        return result;
... was bestens funktioniert. Auf dem jeweiligen Benutzerprofil sollen aber nur eigene Rezepte angezeigt werden:

Java:
...
SessionBean sessionBean = (SessionBean) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("sessionBean");

    public List<Recipe> getMyRecipes() {
        List result = getFacade().getEntityManager().createQuery("select r from Recipe r where USER_ID = "+sessionBean.getCurrentUser().getId()).getResultList();
        return result;

was folgende Exception wirft:

Java:
javax.el.ELException: /pages/myProfile.xhtml @34,49 value="#{recipeController.myRecipes}": java.lang.IllegalArgumentException: An exception occurred while creating a query in EntityManager: 
Exception Description: Error compiling the query [select r from Recipe r where USER_ID = 1], line 1, column 29: unknown identification variable [user_id]. The FROM clause of the query does not declare an identification variable [user_id].
	at com.sun.faces.facelets.el.TagValueExpression.getValue(TagValueExpression.java:114)
	at javax.faces.component.ComponentStateHelper.eval(ComponentStateHelper.java:194)
	at javax.faces.component.ComponentStateHelper.eval(ComponentStateHelper.java:182)
	at javax.faces.component.UIData.getValue(UIData.java:731)
	at org.primefaces.component.datatable.DataTable.getValue(DataTable.java:773)
	at javax.faces.component.UIData.getDataModel(UIData.java:1798)
	at javax.faces.component.UIData.getRowCount(UIData.java:356)
	at org.primefaces.component.api.UIData.calculatePage(UIData.java:127)

(In der sessionBean wird der aktuelle User gespeichert.) Was mache ich falsch?
 

cljk

Mitglied
Also das einfachste(!) da du die Relation definiert hast ist ja, den User zu laden und über das User-Objekt mit getReceipes() (oder wie es heisst) auf die Rezepte zuzugreifen.
Java:
sessionBean.getCurrentUser().getReceipes()

Die Lösung die du da nutzt, geht auch... du solltest aber daran denken, dass du JPA nutzt und nicht SQL. JPA kennt die Column-Names etc nicht. Ich mag grad nicht nachschlagen - aber du schreibst etwas in der Art wie

Java:
Query q = getFacade().getEntityManager().createQuery("select r from Recipe r where r.createdByUser = :user");
q.setParameter("user", sessionBean.getCurrentUser());
return q.getResultList();


Im übrigen ist auch obiges relativ gesehen Mist. Queries gehören in Transaktionen und damit nicht in die Managementbean sondern in die SessionBean. Der EJB-Container bastelt dann automatisch eine Transaktion drum.

Und noch eine Anmerkung.
Java:
SessionBean sessionBean = (SessionBean) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("sessionBean");
kannste dir vermutlich sparen. Ist das eine RequestScope- ManagementBean?
Dann kannste auch einfach schreiben.

Java:
@ManagedProperty(value="#{sessionBean}")
SessionBean sessionBean
Die sollte dann automatisch injiziert werden - vorausgesetzt der Name stimmt.
 

markai

Aktives Mitglied
Ist ja direkt peinlich dass ich nicht selbst dran gedacht hab... Jedenfalls funktioniert es so.

Die sessionBean verwende ich um mein User-Objekt zu speichern wenn dieser sich anmeldet.
Java:
@SessionScoped
@ManagedBean(name="sessionBean")
public class SessionBean implements Serializable {
    
    private User currentUser;

// getter u setter
    }
Ist zwar vermutlich nicht schön, aber es war die einzige Lösung die mir eingefallen ist... Du meinst also dass Tranksaktionen auch in meine sessionBean rein gehören? Wäre für Verbesserungsvorschläge dankbar.
 

cljk

Mitglied
Nene... jetzt hauen wir gerade zwei Sachen durcheinander (JSF-SessionBean und Stateless Session Bean).

Ich bin davon ausgegangen, dass "getFacade()" dir einen StatelessSessionBean (EJB) zurück gibt. Ist das so?
Wenn ja - gehört die JPA-Anfrage als Methode definitiv da rein und von "getEntityManager" hast du dann auf JSF-Seite die Finger zu lassen. Der EntityManager wäre dann nur gültig für den Kontext der EJB und soll nicht weitergegeben werden - auch wenn es aktuell funktioniert.
Wenn du allerdings alles irgendwie in JSF Request- und SessionBeans abwickelst, ist das zwar definitiv nicht schön - kann dann bei dem einfachen Projekt so bleiben.
 

markai

Aktives Mitglied
Das ist meine Facade.
Java:
@Stateless
public class RecipeFacade extends AbstractFacade<Recipe> {
    @PersistenceContext(unitName = "cookItLikeABoss_PU")
    private EntityManager em;

    @Override
    public EntityManager getEntityManager() {
        return em;
    }

    public RecipeFacade() {
        super(Recipe.class);
    }
}
Verstehe ich das richtig? Ich soll meine queries da reinschreiben und diese dann vom RecipeController aus aufrufen?
 

cljk

Mitglied
Das ist meine Facade.
Verstehe ich das richtig? Ich soll meine queries da reinschreiben und diese dann vom RecipeController aus aufrufen?

Absolut - Queries gehören in einem JavaEE-Projekt immer in EJBs.

Der Sinn von StatelessSessionBeans (inbes. bei sog. Fassaden) ist u.a. die Behandlung der Datenspeicherung. Alles andere geht - wie du siehst - auch - ist aber ganz böser Stil.


Ich weiss nicht wo du die Oberklasse AbstractFacade her hast - aber da würde ich getEntityManager auf "protected" statt "public" setzen. Dann siehst du recht schnell, was geändert werden muss - weils nämlich nicht mehr läuft *g. Ich vermute mal einfach, dass AbstractFacade manche Basis-Methoden für den Datenzugriff definieren wie z.B. "findByPrimaryKey" o.ä. und "getEntityManager" benötigt, um den EntityManager der Subklassen zu beziehen. Das ist aber nicht für die JSF-Klassen gedacht.

Also "getAllRecipes()" gehört *definitiv* in die EJB - wenn du getMyReceipes jetzt nur über den Objektzugriff regelst, kannst das in der ManagedBean lassen - mit der Querie-Variante muss das sonst aber def. auch dahin.
Den Benutzer wirst du ja vermutlich auch über eine Fassade (und damit einer EJB) suchen.

Auf JSF-Seite rufst die Methoden passend auf und leitest das Ergebnis weiter. Ist 3 Zeilen Mehrarbeit - lohnt sich aber - und schöner strukturiert ist es allemale.


So, ich geh jetzt in die Sonne - schönen Sonntag noch.
 

Neue Themen


Oben