Anfängerfrage: h:selectOneMenu (JSF 2.0), @ManyToOne Annotation in Entity (JPA 2.0)

Tatsu

Mitglied
Hallo alle zusammen,
wie im Header schon erwähnt bin ich gerade dabei Java EE 6 zu lernen und lese zur Zeit ein halbes Dutzend Bücher zu dem Thema. Bevor ich diese Frage im Forum gestellt habe, habe ich bereits in den Büchern nachgeschaut und natürlich auch Google "befragt". Die Bespiele die ich dort gefunden habe sind sehr unterschiedlich und haben bei mir leider nicht so funktioniert wie ich mir das gedacht habe. Daher folgende Fragestellung.

Problem:
Ich versuche seit geraumer Zeit eine ManyToOne-Verbindung im Formular einer JSF-Seite als Drop-Down abzubilden.

Ich habe eine Entity "Product" und eine Entity "License". Es gibt eine JSF-Seite mit einem Formular mit dem man eine neue Lizenz erstellen können soll. Im SelectMenü soll ein Produkt ausgewählt werden. Wenn ich versuche das Formular zu sichern, treten immer Fehlermeldungen auf. Meine Vermutung ist, das ich einen Converter schreiben muss. Alledings habe ich keine Ahnung ob das wirklich so ist und wie man das am besten macht (state of the art).

Ich experimentiere zur Zeit mit 2 JSF-Varianten.

Variante 1:
HTML:
...
<h:selectOneMenu id="product" value="#{licenseController.license.productId}">
  <f:selectItem itemLabel="Bitte auswählen..." itemValue="" />  
    <f:selectItems value="#{productController.productList}" var="n" itemLabel="#{n.name}" itemValue="#{n}"/>
</h:selectOneMenu>

Variante 2:
HTML:
...
<h:selectOneMenu id="product" value="#{licenseController.license.productId}">
  <f:selectItem itemLabel="Bitte auswählen..." itemValue="" />  
    <f:selectItems value="#{productController.productList}" var="n" itemLabel="#{n.name}" itemValue="#{n.id}"/>
</h:selectOneMenu>

Der Unterschied liegt im ItemValue (einmal itemValue="#{n}, bzw. itemValue="#{n.id}). Wenn ich {n} angebe, dann scheint im Value des Feldes ein Verweis auf das Objekt zu stehen. Im zweiten Fall {n.id}, der Primärschlüssel des Datensatzes auf den verwiesen wird. Beides funktioniert nicht und erzeugt eine Fehlerausgabe. Bei beiden Varianten tritt ein Fehler beim Sichern auf.

Fehlerausgabe Variante 1:
Konvertierungsfehler beim Festlegen von Wert 'de.insecma.ilm.Product@1b27080' für 'null Converter'.

Fehlerausgabe Variante 2:
Konvertierungsfehler beim Festlegen von Wert '51' für 'null Converter'.

Fragen:
1. Ist die Art der Umsetzung wie ich das momentan in der JSF-Seite mache überhaupt richtig (best coding practice)?
2. Brauche ich einen eigenen Converter?
3. Wenn ja, wie schreibe ich den am besten und wir binde ich den ein?

Sonstige Code-Auszüge:

License.java
Java:
@Entity
@NamedQuery(name = "findAllLicenses", query = "SELECT l FROM License l")
public class License implements Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;
    @JoinColumn(name = "produktId", referencedColumnName = "name")
    @ManyToOne(optional = false)
    private Product productId;
// ... Attribute, Getter und Setter

Product.java
Java:
@Entity
@NamedQueries({
    @NamedQuery(name = "findAllProducts", query = "SELECT p FROM Product p")
})
public class Product implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    @NotNull
//...
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "productId")
    private Collection<License> licenseCollection;
// ... Attribute, Getter und Setter

CustomerEJB.java
Java:
@Stateless
public class CustomerEJB {
    @PersistenceContext(unitName = "IlmPU")
    private EntityManager em;

    public List<Customer> findAll() {
        TypedQuery<Customer> query = em.createNamedQuery("findAll", Customer.class);
        return query.getResultList();
    }

    public Object findById(int id) {
        TypedQuery<Customer> query = em.createNamedQuery("findById", Customer.class);
        query.setParameter("id", id);
        return query.getSingleResult();
    }

    public Customer createCustomer(Customer customer) {
        em.persist(customer);
        return customer;
    }
}

