“null Converter”的转换错误设置值- 为什么我在 JSF 中需要转换器?

发布于 2024-10-12 22:27:58 字数 1793 浏览 3 评论 0原文

我在理解如何在 JSF 2 中有效地使用 POJO/实体的选择时遇到问题。例如,我尝试通过以下下拉列表选择一个 Warehouse 实体:

<h:selectOneMenu value="#{bean.selectedWarehouse}">
    <f:selectItem itemLabel="Choose one .." itemValue="#{null}" />
    <f:selectItems value="#{bean.availableWarehouses}" />
</h:selectOneMenu>

以及下面的托管 bean:

@Named
@ViewScoped
public class Bean {

    private Warehouse selectedWarehouse;
    private List<SelectItem> availableWarehouses;

    // ...

    @PostConstruct
    public void init() {
        // ...

        availableWarehouses = new ArrayList<>();

        for (Warehouse warehouse : warehouseService.listAll()) {
            availableWarehouses.add(new SelectItem(warehouse, warehouse.getName()));
        }
    }

    // ...
}

请注意,我使用整个 Warehouse 实体作为 <代码>选择项目。

当我提交表单时,失败并显示以下消息:

“null Converter”的设置值“com.example.Warehouse@cafebabe”发生转换错误。

我希望当我将托管 bean 包装在 SelectItem 中时,JSF 能够为它设置正确的 Warehouse 对象。将我的实体包装在 SelectItem 中意味着跳过为我的实体创建 Converter

每当我想要使用 中的实体时,我真的必须使用 Converter 吗?对于 JSF 来说,应该可以从可用项目列表中提取选定的项目。如果我真的必须使用转换器,那么实际的做法是什么?到目前为止,我想到了这一点:

  1. 为实体创建一个 Converter 实现。
  2. 重写getAsString()。我认为我不需要这个,因为 SelectItem 的 label 属性将用于显示下拉选项标签。
  3. 重写getAsObject()。我认为这将用于返回正确的 SelectItem 或实体,具体取决于托管 bean 中定义的选定字段的类型。

getAsObject() 让我感到困惑。做到这一点的有效方法是什么?有了字符串值,如何获取关联的实体对象?我应该根据字符串值从服务对象中查询实体对象并返回实体吗?或者也许我可以以某种方式访问​​形成选择项的实体列表,循环它们以找到正确的实体,然后返回该实体?

这的正常做法是什么?

I have problems understanding how to use selection in JSF 2 with POJO/entity effectively. For example, I'm trying to select a Warehouse entity via the below dropdown:

<h:selectOneMenu value="#{bean.selectedWarehouse}">
    <f:selectItem itemLabel="Choose one .." itemValue="#{null}" />
    <f:selectItems value="#{bean.availableWarehouses}" />
</h:selectOneMenu>

And the below managed bean:

@Named
@ViewScoped
public class Bean {

    private Warehouse selectedWarehouse;
    private List<SelectItem> availableWarehouses;

    // ...

    @PostConstruct
    public void init() {
        // ...

        availableWarehouses = new ArrayList<>();

        for (Warehouse warehouse : warehouseService.listAll()) {
            availableWarehouses.add(new SelectItem(warehouse, warehouse.getName()));
        }
    }

    // ...
}

Notice that I use the whole Warehouse entity as the value of SelectItem.

When I submit the form, this fails with the following faces message:

Conversion Error setting value 'com.example.Warehouse@cafebabe' for 'null Converter'.

I was hoping that JSF could just set the correct Warehouse object to my managed bean when I wrap it in a SelectItem. Wrapping my entity inside the SelectItem was meant to skip creating a Converter for my entity.

Do I really have to use a Converter whenever I want to make use of entities in my <h:selectOneMenu>? It should for JSF be possible to just extract the selected item from the list of available items. If I really have to use a converter, what is the practical way of doing it? So far I came up to this:

  1. Create a Converter implementation for the entity.
  2. Overriding the getAsString(). I think I don't need this since the label property of the SelectItem will be used to display the dropdown option label.
  3. Overriding the getAsObject(). I think this will be used to return the correct SelectItem or entity depending on the type of the selected field defined in the managed bean.

