3

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

我遇到的问题的根源是,现在我想将相同的组件与 EJB 实体一起使用。我有一个与 BannerPeriod 实体具有一对多关系的 Banner 实体。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 对象,该对象根本没有主键。它应该继续这样做。

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

4

1 回答 1

1

在您getAsObject()中,您正在创建一个完全非托管的实例,BannerPeriod而不是通过 JPA 直接从 DB 获取一个。

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

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

基本上,您应该改为通过 JPA 从数据库获取实例:

@EJB
private BannerPeriodService service;

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

    return service.find(from, to);
}

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

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

例如(省略 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 转换器会走错方向。

我知道在转换器中访问数据库是一项相对昂贵的工作。在这种情况下,OmniFacesSelectItemsConverter可能就是您正在寻找的。

于 2013-03-13T14:30:12.307 回答