排序变更后,PrimeFaces DataTable选中项返回内容错误
我用PrimeFaces的commandLink在DataTable行上弹出对话框查看详情,升序排序时一切正常,但切换成降序后,点击某一行弹出的内容却是升序时对应位置的行数据。比如8条数据降序排列为8,7,…,2,1时,点击id=2的行,显示的却是id=7的内容。使用版本:PrimeFaces 5.3,JSF 2.2.7。
相关DataTable及对话框代码:
<p:dataTable id="improvementTable" var="improvement" widgetVar="improvementsTable" value="#{Controller.improvements}" sortBy="#{improvement.id}" sortOrder="descending"> <p:column headerText="ID"> <p:cellEditor> <f:facet name="output"> <h:outputText value="#{improvement.id}" /> </f:facet> <f:facet name="input"> <p:inputText id="modelInput" value="#{improvement.id}" /> </f:facet> </p:cellEditor> </p:column> <p:column> <p:commandLink id="detailOut" value="#{improvement.detail}" action="#{Controller.setSelectedImprovement(improvement)}" process="@this" oncomplete="PF('wDetail').show();" update=":dlgDetail" /> </p:column> </p:dataTable> </h:form> <p:dialog id="dlgDetail" widgetVar="wDetail"> <h:outputText value="#{Controller.selectedImprovement.detail}" /> </p:dialog>
后台Bean代码:
@ManagedBean (name="Controller") @RequestScoped public class Controller{ private List<Improvement> improvements; private Improvement selectedImprovement; @PostConstruct public void load() { CIMImprovementDao cimDao = new CIMImprovementDao(); improvements = cimDao.getAll(); } public List<Improvement> getImprovements() { return improvements; } public Improvement getSelectedImprovement() { return selectedImprovement; } public void setSelectedImprovement(Improvement selectedImprovement) { this.selectedImprovement = selectedImprovement; } }
我来帮你分析下这个问题的原因,以及对应的解决方案:
问题根源
这个问题的核心是请求作用域(RequestScoped)的Bean无法和前端排序后的表格状态同步。
当你对DataTable执行降序排序时,PrimeFaces只是在前端重新渲染了行的展示顺序,但你的Controller是@RequestScoped的——每次请求(包括排序后的AJAX渲染请求、点击commandLink的请求)都会创建一个全新的Bean实例,而@PostConstruct里又会重新加载原始顺序的improvements列表。
JSF在处理commandLink传递improvement对象时,是通过行索引来从后台列表中获取对象的。当前端显示的行顺序和后台Bean中列表的顺序不一致时,索引就会错位,导致你点击的是前端第N行,后台却取了原始列表的第N个对象,自然就出现了内容不匹配的情况。
最优解决方案
方案1:将Bean改为视图作用域(ViewScoped)
这是最简洁也最推荐的方案。视图作用域的Bean会在整个页面的生命周期内保持存在,包括所有AJAX请求(排序、分页、点击commandLink),这样后台的improvements列表会和前端表格的排序状态保持同步,索引匹配就不会出错。
只需修改Bean的作用域注解:
@ManagedBean(name="Controller") @ViewScoped // 替换原来的@RequestScoped public class Controller { // 其余代码完全不变 }
注意:如果你的项目用的是CDI的
@Named而不是JSF的@ManagedBean,对应的视图作用域是@javax.faces.view.ViewScoped(JSF 2.2及以上支持)。
方案2:通过唯一ID传递对象(兼容请求作用域)
如果因为项目限制不能改成视图作用域,那可以换一种思路:不直接传递对象实例,而是传递对象的唯一标识(比如id),在后台通过ID重新查询获取正确的对象,彻底避开索引匹配的问题。
首先修改前端的commandLink,把传递对象改成传递ID:
<p:commandLink id="detailOut" value="#{improvement.detail}" action="#{Controller.setSelectedImprovementById(improvement.id)}" process="@this" oncomplete="PF('wDetail').show();" update=":dlgDetail" />
然后在后台Bean中添加对应的方法:
// 假设你的Improvement的id是Long类型,根据实际类型调整参数 public void setSelectedImprovementById(Long id) { CIMImprovementDao cimDao = new CIMImprovementDao(); this.selectedImprovement = cimDao.getById(id); // 需要你的Dao实现根据ID查询的方法 }
这种方式不管前端怎么排序,都能通过唯一ID拿到正确的对象,完全避免了索引错位的问题。
方案3:手动维护排序后的列表(不推荐)
如果上面两种方案都无法实施,你可以监听PrimeFaces的排序事件,在后台手动更新improvements列表的顺序。但这种方式需要处理排序字段、排序方向等逻辑,代码繁琐且容易出错,所以一般不推荐使用。
内容的提问来源于stack exchange,提问作者Incognito Raccoon