The getAsObject() confuses me. What is the efficient way to do this? Having the string value, how do I get the associated entity object? Should I query the entity object from the service object based on the string value and return the entity? Or perhaps somehow I can access the list of the entities that form the selection items, loop them to find the correct entity, and return the entity?

What is the normal approach of this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

第几種人 2024-10-19 22:27:59

带有 ABaseEntity 和标识符的 JSF 通用转换器示例:

ABaseEntity.java

public abstract class ABaseEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    public abstract Long getIdentifier();
}

SelectItemToEntityConverter.java

@FacesConverter(value = "SelectItemToEntityConverter")
public class SelectItemToEntityConverter implements Converter {

    @Override
    public Object getAsObject(FacesContext ctx, UIComponent comp, String value) {
        Object o = null;
        if (!(value == null || value.isEmpty())) {
            o = this.getSelectedItemAsEntity(comp, value);
        }
        return o;
    }

    @Override
    public String getAsString(FacesContext ctx, UIComponent comp, Object value) {
        String s = "";
        if (value != null) {
            s = ((ABaseEntity) value).getIdentifier().toString();
        }
        return s;
    }

    private ABaseEntity getSelectedItemAsEntity(UIComponent comp, String value) {
        ABaseEntity item = null;

        List<ABaseEntity> selectItems = null;
        for (UIComponent uic : comp.getChildren()) {
            if (uic instanceof UISelectItems) {
                Long itemId = Long.valueOf(value);
                selectItems = (List<ABaseEntity>) ((UISelectItems) uic).getValue();

                if (itemId != null && selectItems != null && !selectItems.isEmpty()) {
                    Predicate<ABaseEntity> predicate = i -> i.getIdentifier().equals(itemId);
                    item = selectItems.stream().filter(predicate).findFirst().orElse(null);
                }
            }
        }

        return item;
    }
}

和用法:

<p:selectOneMenu id="somItems" value="#{exampleBean.selectedItem}" converter="SelectItemToEntityConverter">
    <f:selectItem itemLabel="< select item >" itemValue="#{null}"/>
    <f:selectItems value="#{exampleBean.availableItems}" var="item" itemLabel="${item.identifier}" itemValue="#{item}"/>
</p:selectOneMenu>

Example of JSF generic converter with ABaseEntity and identifier:

ABaseEntity.java

public abstract class ABaseEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    public abstract Long getIdentifier();
}

SelectItemToEntityConverter.java

@FacesConverter(value = "SelectItemToEntityConverter")
public class SelectItemToEntityConverter implements Converter {

    @Override
    public Object getAsObject(FacesContext ctx, UIComponent comp, String value) {
        Object o = null;
        if (!(value == null || value.isEmpty())) {
            o = this.getSelectedItemAsEntity(comp, value);
        }
        return o;
    }

    @Override
    public String getAsString(FacesContext ctx, UIComponent comp, Object value) {
        String s = "";
        if (value != null) {
            s = ((ABaseEntity) value).getIdentifier().toString();
        }
        return s;
    }

    private ABaseEntity getSelectedItemAsEntity(UIComponent comp, String value) {
        ABaseEntity item = null;

        List<ABaseEntity> selectItems = null;
        for (UIComponent uic : comp.getChildren()) {
            if (uic instanceof UISelectItems) {
                Long itemId = Long.valueOf(value);
                selectItems = (List<ABaseEntity>) ((UISelectItems) uic).getValue();

                if (itemId != null && selectItems != null && !selectItems.isEmpty()) {
                    Predicate<ABaseEntity> predicate = i -> i.getIdentifier().equals(itemId);
                    item = selectItems.stream().filter(predicate).findFirst().orElse(null);
                }
            }
        }

        return item;
    }
}

And usage:

<p:selectOneMenu id="somItems" value="#{exampleBean.selectedItem}" converter="SelectItemToEntityConverter">
    <f:selectItem itemLabel="< select item >" itemValue="#{null}"/>
    <f:selectItems value="#{exampleBean.availableItems}" var="item" itemLabel="${item.identifier}" itemValue="#{item}"/>
</p:selectOneMenu>
牛↙奶布丁 2024-10-19 22:27:59

