config: WildFly 8.2 server, JPA 2.1, Hibernate 4.3.7, JSF 2.2, Mojarra 2.2.8, PrimeFaces 5.1
I want to tuned my JavaEE web application but when I turn @ManyToOne(fetch=FetchType.EAGER) to @ManyToOne(fetch=FetchType.LAZY) some issues appear
I want to edit employee category in a xhtml facelet
here is the model:
@Entity
public class Employee implements Serializable {
@Id
private Integer id;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "id_category", referencedColumnName = "id")
private EmployeeCategory category;
//...
}
@Entity
@Cacheable(true)
@Slf4j
public class EmployeeCategory implements Serializable {
@Id
private Integer id;
private String label;
@Override
public boolean equals(Object o) {
if (o==null) {
return false;
}
if (!testClasses(this,o)) {
return false;
}
final EmployeeCategory that = (EmployeeCategory) o;
return this==that || this.id==that.id;
}
private boolean testClasses(Object o1, Object o2) {
return getClass(o1).equals(getClass(o2));
}
private Class getClass(Object o) {
if(o instanceof HibernateProxy) {
return HibernateProxyHelper.getClassWithoutInitializingProxy(o);
} else {
return o.getClass();
}
}
//...
}
Here is the controller of Employee, a JSF managed bean owning an employee and exposing some methods (save(), remove()...) to the facelet view:
@Named
@ViewScoped
public class EmployeeView implements Serializable {
private Employee value;
public Employee getValue() {
return value;
}
public void save() {
//...
}
//init(), save(), remove()....
}
The EJB service/DAO of EmployeeCategory:
@Stateless
public class EmployeeCategoryService {
@PersistenceContext(unitName="myAppPU")
private EntityManager entityManager;
public List<EmployeeCategory> findAll() {
CriteriaQuery cq = entityManager.getCriteriaBuilder().createQuery();
cq.select(cq.from(EmployeeCategory.class));
Query q = entityManager.createQuery(cq) ;
return q.getResultList();
}
//...
}
the JSF managed been wich provide the list of all EmployeeCategory to the view and wich have the role of converter:
@Named
@ViewScoped
@Slf4j
public class EmployeeCategoryItems implements Converter, Serializable{
@Inject
private EmployeeCategoryService service;
private final String defaultSelection = "select one item";
private List<EmployeeCategory> values;
private Map<Integer,EmployeeCategory> index;
@PostConstruct
protected void init() {
values = service.findAll();
unproxyfied();
index = values.stream().collect(Collectors.toMap(c->c.getId(), c->c));
}
private void unproxyfied() {
values.stream().map(item -> {
if (item instanceof HibernateProxy) {
log.debug("unproxyfied() {} was HibernateProxy",item);
return (EmployeeCategory) ((HibernateProxy)item).getHibernateLazyInitializer().getImplementation();
} else {
return item;
}
}).collect(Collectors.toList()) ;
}
public String getDefaultSelection() {
return defaultSelection;
}
public List<EmployeeCategory> getValues() {
return values;
}
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value==null || value.equals(defaultSelection)) {
return null;
}
Integer id = Integer.parseInt(value);
EmployeeCategory category = index.get(id);
log.debug("getAsObject({}) return {}", value, category);
return entity;
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return null;
}
if(value instanceof HibernateProxy) {
Integer id = (Integer) ((HibernateProxy)value).getHibernateLazyInitializer().getIdentifier();
log.debug("getAsString({}) return {} HibernateProxy", value, id);
return id.toString();
} else {
Integer id = ((EmployeeCategory)value).getId();
log.debug("getAsString({}) return {}", value, id);
return id.toString();
}
}
}
and the xhtml page where pf is the prefix for primefaces components:
<h:form>
<pf:messages/>
<!-- ... -->
<pf:selectOneMenu value="#{employeeView.value.category}"
converter="#{employeeCategoryItems}">
<f:selectItem itemValue="#{null}"
itemLabel="#{employeeCategoryItems.defaultSelection}"
noSelectionOption="true"/>
<f:selectItems value="#{employeeCategoryItems.values}"
var="item" itemValue="#{item}" itemLabel="#{item.label}"/>
</pf:selectOneMenu>
<!-- ... -->
<pf:commandButton value="Save" action="#{employeeView.save()}" update="@form"/>
</h:form>
So the problem occure when I replace real EmployeeCategory object by HibernateProxy using lazy loading instead of eager
First everything goes right, in the page the selectOneMenu is set to "select one item", the list of categories is display, and I succed to save (the id_category is set in the database).
But when I reload the page the selectOneMenu display again "select one item" And if I choose the same category and save again I have a JSF message saying: "category: Validation Error: Value is not valid" but if I choose a different category I succeed to save.
I investigate a lot on this issue, as you can see in the code I try to unproxyfied object in the employeeCategoryItems. I also change the equals() method of EmployeeCategory to be aware of HibernateProxy, I also try to move all annotations to getters instead of field, and also try to unproxyfied employee.category in the EmployeeService.findById() which is call in the EmployeeView (not shown here). All those solutions doesn't work.
The only solution I found is to set the scope of employeeCategoryItems at @ApplicationScoped and load this JSF managed bean at startup, in this case, when items are first loaded in the employeeCategoryItems before in the employee the system works. But I can't appply this solution to all @ManyToOne...
I don't know what is going wrong between HibernateProxy and JSF (or PrimeFaces), and I don't know how to nicely solve it.
The only soution I have is to let @ManyToOne in an EAGER mode
If some one could help me it would be great