Hibernate代理和JSF选择一个菜单不起作用

时间:2015-04-02 13:08:27

标签: hibernate jsf jpa primefaces

config: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的控制器,一个拥有员工的JSF托管bean,并将一些方法(save(),remove()...)暴露给facelet视图:

@Named
@ViewScoped
public class EmployeeView implements Serializable {

    private Employee value;

    public Employee getValue() {
        return value;
    } 

    public void save() {
        //...
    }

    //init(), save(), remove()....
}

EmployeeCategory的EJB服务/ 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();
        }
    }
}

和xhtml页面,其中pf是primefaces组件的前缀:

<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被设置为“选择一个项目”,显示类别列表,我成功保存(id_category在数据库中设置)。

但是当我重新加载页面时,selectOneMenu再次显示“选择一个项目” 如果我选择相同的类别并再次保存,我会收到一条JSF消息:“类别:验证错误:值无效”,但如果我选择其他类别,我会成功保存。

我在这个问题上进行了很多调查,正如您在代码中看到的那样,我尝试在employeeCategoryItems中取消解析对象。我还更改了EmployeeCategory的equals()方法以了解HibernateProxy,我还尝试将所有注释移动到getter而不是field,并且还尝试在EmployeeService.findById()中的unproxyfied employee.category中调用EmployeeView (此处未显示)。所有这些解决方案都不起作用。

我找到的唯一解决方案是在@ApplicationScoped中设置employeeCategoryItems的范围,并在启动时加载此JSF托管bean,在这种情况下,在系统工作的员工之前首先在employeeCategoryItems中加载项目。但我无法将此解决方案应用于所有@ManyToOne ......

我不知道HibernateProxy和JSF(或PrimeFaces)之间出了什么问题,我不知道如何很好地解决它。

我唯一的选择就是让@ManyToOne处于EAGER模式

如果有人可以帮助我,那就太棒了

1 个答案:

答案 0 :(得分:0)

解决方案几乎就在那里,实际上我忘记了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()) ;
}

有了这个,解决方案正在运作