我通过简单地将

I achieved this by simply changing the type for "value" in <h:selectOneMenu.. to a String.

っ〆星空下的拥抱 2024-10-19 22:27:58

简介

JSF 生成 HTML。 HTML 在 Java 术语中基本上是一个大的String。要在 HTML 中表示 Java 对象,必须将它们转换为 String。此外,当提交 HTML 表单时,提交的值将被视为 HTTP 请求参数中的 String。在幕后,JSF 从 HttpServletRequest#getParameter() 返回String

在非标准 Java 对象(即不是 EL 具有内置转换的 StringNumberBooleanJSF 为其提供内置的日期/LocalDate/ZonedDateTime /convertdatetime" rel="nofollow noreferrer"> 标记),您确实必须提供自定义 转换器SelectItem 根本没有特殊用途。它只是 JSF 1.x 的遗留物,当时无法直接向 提供 List 等。它对于标签和转换也没有特殊处理。

getAsString()

您需要实现 getAsString() 方法,以便所需的 Java 对象以唯一的形式表示String 表示形式,可用作 HTTP 请求参数。通常,这里使用技术ID(数据库主键)。

public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
    if (modelValue == null) {
        return ""; // Never return null here!
    }

    if (modelValue instanceof Warehouse) {
        return String.valueOf(((Warehouse) modelValue).getId());
    } else {
        throw new ConverterException(new FacesMessage(modelValue + " is not a valid Warehouse"));
    }
}

请注意,在 null/空模型值的情况下返回空字符串非常重要,并且是 javadoc:

返回:如果 value 为 null,则返回零长度字符串,否则返回转换结果

否则生成的 将没有 value 属性,并且默认发送项目标签返回到 getAsObject() 中。另请参阅使用“请选择” ap:selectOneMenu 内具有 null/空值的 f:selectItem。

getAsObject()

您需要实现 getAsObject() 的方式完全 String<由 getAsString() 返回的 /code> 表示形式可以转换回完全getAsString() 中指定为 modelValue 的相同 Java 对象)

public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
    if (submittedValue == null || submittedValue.isEmpty()) {
        return null;
    }

    try {
        return warehouseService.find(Long.valueOf(submittedValue));
    } catch (NumberFormatException e) {
        throw new ConverterException(new FacesMessage(submittedValue + " is not a valid Warehouse ID"), e);
    }
}

换句话说,您必须在技术上能够将返回的对象作为 getAsString()modelValue 参数传回,然后将获取的字符串作为 subscribedValue 传回无限循环中 getAsObject() 的 code> 参数。

用法

最后只需用 注释 Converter @FacesConverter 来挂钩有问题的对象类型,当 Warehouse 类型出现时,JSF 将自动处理转换:

@FacesConverter(forClass=Warehouse.class)

这就是“规范的”JSF 方法。毕竟它不是很有效,因为它确实也可以从 中获取项目。但 Converter 最重要的一点是它返回一个唯一 String 表示形式,以便 Java 对象可以通过简单的 < code>String 适合在 HTTP 和 HTML 中传递。

基于 toString()

JSF 实用程序库的通用转换器 OmniFaces 有一个 SelectItemsConverter 其基于 toString() 结果工作实体的。这样您就不再需要摆弄 getAsObject() 和昂贵的业务/数据库操作。有关一些具体的使用示例,另请参阅showcase

要使用它,只需按如下方式注册它:

<h:selectOneMenu ... converter="omnifaces.SelectItemsConverter">

并确保 Warehouse 实体的 toString() 返回该实体的唯一表示形式。例如,您可以直接返回 ID:

@Override
public String toString() {
    return String.valueOf(id);
}

或者更具可读性/可重用性的内容:

@Override
public String toString() {
    return "Warehouse[id=" + id + "]";
}

另请参阅:


与问题无关,因为 JSF 2.0 不再明确要求将 List 作为 < code>值。只需一个 List 就足够了。

<h:selectOneMenu value="#{bean.selectedWarehouse}">
    <f:selectItem itemLabel="Choose one .." itemValue="#{null}" />
    <f:selectItems value="#{bean.availableWarehouses}" var="warehouse"
        itemLabel="#{warehouse.name}" itemValue="#{warehouse}" />
