如何从数据库中填充h:selectOneMenu的选项?


72

我正在创建一个Web应用程序,您必须在其中从数据库读取对象/实体的列表,并将其填充到JSF中<h:selectOneMenu>。我无法对此进行编码。有人可以告诉我该怎么做吗?

我知道如何List<User>从数据库中获取一个。我需要知道的是,如何在中填充此列表<h:selectOneMenu>

<h:selectOneMenu value="#{bean.name}">
    ...?
</h:selectOneMenu>

Answers:


179

根据您的问题历史记录,您正在使用JSF2.x。因此,这是针对JSF 2.x的答案。在JSF 1.x中,您将不得不在丑陋的SelectItem实例中包装商品值/标签。幸运的是,在JSF 2.x中不再需要此功能。


基本例子

要直接回答您的问题,只需使用<f:selectItems>谁的value指向List<T>在bean的(后)构造过程中从数据库保留的属性的点即可。这是一个基本的启动示例,假设该示例T实际代表String

<h:selectOneMenu value="#{bean.name}">
    <f:selectItems value="#{bean.names}" />
</h:selectOneMenu>

@ManagedBean
@RequestScoped
public class Bean {

    private String name;
    private List<String> names; 

    @EJB
    private NameService nameService;

    @PostConstruct
    public void init() {
        names = nameService.list();
    }

    // ... (getters, setters, etc)
}

就那么简单。实际上,TtoString()将用于代表下拉项标签和值。因此,当您不List<String>使用诸如此类的复杂对象的列表,List<SomeEntity>并且尚未覆盖类的toString()方法时,您将看到com.example.SomeEntity@hashcode项目值。请参阅下一节如何正确解决它。

还请注意,用于<f:selectItems>价值的Bean不一定必须与用于<h:selectOneMenu>价值的Bean相同。只要这些值实际上是应用程序范围的常量,而您在应用程序启动期间仅需加载一次,则此功能将非常有用。然后,您可以使其成为应用程序范围的bean的属性。

<h:selectOneMenu value="#{bean.name}">
    <f:selectItems value="#{data.names}" />
</h:selectOneMenu>

复杂对象作为可用项目

每当T涉及复杂对象(如javabean),例如UserString属性为时name,您就可以使用该var属性来获取迭代变量,而该迭代变量又可用于itemValue和/或itemLabel属性(如果省略itemLabel,则标签与值相同)。

范例1:

<h:selectOneMenu value="#{bean.userName}">
    <f:selectItems value="#{bean.users}" var="user" itemValue="#{user.name}" />
</h:selectOneMenu>

private String userName;
private List<User> users;

@EJB
private UserService userService;

@PostConstruct
public void init() {
    users = userService.list();
}

// ... (getters, setters, etc)

或当它具有要设置为项目值的Long属性时id

范例2:

<h:selectOneMenu value="#{bean.userId}">
    <f:selectItems value="#{bean.users}" var="user" itemValue="#{user.id}" itemLabel="#{user.name}" />
</h:selectOneMenu>

private Long userId;
private List<User> users;

// ... (the same as in previous bean example)

复杂对象作为选定项目

每当您希望将其也设置T为Bean中的一个属性并T表示时User,那么您就需要烘焙一个Converter可在之间进行转换的自定义User和一个唯一的字符串表示形式(可以是该id属性)。请注意,itemValue必须代表复杂对象本身,确切来说是需要设置为选择组件的类型value

<h:selectOneMenu value="#{bean.user}" converter="#{userConverter}">
    <f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>

private User user;
private List<User> users;

// ... (the same as in previous bean example)

@ManagedBean
@RequestScoped
public class UserConverter implements Converter {

    @EJB
    private UserService userService;

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

        try {
            return userService.find(Long.valueOf(submittedValue));
        } catch (NumberFormatException e) {
            throw new ConverterException(new FacesMessage(String.format("%s is not a valid User ID", submittedValue)), e);
        }
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
        if (modelValue == null) {
            return "";
        }

        if (modelValue instanceof User) {
            return String.valueOf(((User) modelValue).getId());
        } else {
            throw new ConverterException(new FacesMessage(String.format("%s is not a valid User", modelValue)), e);
        }
    }

}

