1:n Beziehung ralisieren

Dieses Thema 1:n Beziehung ralisieren im Forum "Application Tier" wurde erstellt von markai, 9. Juni 2012.

Thema: 1:n Beziehung ralisieren Ich hätte gerne dass die User Entities in meiner Applikation Rezepte anlegen können. Es handelt sich also um eine...

  1. 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:

    Code (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
     
    Code (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 (Text):
    recipes.add(new Recipe)
    in meinem UserController gemacht, aber das funktioniert ja nicht.
    Ich verwende die derby db und jsf.
     
  2. Vielleicht hilft dir das Grundlagen Training weiter --> *Klick*
  3. Vielleicht so ?
    Code (Java):

    @ManyToOne
    private List<Recipe> recipes = new ArrayList<>();
     
     
  4. Sry hab versehentlich die Annotation beim kopieren rausgelöscht. Hatte

    Code (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?
     
  5. 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.
    Code (Java):

    @ManyToOne
    @JoinColumn(name="CREATED_BY_USER")
    User createdByUser;
     
    und in deiner User-Klasse beziehst du dich in umgek. Notation auf die Receipe-Klasse
    Code (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
     
  6. 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.
    Code (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:

    Code (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:

    Code (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?
     
  7. 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.
    Code (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

    Code (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.
    Code (Java):

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

    Code (Java):

    @ManagedProperty(value="#{sessionBean}")
    SessionBean sessionBean
     
    Die sollte dann automatisch injiziert werden - vorausgesetzt der Name stimmt.
     
  8. 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.
    Code (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.
     
  9. 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.
     
  10. Das ist meine Facade.
    Code (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?
     
  11. 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.
     
  12. Toll, wieder was gelernt! Ich danke dir. Wünsche auch noch einen schönen Sonntag :)
     
  13. Kostenloses Java-Grundlagen Training im Wert von 39 €
    Schau dir jetzt hier das Tutorial an und starte richtig durch!