我有一个定制的JSF输入组件,名为inputPeriod,用于输入日期周期。每个时期都有一个从和到日期。组件的功能是通过Javascript实现的,Javascript生成一个JSON字符串并将其提交给组件。然后,输入组件使用默认转换器将JSON周期转换为Period对象列表,并将它们设置在托管bean上。
我遇到的问题的根源是,现在我想对EJB实体使用相同的组件。我有一个Banner实体与BannerPeriod实体的一对多关系。BannerPeriod实体的每个实例都有一个from(开始)和to(结束)日期,就像我在输入组件中使用的现有Period对象一样。我已经为此实现了一个新的转换器:
@ManagedBean
@RequestScoped
public class BannerPeriodConverter implements Converter {
@Override
public Object getAsObject(FacesContext fc, UIComponent uic, String str) {
if (str != null) {
Date from = null, to = null;
try {
JSONObject period = new JSONObject(str);
if (period.has("from")) {
from = new Date(period.getLong("from"));
}
if (period.has("to")) {
to = new Date(period.getLong("to"));
}
} catch (JSONException ex) {
throw new ConverterException(ex);
}
BannerPeriod bp = new BannerPeriod();
bp.setBegins(from);
bp.setEnds(to);
return bp;
}
return null;
}
@Override
public String getAsString(FacesContext fc, UIComponent uic, Object o) {
if (o != null && o instanceof BannerPeriod) {
BannerPeriod bp = (BannerPeriod) o;
JSONObject period = new JSONObject();
try {
period.put("from", bp.getBegins() != null ? bp.getBegins().getTime() : (Object) null);
period.put("to", bp.getEnds() != null ? bp.getEnds().getTime() : (Object) null);
} catch (JSONException ex) {
throw new ConverterException(ex);
}
return period.toString();
}
return "";
}
}
转换器与组件配合良好。我遇到的问题是,当我编辑一个横幅与现有的横幅时期,实体失去了他们的主键。因此,当我提交表单时,不是更新现有的周期,而是得到一个重复异常,或者再次创建现有的周期,在数据库中产生实际的重复。
所以我的问题是,我怎么做才能避免这种情况?我的猜测是,输入组件需要在现有实体上保留主键,但我如何才能最好地做到这一点呢?目前,输入组件与实体和我的EJB项目完全分离。输入组件甚至位于它自己的JSF项目中,而上面的转换器位于EJB项目中。默认情况下,输入组件使用一个普通的Period对象,该对象根本没有主键。它应该继续这样做。
或者这应该以其他方式解决?
在您的getAsObject()
中,您正在创建BannerPeriod
的完全非托管实例,而不是通过JPA直接从DB获取。
BannerPeriod bp = new BannerPeriod();
bp.setBegins(from);
bp.setEnds(to);
return bp;
持久化它当然会在数据库中创建一个新条目,因为它不是由JPA管理的。
基本上,您应该通过JPA从DB获取实例:
@EJB
private BannerPeriodService service;
public Object getAsObject(FacesContext context, UIComponent component, String value) {
// ...
return service.find(from, to);
}
其中BannerPeriodService#find()
通过EntityManager
获得所需的实例。
但是这种方法非常笨拙。对于来自DB的实体,规范的方法是使用它们的技术/自然标识符,例如自动生成的主键。
。(null/instanceof检查等省略):
@EJB
private BannerPeriodService service;
public Object getAsString(FacesContext context, UIComponent component, Object value) {
Long id = ((BannerPeriod) value).getId();
return id.toString();
}
public Object getAsObject(FacesContext context, UIComponent component, String value) {
Long id = Long.valueOf(value);
return service.find(id);
}
不需要弄乱JSON格式。如果您由于某些不明确的原因实际上需要JSON格式的它们,那么使用JSF转换器就走错了方向。
我知道在转换器中击中DB是一项相对昂贵的工作。在这种情况下,OmniFaces SelectItemsConverter
可能就是您要找的。