</h:selectOneMenu>
private Warehouse selectedWarehouse;
private List<Warehouse> availableWarehouses;

Introduction

JSF generates HTML. HTML is in Java terms basically one large String. To represent Java objects in HTML, they have to be converted to String. Also, when a HTML form is submitted, the submitted values are treated as String in the HTTP request parameters. Under the covers, JSF extracts them from the HttpServletRequest#getParameter() which returns String.

To convert between a non-standard Java object (i.e. not a String, Number or Boolean for which EL has builtin conversions, or Date/LocalDate/ZonedDateTime for which JSF provides builtin <f:convertDateTime> tag), you really have to supply a custom Converter. The SelectItem has no special purpose at all. It's just a leftover from JSF 1.x when it wasn't possible to supply e.g. List<Warehouse> directly to <f:selectItems>. It has also no special treatment as to labels and conversion.

getAsString()

You need to implement getAsString() method in such way that the desired Java object is been represented in an unique String representation which can be used as HTTP request parameter. Normally, the technical ID (the database primary key) is used here.

public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
    if (modelValue == null) {
        return ""; // Never return null here!
    }

    if (modelValue instanceof Warehouse) {
        return String.valueOf(((Warehouse) modelValue).getId());
    } else {
        throw new ConverterException(new FacesMessage(modelValue + " is not a valid Warehouse"));
    }
}

Note that returning an empty string in case of a null/empty model value is significant and required by the javadoc:

Returns: a zero-length String if value is null, otherwise the result of the conversion

Otherwise the generated <option> will not have a value attribute and by default send the item label back into getAsObject(). See also Using a "Please select" f:selectItem with null/empty value inside a p:selectOneMenu.

getAsObject()

You need to implement getAsObject() in such way that exactly that String representation as returned by getAsString() can be converted back to exactly the same Java object specified as modelValue in getAsString().

public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
    if (submittedValue == null || submittedValue.isEmpty()) {
        return null;
    }

    try {
        return warehouseService.find(Long.valueOf(submittedValue));
    } catch (NumberFormatException e) {
        throw new ConverterException(new FacesMessage(submittedValue + " is not a valid Warehouse ID"), e);
    }
}

In other words, you must be technically able to pass back the returned object as modelValue argument of getAsString() and then pass back the obtained string as submittedValue argument of getAsObject() in an infinite loop.

Usage

Finally just annotate the Converter with @FacesConverter to hook on the object type in question, JSF will then automatically take care of conversion when Warehouse type ever comes into the picture:

@FacesConverter(forClass=Warehouse.class)

That was the "canonical" JSF approach. It's after all not very effective as it could indeed also just have grabbed the item from the <f:selectItems>. But the most important point of a Converter is that it returns an unique String representation, so that the Java object could be identified by a simple String suitable for passing around in HTTP and HTML.

Generic converter based on toString()

JSF utility library OmniFaces has a SelectItemsConverter which works based on toString() outcome of the entity. This way you do not need to fiddle with getAsObject() and expensive business/database operations anymore. For some concrete use examples, see also the showcase.

To use it, just register it as below:

<h:selectOneMenu ... converter="omnifaces.SelectItemsConverter">

And make sure that the toString() of your Warehouse entity returns an unique representation of the entity. You could for instance directly return the ID:

@Override
public String toString() {
    return String.valueOf(id);
}

Or something more readable/reusable:

@Override
public String toString() {
    return "Warehouse[id=" + id + "]";
}

See also:


Unrelated to the problem, since JSF 2.0 it's not explicitly required anymore to have a List<SelectItem> as <f:selectItem> value. Just a List<Warehouse> would also suffice.

<h:selectOneMenu value="#{bean.selectedWarehouse}">
    <f:selectItem itemLabel="Choose one .." itemValue="#{null}" />
    <f:selectItems value="#{bean.availableWarehouses}" var="warehouse"
        itemLabel="#{warehouse.name}" itemValue="#{warehouse}" />
</h:selectOneMenu>
private Warehouse selectedWarehouse;
private List<Warehouse> availableWarehouses;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文