database 如何从数据库中填充 h:selectOneMenu 的选项?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6848970/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to populate options of h:selectOneMenu from database?
提问by Illep
I am creating a web application, where you have to read a list of objects / entities from a DB and populate it in a JSF <h:selectOneMenu>. I am unable to code this. Can someone show me how to do it?
我正在创建一个 Web 应用程序,您必须在其中从数据库读取对象/实体列表并将其填充到 JSF 中<h:selectOneMenu>。我无法对此进行编码。有人可以告诉我怎么做吗?
I know how to get a List<User>from the DB. What I need to know is, how to populate this list in a <h:selectOneMenu>.
我知道如何List<User>从数据库中获取 a 。我需要知道的是,如何在<h:selectOneMenu>.
<h:selectOneMenu value="#{bean.name}">
...?
</h:selectOneMenu>
回答by BalusC
Based on your question history, you're using JSF 2.x. So, here's a JSF 2.x targeted answer. In JSF 1.x you would be forced to wrap item values/labels in ugly SelectIteminstances. This is fortunately not needed anymore in JSF 2.x.
根据您的问题历史,您使用的是 JSF 2.x。所以,这是一个针对 JSF 2.x 的答案。在 JSF 1.x 中,您将被迫在丑陋的SelectItem实例中包装项目值/标签。幸运的是,这在 JSF 2.x 中不再需要了。
Basic example
基本示例
To answer your question directly, just use <f:selectItems>whose valuepoints to a List<T>property which you preserve from the DB during bean's (post)construction. Here's a basic kickoff example assuming that Tactually represents a String.
要直接回答您的问题,只需使用<f:selectItems>whovalue指向List<T>您在 bean 的(后)构造期间从数据库中保留的属性。这是一个基本的启动示例,假设T实际上代表String.
<h:selectOneMenu value="#{bean.name}">
<f:selectItems value="#{bean.names}" />
</h:selectOneMenu>
with
和
@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)
}
Simple as that. Actually, the T's toString()will be used to represent both the dropdown item label and value. So, when you're instead of List<String>using a list of complex objects like List<SomeEntity>and you haven't overridden the class' toString()method, then you would see com.example.SomeEntity@hashcodeas item values. See next section how to solve it properly.
就那么简单。实际上,T'stoString()将用于表示下拉项标签和值。因此,当您不List<String>使用像复杂对象列表那样List<SomeEntity>并且没有覆盖类的toString()方法时,您将看到com.example.SomeEntity@hashcode项目值。请参阅下一节如何正确解决它。
Also note that the bean for <f:selectItems>value does not necessarily need to be the same bean as the bean for <h:selectOneMenu>value. This is useful whenever the values are actually applicationwide constants which you just have to load only once during application's startup. You could then just make it a property of an application scoped bean.
另请注意,用于<f:selectItems>值的 bean不一定需要与用于<h:selectOneMenu>值的 bean 相同。当值实际上是应用程序范围的常量时,这很有用,您只需在应用程序启动期间加载一次。然后,您可以将其设为应用程序作用域 bean 的属性。
<h:selectOneMenu value="#{bean.name}">
<f:selectItems value="#{data.names}" />
</h:selectOneMenu>
Complex objects as available items
作为可用项目的复杂对象
Whenever Tconcerns a complex object (a javabean), such as Userwhich has a Stringproperty of name, then you could use the varattribute to get hold of the iteration variable which you in turn can use in itemValueand/or itemLabelattribtues (if you omit the itemLabel, then the label becomes the same as the value).
每当T涉及一个复杂对象(一个 javabean),例如User它有一个String属性 of name,那么你可以使用该var属性来获取迭代变量,你又可以在itemValue和/或itemLabel属性中使用它(如果你省略了itemLabel,那么标签变得与值相同)。
Example #1:
示例#1:
<h:selectOneMenu value="#{bean.userName}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user.name}" />
</h:selectOneMenu>
with
和
private String userName;
private List<User> users;
@EJB
private UserService userService;
@PostConstruct
public void init() {
users = userService.list();
}
// ... (getters, setters, etc)
Or when it has a Longproperty idwhich you would rather like to set as item value:
或者当它具有您希望设置为项目值的Long属性时id:
Example #2:
示例#2:
<h:selectOneMenu value="#{bean.userId}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user.id}" itemLabel="#{user.name}" />
</h:selectOneMenu>
with
和
private Long userId;
private List<User> users;
// ... (the same as in previous bean example)
Complex object as selected item
作为选定项的复杂对象
Whenever you would like to set it to a Tproperty in the bean as well and Trepresents an User, then you would need to bake a custom Converterwhich converts between Userand an unique string representation (which can be the idproperty). Do note that the itemValuemust represent the complex object itself, exactly the type which needs to be set as selection component's value.
每当您想将它设置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>
with
和
private User user;
private List<User> users;
// ... (the same as in previous bean example)
and
和
@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);
}
}
}
(please note that the Converteris a bit hacky in order to be able to inject an @EJBin a JSF converter; normally one would have annotated it as @FacesConverter(forClass=User.class), but that unfortunately doesn't allow @EJBinjections)
(请注意,Converter为了能够@EJB在 JSF 转换器中注入 an 有点笨拙;通常人们会将其注释为@FacesConverter(forClass=User.class),但不幸的是不允许@EJB注入)
Don't forget to make sure that the complex object class has equals()and hashCode()properly implemented, otherwise JSF will during render fail to show preselected item(s), and you'll on submit face Validation Error: Value is not valid.
不要忘记,以确保复杂的对象类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();
}
}
Complex objects with a generic converter
具有通用转换器的复杂对象
Head to this answer: Implement converters for entities with Java Generics.
前往此答案:使用 Java 泛型实现实体转换器。
Complex objects without a custom converter
没有自定义转换器的复杂对象
The JSF utility library OmniFacesoffers a special converter out the box which allows you to use complex objects in <h:selectOneMenu>without the need to create a custom converter. The SelectItemsConverterwill simply do the conversion based on readily available items in <f:selectItem(s)>.
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>
See also:
也可以看看:
回答by Nayan Wadekar
View-Page
查看页面
<h:selectOneMenu id="selectOneCB" value="#{page.selectedName}">
<f:selectItems value="#{page.names}"/>
</h:selectOneMenu>
Backing-Bean
支持豆
List<SelectItem> names = new ArrayList<SelectItem>();
//-- Populate list from database
names.add(new SelectItem(valueObject,"label"));
//-- setter/getter accessor methods for list
To display particular selected record, it must be one of the values in the list.
要显示特定的选定记录,它必须是列表中的值之一。
回答by Lii
Roll-your-own generic converter for complex objects as selected item
为复杂对象滚动您自己的通用转换器作为选定项
The Balusc gives a very useful overview answer on this subject. But there is one alternative he does not present: The Roll-your-own generic converter that handles complex objects as the selected item. This is very complex to do if you want to handle all cases, but pretty simple for simple cases.
Balusc 对这个主题给出了一个非常有用的概述答案。但他没有提出一种替代方案:将复杂对象作为选定项目处理的自己动手做的通用转换器。如果您想处理所有情况,这是非常复杂的,但对于简单情况来说非常简单。
The code below contains an example of such a converter. It works in the same spirit as the OmniFaces SelectItemsConverteras it looks through the children of a component for UISelectItem(s)containing objects. The difference is that it only handles bindings to either simple collections of entity objects, or to strings. It does not handle item groups, collections of SelectItems, arrays and probably a lot of other things.
下面的代码包含此类转换器的示例。它的工作原理与 OmniFaces SelectItemsConverter相同,因为它查看UISelectItem(s)包含对象的组件的子项。不同之处在于它只处理实体对象的简单集合或字符串的绑定。它不处理项组、SelectItems 的集合、数组以及可能很多其他事情。
The entities that the component binds to must implement the IdObjectinterface. (This could be solved in other way, such as using toString.)
组件绑定到的实体必须实现该IdObject接口。(这可以通过其他方式解决,例如使用toString.)
Note that the entities must implement equalsin such a way that two entities with the same ID compares equal.
请注意,实体必须equals以具有相同 ID 的两个实体比较相等的方式实现。
The only thing that you need to do to use it is to specify it as converter on the select component, bind to an entity property and a list of possible entities:
要使用它,您唯一需要做的就是在选择组件上将其指定为转换器,绑定到实体属性和可能的实体列表:
<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>
Converter:
转换器:
/**
* 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");
}
}
回答by Lii
I'm doing it like this:
我是这样做的:
Models are ViewScoped
converter:
@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); } }
模型是 ViewScoped
转换器:
@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); } }
and bind to component with:
并绑定到组件:
<f:converter binding="#{viewScopedFacesConverter}" />
If you will use entity id rather than hashCode you can hit a collision- if you have few lists on one page for different entities (classes) with the same id
如果您将使用实体 id 而不是 hashCode,您可能会遇到冲突 - 如果您在一个页面上有几个具有相同 id 的不同实体(类)的列表
回答by snakedog
Call me lazy but coding a Converter seems like a lot of unnecessary work. I'm using Primefaces and, not having used a plain vanilla JSF2 listbox or dropdown menu before, I just assumed (being lazy) that the widget could handle complex objects, i.e. pass the selected object as is to its corresponding getter/setter like so many other widgets do. I was disappointed to find (after hours of head scratching) that this capability does not exist for this widget type without a Converter. In fact if you supply a setter for the complex object rather than for a String, it fails silently (simply doesn't call the setter, no Exception, no JS error), and I spent a ton of time going through BalusC's excellent troubleshooting toolto find the cause, to no avail since none of those suggestions applied. My conclusion: listbox/menu widget needs adapting that other JSF2 widgets do not. This seems misleading and prone to leading the uninformed developer like myself down a rabbit hole.
称我为懒惰但编写转换器似乎是很多不必要的工作。我正在使用 Primefaces,并且之前没有使用过普通的普通 JSF2 列表框或下拉菜单,我只是假设(懒惰)该小部件可以处理复杂的对象,即将选定的对象按原样传递给其相应的 getter/setter,就像这样许多其他小部件都可以。我很失望地发现(经过数小时的挠头)没有转换器的这种小部件类型不存在此功能。事实上,如果你为复杂对象而不是字符串提供一个 setter,它会默默地失败(只是不调用 setter,没有异常,没有 JS 错误),我花了大量时间浏览BalusC 出色的故障排除工具找到原因,但无济于事,因为这些建议都不适用。我的结论是:列表框/菜单小部件需要适应,而其他 JSF2 小部件则不需要。这似乎具有误导性,并且容易导致像我这样不知情的开发人员陷入困境。
In the end I resisted coding a Converter and found through trial and error that if you set the widget value to a complex object, e.g.:
最后,我拒绝编写 Converter 并通过反复试验发现,如果将小部件值设置为复杂对象,例如:
<p:selectOneListbox id="adminEvents" value="#{testBean.selectedEvent}">
... when the user selects an item, the widget can call a String setter for that object, e.g. setSelectedThing(String thingString) {...}, and the String passed is a JSON String representing the Thing object. I can parse it to determine which object was selected. This feels a little like a hack, but less of a hack than a Converter.
...当用户选择一个项目时,小部件可以为该对象调用一个字符串设置器,例如setSelectedThing(String thingString) {...},传递的字符串是一个表示 Thing 对象的 JSON 字符串。我可以解析它以确定选择了哪个对象。这感觉有点像黑客,但比转换器更像是黑客。

