1:n Beziehung ralisieren

Diskutiere 1:n Beziehung ralisieren im Application Tier Bereich.
M

markai

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.
 
D

diel2001

Vielleicht so ?
Java:
@ManyToOne
private List<Recipe> recipes = new ArrayList<>();
 
M

markai

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

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
 
M

markai

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?
 
C

cljk

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.
 
M

markai

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.
 
C

cljk

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.
 
M

markai

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?
 
C

cljk

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.
 
M

markai

Toll, wieder was gelernt! Ich danke dir. Wünsche auch noch einen schönen Sonntag :)
 
Thema: 

1:n Beziehung ralisieren

Passende Stellenanzeigen aus deiner Region:
Anzeige

Neue Themen

Anzeige

Anzeige
Oben