LicenseEJB.java
Java:
@Stateless
public class LicenseEJB {
    @PersistenceContext(unitName = "IlmPU")
    private EntityManager em;

    public List<License> findLicenses() {
        TypedQuery<License> query = em.createNamedQuery("findAllLicenses", License.class);
        return query.getResultList();
    }

    public License createLicense(License license) {
        em.persist(license);
        return license;
    }
}

LicenseController:
Java:
@ManagedBean
@RequestScoped
public class LicenseController implements Serializable {
    
    @EJB
    private LicenseEJB licenseEJB;
    private License license = new License();
    private List<License> licenseList = new ArrayList<License>();

    public String doCreateLicense() {
        this.license = licenseEJB.createLicense(license);
        this.licenseList = licenseEJB.findLicenses();
        return "licenses.xhtml?faces-redirect=true";
    }

    public License getLicense() {
        return this.license;
    }

    public void setLicense(License license) {
        this.license = license;
    }

    public List<License> getLicenseList() {
        this.licenseList = licenseEJB.findLicenses();
        return this.licenseList;
    }

    public void setLicenseList(List<License> licenseList) {
        this.licenseList = licenseList;
    }

}

CustomerController.java
Java:
@ManagedBean
@RequestScoped
public class CustomerController implements Serializable {

    private static final Logger logger = Logger.getLogger("de.ispdata.test1.CustomerController");
    
    @EJB
    private CustomerEJB customerEJB;
    private Customer customer = new Customer();
    private List<Customer> customerList = new ArrayList<Customer>();

    public String doCreate() {
        this.customer = customerEJB.createCustomer(customer);
        this.customerList = customerEJB.findAll();
        return "customers.xhtml?faces-redirect=true";
    }

    public Customer getCustomer() {
        return this.customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    public List<Customer> getCustomerList() {
        this.customerList = customerEJB.findAll();
        return this.customerList;
    }

    public void setCustomerList(List<Customer> customerList) {
        this.customerList = customerList;
    }    
    
    public Object getCustomerById(int id) {
        return customerEJB.findById(id);
    }

}


Es wäre sehr schön wenn mit eine(r) von Euch helfen würde und mich auf den richtigen Weg bringt.

Vielen Dank im Voraus

Bastian
 

Anhänge

