如何在持久化由JSF组件转换器创建的EJB实体时避免重复的DB内容?

时间:2013-03-11 08:42:26

标签: jsf jsf-2 ejb ejb-3.0

我有一个名为inputPeriod的自定义JSF输入组件,用于输入日期周期。每个时期都有来自和迄今。使用Javascript实现组件的功能,Javascript生成JSON字符串并将其提交给组件。然后,输入组件使用默认转换器,该转换器将JSON句点转换为Period对象列表,并在我的托管bean上设置它们。这一切都很完美。

我遇到的问题的根源是,现在我想在EJB实体中使用相同的组件。我有一个Banner实体,与BannerPeriod实体有一对多的关系。 BannerPeriod实体的每个实例都采用from(开始)和(结束)日期,就像我在输入组件中使用的现有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对象,该对象根本没有主键。它应该继续这样做。

或许这应该以其他方式解决?

1 个答案:

答案 0 :(得分:1)

getAsObject()中,您正在创建一个完全不受管理的BannerPeriod实例,而不是通过JPA直接从数据库中获取。{/ p>

BannerPeriod bp = new BannerPeriod();
bp.setBegins(from);
bp.setEnds(to);
return bp;

坚持使用它当然会在DB中创建一个新条目,因为它不受JPA管理。

基本上,您应该通过JPA从DB获取实例:

@EJB
private BannerPeriodService service;

public Object getAsObject(FacesContext context, UIComponent component, String value) {
    // ...

    return service.find(from, to);
}

其中BannerPeriodService#find()通过EntityManager获得所需的实例。

但这种做法非常笨拙。对于来自数据库的实体,规范方法是使用它们的技术/自然标识符,例如自动生成的主键。

E.g。 (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可能正是您所寻找的。