I tried to ask question here https://stackoverflow.com/questions/28493517/jsf-primefaces-editing-object-composed-of-2-tables but I'm afraid that it was not asked properly, that's way I am trying again, as simple as possible.
I have 2 tables:
nn: id, name
nn_descr: id, long_description
there is foreign key constraint between nn_descr.id and nn.id.
In general, one table contains data that can be searched and managed fairly quickly via db engine compared to nn_descr table.
And now I'd like to manage that data not in the way all CRUD creators do. I'd like to create 2 related records in my tables via one submit on JSF form consisting on text-box and text-area referenced to nn.name and descr.long_description respectively.
Without JPA I would insert record to the first table, read identity value and insert the data to the second table using identity value I already read.
With JPA I should somehow tell that engine to magically persist the data. And here is the point that I don't know how to do that. Whatever I read refers to simple CRUD which is just to be easily generated via my Netbeans.
I must admit I am getting lost with this JPA, facades and all the stuff generated but I am trying to learn this by doing simple application
I managed to have records in both tables updateable. But when I try to insert the data I get:
/nn/Create.xhtml @21,81 value="#{nnController.nnDescr.descr}": Target Unreachable, 'null' returned null
I must be doing something wrong, but I don;t know where to find the answer, tutorial, book, whatever that would go slightly deeper than simple example with one flat table and CRUD in that table.
here is my JSF (as basic as possible):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<ui:composition template="/template.xhtml">
<ui:define name="title">
<h:outputText value="#{boundle.CreateNnTitle}"></h:outputText>
</ui:define>
<ui:define name="body">
<h:panelGroup id="messagePanel" layout="block">
<h:messages errorStyle="color: red" infoStyle="color: green" layout="table"/>
</h:panelGroup>
<h:form>
<h:panelGrid columns="2">
<h:outputLabel value="#{boundle.CreateNnLabel_nnName}" for="nnName" />
<h:inputText id="nnName" value="#{nnController.selected.nnName}" title="#{boundle.CreateNnTitle_nnName}" />
<h:outputLabel value="#{boundle.CreateNnLabel_nnDescr}" for="nnDescr" />
<h:inputTextarea id="nnDescr" value="#{nnController.selected.nnDescr.descr}" />
</h:panelGrid>
<br />
<h:commandLink action="#{nnController.create}" value="#{boundle.CreateNnSaveLink}" />
<br />
<br />
<h:commandLink action="#{nnController.prepareList}" value="#{boundle.CreateNnShowAllLink}" immediate="true"/>
<br />
<br />
<h:link outcome="/index" value="#{boundle.CreateNnIndexLink}"/>
</h:form>
</ui:define>
</ui:composition>
and controller
package app;
import app.util.JsfUtil;
import app.util.PaginationHelper;
import java.io.Serializable;
import java.util.ResourceBundle;
import javax.ejb.EJB;
import javax.inject.Named;
import javax.enterprise.context.SessionScoped;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.faces.model.SelectItem;
@Named("nnController")
@SessionScoped
public class NnController implements Serializable {
private Nn current;
private NnDescr nnDescr;
private DataModel items = null;
@EJB
private app.NnFacade ejbFacade;
private PaginationHelper pagination;
private int selectedItemIndex;
public NnController() {
}
public Nn getSelected() {
if (current == null) {
current = new Nn();
selectedItemIndex = -1;
}
return current;
}
private NnFacade getFacade() {
return ejbFacade;
}
public PaginationHelper getPagination() {
if (pagination == null) {
pagination = new PaginationHelper(10) {
@Override
public int getItemsCount() {
return getFacade().count();
}
@Override
public DataModel createPageDataModel() {
return new ListDataModel(getFacade().findRange(new int[]{getPageFirstItem(), getPageFirstItem() + getPageSize()}));
}
};
}
return pagination;
}
public String prepareList() {
recreateModel();
return "List";
}
public String prepareView() {
current = (Nn) getItems().getRowData();
selectedItemIndex = pagination.getPageFirstItem() + getItems().getRowIndex();
return "View";
}
public String prepareCreate() {
current = new Nn();
selectedItemIndex = -1;
return "Create";
}
public String create() {
try {
getFacade().create(current);
JsfUtil.addSuccessMessage(ResourceBundle.getBundle("/Boundle").getString("NnCreated"));
return prepareCreate();
} catch (Exception e) {
JsfUtil.addErrorMessage(e, ResourceBundle.getBundle("/Boundle").getString("PersistenceErrorOccured"));
return null;
}
}
public String prepareEdit() {
current = (Nn) getItems().getRowData();
selectedItemIndex = pagination.getPageFirstItem() + getItems().getRowIndex();
return "Edit";
}
public String update() {
try {
getFacade().edit(current);
JsfUtil.addSuccessMessage(ResourceBundle.getBundle("/Boundle").getString("NnUpdated"));
return "View";
} catch (Exception e) {
JsfUtil.addErrorMessage(e, ResourceBundle.getBundle("/Boundle").getString("PersistenceErrorOccured"));
return null;
}
}
public String destroy() {
current = (Nn) getItems().getRowData();
selectedItemIndex = pagination.getPageFirstItem() + getItems().getRowIndex();
performDestroy();
recreatePagination();
recreateModel();
return "List";
}
public String destroyAndView() {
performDestroy();
recreateModel();
updateCurrentItem();
if (selectedItemIndex >= 0) {
return "View";
} else {
// all items were removed - go back to list
recreateModel();
return "List";
}
}
private void performDestroy() {
try {
getFacade().remove(current);
JsfUtil.addSuccessMessage(ResourceBundle.getBundle("/Boundle").getString("NnDeleted"));
} catch (Exception e) {
JsfUtil.addErrorMessage(e, ResourceBundle.getBundle("/Boundle").getString("PersistenceErrorOccured"));
}
}
private void updateCurrentItem() {
int count = getFacade().count();
if (selectedItemIndex >= count) {
// selected index cannot be bigger than number of items:
selectedItemIndex = count - 1;
// go to previous page if last page disappeared:
if (pagination.getPageFirstItem() >= count) {
pagination.previousPage();
}
}
if (selectedItemIndex >= 0) {
current = getFacade().findRange(new int[]{selectedItemIndex, selectedItemIndex + 1}).get(0);
}
}
public DataModel getItems() {
if (items == null) {
items = getPagination().createPageDataModel();
}
return items;
}
private void recreateModel() {
items = null;
}
private void recreatePagination() {
pagination = null;
}
public String next() {
getPagination().nextPage();
recreateModel();
return "List";
}
public String previous() {
getPagination().previousPage();
recreateModel();
return "List";
}
public SelectItem[] getItemsAvailableSelectMany() {
return JsfUtil.getSelectItems(ejbFacade.findAll(), false);
}
public SelectItem[] getItemsAvailableSelectOne() {
return JsfUtil.getSelectItems(ejbFacade.findAll(), true);
}
public Nn getNn(java.lang.Integer id) {
return ejbFacade.find(id);
}
public NnDescr getNnDescr() {
return nnDescr;
}
public void setNnDescr(NnDescr nnDescr) {
this.nnDescr = nnDescr;
}
@FacesConverter(forClass = Nn.class)
public static class NnControllerConverter implements Converter {
@Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
if (value == null || value.length() == 0) {
return null;
}
NnController controller = (NnController) facesContext.getApplication().getELResolver().
getValue(facesContext.getELContext(), null, "nnController");
return controller.getNn(getKey(value));
}
java.lang.Integer getKey(String value) {
java.lang.Integer key;
key = Integer.valueOf(value);
return key;
}
String getStringKey(java.lang.Integer value) {
StringBuilder sb = new StringBuilder();
sb.append(value);
return sb.toString();
}
@Override
public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Nn) {
Nn o = (Nn) object;
return getStringKey(o.getId());
} else {
throw new IllegalArgumentException("object " + object + " is of type " + object.getClass().getName() + "; expected type: " + Nn.class.getName());
}
}
}
}
Nn.java
package app;
import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.validation.constraints.Size;
@Entity
@Table(name = "nn")
@NamedQueries({
@NamedQuery(name = "Nn.findAll", query = "SELECT n FROM Nn n"),
@NamedQuery(name = "Nn.findById", query = "SELECT n FROM Nn n WHERE n.id = :id"),
@NamedQuery(name = "Nn.findByNnName", query = "SELECT n FROM Nn n WHERE n.nnName = :nnName")})
public class Nn implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Integer id;
@Size(max = 50)
@Column(name = "nn_name")
private String nnName;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "nn")
private NnDescr nnDescr;
public Nn() {
}
public Nn(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNnName() {
return nnName;
}
public void setNnName(String nnName) {
this.nnName = nnName;
}
public NnDescr getNnDescr() {
return nnDescr;
}
public void setNnDescr(NnDescr nnDescr) {
this.nnDescr = nnDescr;
}
@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Nn)) {
return false;
}
Nn other = (Nn) 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 "app.util.Nn[ id=" + id + " ]";
}
}
As you see the data is generated. I just modified generated JSF in order to have textarea for putting the description.
I am trying to learn and spend a lot of time trying to figure out this but nothing works. It cannot be that difficult. I just don;t get something obvious.
Thank you in advance for your help.
If possible I would greatly appreciate if somebody could post somewhere complete example of how to deal with problem like that.