  • Variante1.png
    Variante1.png
    18 KB · Aufrufe: 37
  • Variante2.png
    Variante2.png
    15,3 KB · Aufrufe: 34

JimPanse

Bekanntes Mitglied
hi,

der grundlegende Fehler liegt darin, dass die Menü-Komponenten in JSF eine Liste an SelectItem-Komponenten erwartet. Ich kann den code jetzt nicht ganz gut reproduzieren aber ich versuche dir mal 2. Varianten auf zu zeigen (eher pseudo-code)

1. Varainte

Code:
@ManagedBean
@<scope>
public Controller {
              private <Typ der Id> selectedId;
              private List<SelectItem> list = new ArrayList<SelectItem>();

             @PostConstruct
             public void init(){
                  list .add(new SelectItem(null,"Auswahl");
                  for(Entity e : ejb.findAll()){
                        list .add(new SelectItem(e.getId(),e.getName());
                    }
            }
//getter & setter
}
2. Variante mit Objecten (dann muss in der Entity aber equals() implementiert sein!!!!)

Code:
@ManagedBean
@<scope>
public Controller {
                private Entity  select;
               private List<SelectItem> list = new ArrayList<SelectItem>();
                
                 @PostConstruct
                public void init(){
                      list .add(new SelectItem(null,"Auswahl");
                     for(Entity e : ejb.findAll()){
                           list .add(new SelectItem(e,e.getName());
                     }
              }
//getter & setter
}

@FacesConverter("convertId")
public EntityConverter implements Converter{

//Ejb Bean holen entweder aus einer JSF-ManagedBean oder per InitialContext

private EJBBean ejbBean;

public Object getAsObject(arg0,arg1,arg2)
{
// "Auswahl" abfangen
if(arg2 == null)return null;
return  ejbBean.findByName(arg2);
 
}


public String getAsString(....)
{
// "Auswahl" abfangen
if(o == null) return "";
return ((Entity)o.getName();
 
}

}

Code:
<h:selectOneMenu id="product" value="#{controller.select}" convertId="<converterId falls mit Object gearbeitet wird>">
    <f:selectItems value="#{controller.list}"/>
</h:selectOneMenu>

Die erste Variante wird aber eher bevorzugt weil es doch sehr unschön ist in den Convertern Hibernate bzw. Spring DAO's oder EJB-Session-Beans zu verwenden -> der einzige Vorteil liegt daran, dass man mit den Objekten selbst arbeiten kann...


grüße
 

Tatsu

Mitglied
Auf Basis der Informationen und des Beispiels von JimPanse konnte ich das Problem lösen. JimPanse, danke für Deine Hilfe. Ich war kurz davor mein Notebook aus dem Fenster zu schmeißen. :)

Gelöst:
Die Lösung - wie sie für mich funktioniert - folgt unten in diesem Text.

Da ich aber allerdings absoluter Anfänger in Java EE 6 bin, würde ich mich darüber freuen, wenn Ihr Eure Meinung dazu abgeben würdet ob dieser Code so in Ordnung ist.

Daher noch folgende Fragen:
  1. Sollte man diesen Code umstellen, oder ist er gut so wie er ist (Best Practice, Skalierung, Performance)?
  2. Ist das EE 6 konform oder habe ich da versehentlich "alte" Techniken eingebaut?
  3. Ist es richtig, das ich die *EJB.java-Klassen auch weglassen könnte, wenn ich ausschließlich über JSF auf die Entities zugreifen wollen würde (keine Webservices, Soap, etc.)?

Funktionierendes Codebeispiel: (ManyToOne, EJB und JSF mit Converter)

Converter [ProductListConverter.java]
Java:
@ManagedBean
@FacesConverter(forClass = License.class)
public class ProductListConverter implements Converter, Serializable {

    @EJB
    private ProductEJB ejbBean;

    public ProductListConverter() {
    }

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (value.isEmpty()) {
            return null;
        }
        Integer id = new Integer(value);
        return ejbBean.findById(id);
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        if (value == null || value.toString().isEmpty()) {
            return "";
        }
        Integer id = ((Product) value).getId();
        return id.toString();
    }
}

Entity [License.java]
Java:
@Entity
@NamedQueries({
    @NamedQuery(name = "findAllLicenses", query = "SELECT c FROM License c"),
    @NamedQuery(name = "findLicenseById", query = "SELECT c FROM License c where c.id = :id")
})
public class License implements Serializable {

// Attribute ...
    @JoinColumn(name = "productId")
    @ManyToOne()
    @NotNull
    private Product product;
// ... Attribute, Getter, Setter ...

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof License)) {
            return false;
        }
        License other = (License) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "de.insecma.ilm.License[ id=" + id + " ]";
    }
}

EJB [LicenseEJB.java]
Java:
@Stateless
public class LicenseEJB {

    @PersistenceContext(unitName = "IlmPU")
    private EntityManager em;

    public void LicenseEJB() {
    }

    public List<License> findAll() {
        TypedQuery<License> query = em.createNamedQuery("findAllLicenses", License.class);
        return query.getResultList();
    }

    public Object findById(int id) {
        TypedQuery<License> query = em.createNamedQuery("findLicenseById", License.class);
        query.setParameter("id", id);
        return query.getSingleResult();
    }

    public License createLicense(License license) {
        em.persist(license);
        return license;
    }
}

Controller [LicenseController.java]
Java:
@ManagedBean
@RequestScoped
public class LicenseController implements Serializable {

    private static final Logger logger = Logger.getLogger("de.insecma.ilm");
    @EJB
    private LicenseEJB licenseEJB;
    private License license = new License();
    private List<License> licenseList = new ArrayList<License>();

    public String doCreateLicense() {
        this.license = licenseEJB.createLicense(license);
        return "licenses.xhtml?faces-redirect=true";
    }
// ... diverse Methoden
}

JSF-Seite [license.xhtml]
HTML:
<h:outputLabel value="Produkt*" for="product" />
  <h:selectOneMenu id="product" value="#{licenseController.license.product}" converter=" {productListConverter}">  
    <f:selectItem itemLabel="Bitte auswählen..." itemValue="" />  
    <f:selectItems value="#{productController.productList}" var="n" itemLabel="#{n.name}" itemValue="#{n}"/>
  </h:selectOneMenu>

Ich freue mich auf Eure Verbesserungsvorschläge und Tips.

Grüße
Bastian
 
Zuletzt bearbeitet:

Ähnliche Java Themen

Neue Themen


Oben