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

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

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

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

根据您的问题历史,您正在使用JSF 2.x. 所以,这里有一个JSF 2.x针对性的答案。 在JSF 1.x中,您将被迫将项目值/标签包装在难看的SelectItem实例中。 幸运的是,这在JSF 2.x中不再需要了。


基本的例子

要直接回答你的问题,只要使用<f:selectItems>value指向一个List<T>属性,这个属性在bean的(后期)构造过程中从DB保存。 假设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<SomeEntity>等复杂对象List<SomeEntity>而不是List<String> ,并且没有重写类的toString()方法,则会看到com.example.SomeEntity@hashcode as item值。 请参阅下一节如何正确解决它。

另请注意, <f:selectItems>值的bean不一定需要与<h:selectOneMenu>值的bean相同。 只要这些值实际上是应用程序范围内的常量,在应用程序启动期间您只需加载一次,这就很有用。 然后你可以把它作为应用程序范围的bean的一个属性。

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

复杂对象作为可用项目

每当T涉及一个复杂的对象(一个javabean),比如具有nameString属性的User ,那么你可以使用var属性来获得你可以在itemValue和/或itemLabel属性中使用的迭代variables如果你省略了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中的T属性,并且T代表一个User ,那么您将需要烘烤自定义的Converter ,它在User和唯一的string表示forms(可以是id属性)之间进行转换。 请注意, itemValue必须表示复杂的对象本身,正是需要设置为select组件的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); } } } 

(请注意,为了能够在JSF转换器中注入@EJB ,转换器@FacesConverter(forClass=User.class) ;通常会将其注释为@FacesConverter(forClass=User.class) , 但是不幸的是不允许@EJB注入 )

不要忘记确保复杂的对象类已经正确实现了equals()hashCode() ,否则JSF将在渲染期间无法显示预选项,并且您将在提交时validation错误:值不是有效 。

 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>使用复杂对象,而不需要创build自定义转换器。 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> 

也可以看看:

  • 我们的<h:selectOneMenu> wiki页面

查看页

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

支持bean

  List<SelectItem> names = new ArrayList<SelectItem>(); //-- Populate list from database names.add(new SelectItem(valueObject,"label")); //-- setter/getter accessor methods for list 

要显示特定的选定logging,它必须是列表中的一个值。

将自己的通用转换器作为选定项目复杂对象

Balusc给出了一个非常有用的概述在这个问题上的答案。 但是他没有提供一个替代scheme:将自己的通用转换器处理复杂的对象作为选定的项目。 如果你想处理所有的情况,这是非常复杂的,但是对于简单的情况来说很简单。

下面的代码包含了这样一个转换器的例子。 它与OmniFaces SelectItemsConverter的工作原理相同,因为它通过组件的子元素来查看包含对象的UISelectItem(s) 。 区别在于它只处理绑定到实体对象的简单集合或string。 它不处理项目组, SelectItem的集合,数组,可能还有很多其他的东西。

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

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

你需要做的唯一事情就是在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"); } } 

我是这样做的:

  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,你可以碰到一个碰撞 – 如果你在一个页面上有很less的列表用于具有相同ID的不同实体(类)

打电话给我懒,但编码转换器似乎是很多不必要的工作。 我正在使用Primefaces,并没有使用简单的香草JSF2列表框或下拉菜单之前,我只是认为(懒惰),小部件可以处理复杂的对象,即传递选定的对象,像它这样的对应的getter / setter像这样许多其他小部件呢。 我很失望地发现(小时后头部刮),这个function不存在这个没有转换器的小部件types。 事实上,如果你为复杂的对象而不是一个String提供setter,它会静静的失败(根本不会调用setter,没有Exception,没有JS错误),而且我花了很多时间去处理BalusC的优秀的故障排除工具find原因,没有用,因为没有这些build议适用。 我的结论是:listbox / menu小部件需要适应其他的JSF2小部件。 这似乎有误导性,容易导致像我这样的不知情的开发者陷入一个兔子洞。

最后,我拒绝编码一个转换器,并通过反复试验发现,如果您将widget值设置为一个复杂的对象,例如:

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

…当用户select一个项目时,小部件可以为该对象调用一个String setter,例如setSelectedThing(String thingString) {...} ,并且传递的string是表示Thing对象的JSONstring。 我可以parsing它来确定select了哪个对象。 这感觉有点像黑客,但比一个转换器less一点黑客。