(请注意,Converter为了能够@EJB在JSF转换器中注入,它有点笨拙;通常会以标记为@FacesConverter(forClass=User.class)但是不幸的是不允许@EJB注入

不要忘记,以确保复杂的对象类equals()hashCode()正确实施,否则,JSF将在呈现无法显示预选项目(S),你会在提交脸验证错误:值无效

public class User {

    private Long id;

    @Override
    public boolean equals(Object other) {
        return (other != null && getClass() == other.getClass() && id != null)
            ? id.equals(((User) other).id)
            : (other == this);
    }

    @Override
    public int hashCode() {
        return (id != null) 
            ? (getClass().hashCode() + id.hashCode())
            : super.hashCode();
    }

}

具有通用转换器的复杂对象

前往此答案:使用Java Generics为实体实现转换器


没有自定义转换器的复杂对象

JSF实用程序库OmniFaces开箱即用提供了一个特殊的转换器,使您可以使用复杂的对象,<h:selectOneMenu>而无需创建自定义转换器。该SelectItemsConverter会简单地做基于易于获得的项目转换<f:selectItem(s)>

<h:selectOneMenu value="#{bean.user}" converter="omnifaces.SelectItemsConverter">
    <f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>

也可以看看:


很好的解释!在JSF中,转换器不是注入目标是一个麻烦的目标。我相信缝3便携式扩展将解决此问题。这也是对路线图JSF 2.2 / Java EE的7
迈克·布朗

@Makky:我不知道你在说什么。我无处自己明确初始化列表。此示例假定具有EJB和JPA的标准Java EE堆栈。这似乎也不是问题的话题。按下Ask Question以提出一个明确而具体的问题。
BalusC 2013年

嗨BalusC我提了一个问题,请你,请帮助stackoverflow.com/questions/16062703/...
Makky

为什么这些注释名称出现在示例中?我应该使用注释吗?为什么?
糊糊的

1
@ZiMtyth从上至下阅读“将复杂对象作为所选项目”部分。对于将来的“什么都没有发生”的问题,请访问stackoverflow.com/q/2118656
BalusC

8

查看页面

<h:selectOneMenu id="selectOneCB" value="#{page.selectedName}">
     <f:selectItems value="#{page.names}"/>
</h:selectOneMenu>

菜豆

   List<SelectItem> names = new ArrayList<SelectItem>();

   //-- Populate list from database

   names.add(new SelectItem(valueObject,"label"));

   //-- setter/getter accessor methods for list

要显示特定的选定记录,它必须是列表中的值之一。


您的代码中的valueObject是什么?是下拉列表中将列出的记录列表吗?
2011年

它是在选择选项之一(此处为selectedName)时将在Bean中设置的对象。value(valueObject)可能是您想要在后端使用的一些ID,但必须使用标签在屏幕上显示名称。
Nayan Wadekar

3

自行设计的通用转换器,可将复杂对象作为选定项

Balusc对此主题给出了非常有用的概述答案。但是,他没有提供另一种选择:自己动手的通用转换器,将复杂对象作为选定项处理。如果要处理所有情况,这是非常复杂的,但是对于简单情况,这非常简单。

下面的代码包含此类转换器的示例。通过查看包含对象的组件的子代,它的工作原理与OmniFaces SelectItemsConverter相同UISelectItem(s)。不同之处在于,它仅处理到实体对象的简单集合或字符串的绑定。它不处理项目组,SelectItems的集合,数组以及可能还有很多其他事情。

组件绑定到的实体必须实现该IdObject接口。(这可以通过其他方式解决,例如使用toString。)

请注意,实体必须equals以两个具有相同ID的实体相等的方式实现。

使用它唯一需要做的就是将其指定为select组件上的转换器,绑定到一个实体属性和一个可能的实体列表:

<h:selectOneMenu value="#{bean.user}" converter="selectListConverter">
  <f:selectItem itemValue="unselected" itemLabel="Select user..."/>
  <f:selectItem itemValue="empty" itemLabel="No user"/>
  <f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>

转换器:

/**
 * A converter for select components (those that have select items as children).
 * 
 * It convertes the selected value string into one of its element entities, thus allowing
 * binding to complex objects.
 * 
 * It only handles simple uses of select components, in which the value is a simple list of
 * entities. No ItemGroups, arrays or other kinds of values.
 * 
 * Items it binds to can be strings or implementations of the {@link IdObject} interface.
 */
@FacesConverter("selectListConverter")
public class SelectListConverter implements Converter {

  public static interface IdObject {
    public String getDisplayId();
  }

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

    return component.getChildren().stream()
      .flatMap(child -> getEntriesOfItem(child))
      .filter(o -> value.equals(o instanceof IdObject ? ((IdObject) o).getDisplayId() : o))
      .findAny().orElse(null);
  }

  /**
   * Gets the values stored in a {@link UISelectItem} or a {@link UISelectItems}.
   * For other components returns an empty stream.
   */
  private Stream<?> getEntriesOfItem(UIComponent child) {
    if (child instanceof UISelectItem) {
      UISelectItem item = (UISelectItem) child;
      if (!item.isNoSelectionOption()) {
        return Stream.of(item.getValue());
      }

    } else if (child instanceof UISelectItems) {
      Object value = ((UISelectItems) child).getValue();

      if (value instanceof Collection) {
        return ((Collection<?>) value).stream();
      } else {
        throw new IllegalStateException("Unsupported value of UISelectItems: " + value);
      }
    }

    return Stream.empty();
  }

  @Override
  public String getAsString(FacesContext context, UIComponent component, Object value) {
    if (value == null) return null;
    if (value instanceof String) return (String) value;
    if (value instanceof IdObject) return ((IdObject) value).getDisplayId();

    throw new IllegalArgumentException("Unexpected value type");
  }

}

