Hibernate 代理和 JSF select 一个菜单不工作
Hibernate proxies and JSF select one menu not working
配置:WildFly 8.2 服务器、JPA 2.1、Hibernate 4.3.7、JSF 2.2、Mojarra 2.2.8、PrimeFaces 5.1
我想调整我的 JavaEE Web 应用程序,但是当我将 @ManyToOne(fetch=FetchType.EAGER) 转换为 @ManyToOne(fetch=FetchType.LAZY) 时,出现了一些问题
我想在 xhtml facelet 中编辑员工类别
这是模型:
@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();
}
}
//...
}
这是 Employee 的控制器,它是一个拥有员工并将一些方法(save()、remove()...)暴露给 facelet 视图的 JSF 托管 bean:
@Named
@ViewScoped
public class EmployeeView implements Serializable {
private Employee value;
public Employee getValue() {
return value;
}
public void save() {
//...
}
//init(), save(), remove()....
}
EmployeeCategory 的 EJB service/DAO:
@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();
}
//...
}
管理的 JSF 向视图提供所有 EmployeeCategory 的列表,并且具有转换器的作用:
@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();
}
}
}
以及 pf 是 primefaces 组件前缀的 xhtml 页面:
<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>
所以当我使用延迟加载而不是急切的方式通过 HibernateProxy 替换真实的 EmployeeCategory 对象时出现问题
首先一切顺利,在页面中selectOneMenu设置为"select one item",显示类别列表,我保存成功(数据库中设置id_category)。
但是当我重新加载页面时,selectOneMenu 再次显示 "select one item"
如果我选择相同的类别并再次保存,我会收到一条 JSF 消息:"category: Validation Error: Value is not valid" 但如果我选择不同的类别,我会成功保存。
我在这个问题上进行了很多调查,正如您在代码中看到的那样,我尝试在 employeeCategoryItems 中取消代理对象。我还更改了 EmployeeCategory 的 equals() 方法以了解 HibernateProxy,我还尝试将所有注释移动到 getter 而不是字段,并且还尝试在 EmployeeService.findById() 中取消代理 employee.category 这是在 EmployeeView 中调用(此处未显示)。所有这些解决方案都不起作用。
我找到的唯一解决方案是将 employeeCategoryItems 的范围设置为 @ApplicationScoped 并在启动时加载此 JSF 托管 bean,在这种情况下,当项目首先加载到 employeeCategoryItems 之前在系统工作之前。但我无法将此解决方案应用于所有@ManyToOne...
不知道HibernateProxy和JSF(或者PrimeFaces)之间到底出了什么问题,也不知道怎么才能很好的解决
我唯一的办法就是让@ManyToOne 处于 EAGER 模式
如果有人能帮助我,那就太好了
解决方案已经差不多了,事实上我忘记了 EmployeeCategoryItems.unproxyfied() 方法中的一段代码
private void unproxyfied() {
values = values.stream().map(item -> {
if (item instanceof HibernateProxy) {
return (EmployeeCategory) ((HibernateProxy)item).getHibernateLazyInitializer().getImplementation();
} else {
return item;
}
}).collect(Collectors.toList()) ;
}
有了这个,解决方案就可以了
配置:WildFly 8.2 服务器、JPA 2.1、Hibernate 4.3.7、JSF 2.2、Mojarra 2.2.8、PrimeFaces 5.1
我想调整我的 JavaEE Web 应用程序,但是当我将 @ManyToOne(fetch=FetchType.EAGER) 转换为 @ManyToOne(fetch=FetchType.LAZY) 时,出现了一些问题
我想在 xhtml facelet 中编辑员工类别
这是模型:
@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();
}
}
//...
}
这是 Employee 的控制器,它是一个拥有员工并将一些方法(save()、remove()...)暴露给 facelet 视图的 JSF 托管 bean:
@Named
@ViewScoped
public class EmployeeView implements Serializable {
private Employee value;
public Employee getValue() {
return value;
}
public void save() {
//...
}
//init(), save(), remove()....
}
EmployeeCategory 的 EJB service/DAO:
@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();
}
//...
}
管理的 JSF 向视图提供所有 EmployeeCategory 的列表,并且具有转换器的作用:
@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();
}
}
}
以及 pf 是 primefaces 组件前缀的 xhtml 页面:
<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>
所以当我使用延迟加载而不是急切的方式通过 HibernateProxy 替换真实的 EmployeeCategory 对象时出现问题
首先一切顺利,在页面中selectOneMenu设置为"select one item",显示类别列表,我保存成功(数据库中设置id_category)。
但是当我重新加载页面时,selectOneMenu 再次显示 "select one item" 如果我选择相同的类别并再次保存,我会收到一条 JSF 消息:"category: Validation Error: Value is not valid" 但如果我选择不同的类别,我会成功保存。
我在这个问题上进行了很多调查,正如您在代码中看到的那样,我尝试在 employeeCategoryItems 中取消代理对象。我还更改了 EmployeeCategory 的 equals() 方法以了解 HibernateProxy,我还尝试将所有注释移动到 getter 而不是字段,并且还尝试在 EmployeeService.findById() 中取消代理 employee.category 这是在 EmployeeView 中调用(此处未显示)。所有这些解决方案都不起作用。
我找到的唯一解决方案是将 employeeCategoryItems 的范围设置为 @ApplicationScoped 并在启动时加载此 JSF 托管 bean,在这种情况下,当项目首先加载到 employeeCategoryItems 之前在系统工作之前。但我无法将此解决方案应用于所有@ManyToOne...
不知道HibernateProxy和JSF(或者PrimeFaces)之间到底出了什么问题,也不知道怎么才能很好的解决
我唯一的办法就是让@ManyToOne 处于 EAGER 模式
如果有人能帮助我,那就太好了
解决方案已经差不多了,事实上我忘记了 EmployeeCategoryItems.unproxyfied() 方法中的一段代码
private void unproxyfied() {
values = values.stream().map(item -> {
if (item instanceof HibernateProxy) {
return (EmployeeCategory) ((HibernateProxy)item).getHibernateLazyInitializer().getImplementation();
} else {
return item;
}
}).collect(Collectors.toList()) ;
}
有了这个,解决方案就可以了