我有一个<p:dataTable>
延迟加载。在其中两列中,每列都有一个<p:selectOneMenu>
。
第一列包含国家/地区列表,第二列包含数据库中的状态列表。
我希望第二个菜单(包含状态列表的菜单)仅显示数据表中每行中与中第一个菜单中的国家/地区对应的状态数据表的每一行。
在编辑模式下,当其菜单中的国家/地区更改时,应在其当前行的菜单中填充与该国家/地区对应的状态。
如何在数据表的每一行中加载与其国家/地区对应的状态列表?
数据表中的这两列不完整,因为我对如何实现这一点没有一个确切的想法。
<p:column>
<p:cellEditor>
<f:facet name="output">
<h:outputText value="#{row.state.country.countryName}"/>
</f:facet>
<f:facet name="input">
<p:selectOneMenu value="#{row.state.country}">
<f:selectItems var="country"
value="#{cityBean.selectedCountries}"
itemLabel="#{country.countryName}"
itemValue="#{country}"/>
<p:ajax update="states" listener="#{cityBean.getStates}"/>
</p:selectOneMenu>
</f:facet>
</p:cellEditor>
</p:column>
<p:column>
<p:cellEditor>
<f:facet name="output">
<h:outputText value="#{row.state.stateName}"/>
</f:facet>
<f:facet name="input">
<p:selectOneMenu id="states">
<f:selectItems var="state"
value="#{cityBean.selectedStates}"
itemLabel="#{state.stateName}"
itemValue="#{state}"/>
</p:selectOneMenu>
</f:facet>
</p:cellEditor>
</p:column>
cityBean.selectedCountries
检索所有必需的国家,但cityBean.selectedStates
也从数据库中检索所有状态,这是不必要的,应该修改以仅检索与其他菜单中的国家/地区对应的状态。
我如何从这里开始?
答案 0 :(得分:13)
虽然您的初始解决方案有效,但实际上效率低下。这种方法基本上要求Country
和State
表的整个对象图(即使使用循环引用)在每个JSF视图或会话的Java内存中完全加载,即使您同时仅使用例如150个国家中的5个(因此理论上5个州名单已经足够,而不是150个州名单)。
我没有完全了解您的功能和技术要求。也许你实际上同时使用了所有这150个国家。也许你有很多页面需要所有(至少,“很多”)国家和州。也许您拥有大量内存的最先进的服务器硬件,以便所有国家和州可以毫不费力地复制到内存中的所有JSF视图和HTTP会话。
如果情况并非如此,那么不急切地获取每个国家/地区的州名单将是有益的(即@OneToMany(fetch=LAZY)
应该在Country#states
和State#cities
上使用{1}})。鉴于国家和州列表(可能)静态数据在一年内变化很少,至少足以在每个部署的基础上进行更改,最好将它们存储在应用程序范围内的bean中,该bean可以重复使用所有视图和会话,而不是在每个JSF视图或HTTP会话中重复。
在继续回答之前,我想说一下你的代码中存在逻辑错误。鉴于您正在编辑城市列表,因此#{row}
基本上是#{city}
,您在下拉输入值中通过状态引用国家/地区就像在#{city.state.country}
中一样,这很奇怪。虽然这可能适用于显示,但这不适用于编辑/保存。基本上,你在这里按国家而不是按城市改变国家。当前选定的州将获得新的国家而不是当前迭代的城市。这种变化将反映在这个州的所有城市!
如果您想继续使用此数据模型,这确实不是一件轻而易举的事。理想情况下,您希望在Country
上拥有单独的(虚拟)City
属性,以便更改不会影响城市的State
属性。您可以只使用@Transient
,以便JPA默认情况下不会将其视为@Column
。
@Transient // This is already saved via City#state#country.
private Country country;
public Country getCountry() {
return (country == null && state != null) ? state.getCountry() : country;
}
public void setCountry(Country country) {
this.country = country;
if (country == null) {
state = null;
}
}
总而言之,你应该最终拥有这个(为了简洁省略了不相关/默认/明显的属性):
<p:dataTable value="#{someViewScopedBean.cities}" var="city">
...
<p:selectOneMenu id="country" value="#{city.country}">
<f:selectItems value="#{applicationBean.countries}" />
<p:ajax update="state" />
</p:selectOneMenu>
...
<p:selectOneMenu id="state" value="#{city.state}">
<f:selectItems value="#{applicationBean.getStates(city.country)}" />
</p:selectOneMenu>
...
</p:dataTable>
使用#{applicationBean}
这样的内容:
@Named
@ApplicationScoped
public class ApplicationBean {
private List<Country> countries;
private Map<Country, List<State>> statesByCountry;
@EJB
private CountryService countryService;
@EJB
private StateService stateService;
@PostConstruct
public void init() {
countries = countryService.list();
statesByCountry = new HashMap<>();
}
public List<Country> getCountries() {
return countries;
}
public List<State> getStates(Country country) {
List<State> states = statesByCountry.get(country);
if (states == null) {
states = stateService.getByCountry(country);
statesByCountry.put(country, states);
}
return states;
}
}
(这是延迟加载的方法;您也可以立即在@PostConstruct
中获取它们,只看看哪个更适合你)
答案 1 :(得分:0)
在这种情况下,它非常简单。无需进一步编码。在状态菜单中,以下,
<f:selectItems var="state" value="#{cityManagedBean.selectedStates}"
itemLabel="#{state.stateName}" itemValue="#{state}"
itemLabelEscaped="true" rendered="true"/>
只需要修改如下。
<f:selectItems var="state" value="#{row.state.country.stateTableSet}"
itemLabel="#{state.stateName}" itemValue="#{state}"
itemLabelEscaped="true" rendered="true"/>
因为,实体对象(在这种情况下为row
)包含state
的嵌入对象,而该对象又包含country
的对象,该对象最终包含状态列表仅对country
对应。
那样
cityManagedBean.selectedStates
现在根本不需要额外的托管bean方法,需要将其修改为
row.state.country.stateTableSet
其中stateTableSet
是Set<StateTable>
,其中包含StateTable
实体的对象列表。
此外,listener
中的<p:ajax>
不再需要了。它应该如下所示。
<p:ajax update="cmbStateMenu"/>
只是为了在国家/地区菜单中选择项目(国家/地区)时更新州菜单。
有问题的代码现在应该如下所示。
<p:column id="country" headerText="Country" resizable="true" sortBy="#{row.state.country.countryName}" filterBy="#{row.state.country.countryName}" filterMatchMode="contains" filterMaxLength="45">
<p:cellEditor>
<f:facet name="output">
<h:outputLink value="Country.jsf">
<h:outputText value="#{row.state.country.countryName}"/>
<f:param name="id" value="#{row.state.country.countryId}"/>
</h:outputLink>
</f:facet>
<f:facet name="input">
<p:selectOneMenu id="cmbCountryMenu" converter="#{countryConverter}" value="#{row.state.country}" label="Country" required="true" filter="true" filterMatchMode="contains" effect="fold" rendered="true" editable="false" style="width:100%;">
<f:selectItems var="country" value="#{cityManagedBean.selectedCountries}" itemLabel="#{country.countryName}" itemValue="#{country}" itemLabelEscaped="true" rendered="true"/>
<p:ajax update="cmbStateMenu"/>
</p:selectOneMenu>
</f:facet>
</p:cellEditor>
</p:column>
<p:column id="state" headerText="State" resizable="false" sortBy="#{row.state.stateName}" filterBy="#{row.state.stateName}" filterMatchMode="contains" filterMaxLength="45">
<p:cellEditor>
<f:facet name="output">
<h:outputLink value="State.jsf">
<h:outputText value="#{row.state.stateName}"/>
<f:param name="id" value="#{row.state.stateId}"/>
</h:outputLink>
</f:facet>
<f:facet name="input">
<p:selectOneMenu id="cmbStateMenu" converter="#{stateConverter}" value="#{row.state}" label="State" required="true" filter="true" filterMatchMode="contains" effect="fold" rendered="true" editable="false" style="width:100%;">
<f:selectItems var="state" value="#{row.state.country.stateTableSet}" itemLabel="#{state.stateName}" itemValue="#{state}" itemLabelEscaped="true" rendered="true"/>
</p:selectOneMenu>
</f:facet>
</p:cellEditor>
</p:column>
道歉:我在问题中没有提到我正在检索城市列表。
答案 2 :(得分:0)
您需要根据所选国家/地区获取州名单。你需要一个valueChangeListener。
试试这个(我现在选择一个菜单在同一列中)
在你的xhtml中
<p:column id="state" headerText="State" sortBy="#{row.state.stateName}" filterBy="#{row.state.stateName}">
<p:cellEditor>
<f:facet name="output">
<f:facet name="output">
<h:outputLink value="State.jsf">
<h:outputText value="#{row.state.stateName}"/>
<f:param name="id" value="#{row.state.stateId}"/>
</h:outputLink>
</f:facet>
<f:facet name="input">
<h:selectOneMenu id="cmbCountryMenu" style="width:100px" value="#{row.state.country}" converterMessage="Error message." label="Country" valueChangeListener = "#{countryController.handleCountrySelect}" immediate="true" converter="#{countryConverter}">
<f:selectItem itemLabel = "Select" itemValue="#{null}" />
<f:selectItems var="country" value="#{cityManagedBean.selectedCountries}" itemLabel="#{country.countryName}" itemValue="#{country}"/>
<p:ajax update="cmbStateMenu" />
</h:selectOneMenu>
<h:selectOneMenu style="width:100px" value="#{row.state}" valueChangeListener = "#{stateController.handleStateSelect}" immediate="false" id="cmbStateMenu" converter = "#{stateConverter}">
<f:selectItems var="state" value="#{row.state.country.stateTableSet}" itemLabel="#{state.stateName}" itemValue="#{state}" itemLabelEscaped="true" rendered="true"/>
<p:ajax update="@this" />
</h:selectOneMenu>
</f:facet>
</p:cellEditor>
在您的控制器中
public void handleCountrySelect( ValueChangeEvent event )
{
setStates( ( ( Country) event.getNewValue() ) );
}