0

我正在这样做:

  1. 模型是ViewScoped

  2. 转换器:

    @Named
    @ViewScoped
    public class ViewScopedFacesConverter implements Converter, Serializable
    {
            private static final long serialVersionUID = 1L;
            private Map<String, Object> converterMap;
    
            @PostConstruct
            void postConstruct(){
                converterMap = new HashMap<>();
            }
    
            @Override
            public String getAsString(FacesContext context, UIComponent component, Object object) {
                String selectItemValue = String.valueOf( object.hashCode() ); 
                converterMap.put( selectItemValue, object );
                return selectItemValue;
            }
    
            @Override
            public Object getAsObject(FacesContext context, UIComponent component, String selectItemValue){
                return converterMap.get(selectItemValue);
            }
    }
    

并绑定到组件:

 <f:converter binding="#{viewScopedFacesConverter}" />

如果您将使用实体ID而不是hashCode,则会发生冲突-如果一页上具有相同ID的不同实体(类)的列表很少


0

叫我懒,但是编码一个Converter似乎很多不必要的工作。我使用的是Primefaces,以前没有使用过普通的JSF2列表框或下拉菜单,我只是(懒惰地)假设该小部件可以处理复杂的对象,即像这样将所选对象原样传递给其对应的getter / setter。许多其他小部件也可以。我很失望地发现(经过数小时的头部刮擦)对于没有转换器的这种小部件类型不存在此功能。实际上,如果您为复杂对象而不是字符串提供设置器,它将以静默方式失败(仅不调用设置器,没有异常,没有JS错误),并且我花了很多时间通过BalusC出色的故障排除工具找出原因,但无济于事,因为这些建议均未应用。我的结论是:列表框/菜单小部件需要适应其他JSF2小部件不需要的情况。这似乎具有误导性,并且容易导致像我这样的不知情的开发人员陷入困境。

最后,我拒绝编码Converter,并通过反复试验发现,如果您将小部件的值设置为复杂的对象,例如:

<p:selectOneListbox id="adminEvents" value="#{testBean.selectedEvent}">

...当用户选择一个项目时,小部件可以调用该对象的String setter,例如setSelectedThing(String thingString) {...},并且传递的String是表示Thing对象的JSON String。我可以解析它以确定选择了哪个对象。感觉有点像骇客,但不像转换器,更像骇客。


您的意思是“结论:列表框/菜单小部件需要比其他JSF2小部件不需要调整”。?需要转换器的事实吗?h:inputText如果您创建自己的强类型对象而不是使用字符串,那么平原也是如此。
库奇特耶

我的意思是,其他小部件可以处理(从中传递和在后备bean上设置)复杂对象,而列表框/菜单则不能。考虑一下,String实际上是一个复杂的对象,因此人们会认为这些小部件将能够处理任何复杂的对象。在我看来h:inputText,甚至更复杂的兄弟姐妹都p:inputText只能根据其性质来处理字符串。列表框/菜单似乎应该可以处理任何对象,尽管该对象只能由UI中的String表示。
蛇狗

h:inputText并且p:inputText可以处理更多数字。这是因为它们基于已知的Java类型,并且jsf提供了转换器并隐式地应用了它们。对于任何其他类型,它也需要一个转换器,例如,有效地为数字的自定义强类型GTIN(在此实现中,他们使用字符串)。是的,转换器往返于“客户端”字符串表示形式的转换……
Kukeltje

您编码的内容听起来像是一个序列化器/反序列化器,最终充当转换器(甚至可能易于客户端操作)。我怀疑您是在实体中还是在后备bean(控制器)中编写了代码,这两者都不应该了解客户端和服务器之间的“转换”,因此在我看来更像是一种黑客。尤其是因为Omnifaces具有Showcase.omnifaces.org/converters/SelectItemsConverter之类的东西。并请记住,如果您具有对字符串列表进行操作的选择菜单,则也将使用内置转换器。
库克尔杰(Kukeltje)

完全不同的角度,谢谢。作为Primefaces用户,我希望这些小部件能够像我使用的其他小部件一样“正常工作”。我不知道有些JSF小部件具有内置的Converters,而有些则没有。展望未来,我将内置转换器视为框架提供给我的便利,而不是预期的功能。但是,我不知道在getter / setter中提供转换与在单独的类中提供转换有本质区别,而且看起来更简单。
蛇狗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.