概述
我相信这可能是大家最期待的文章之一。它将深入探讨RichFaces迭代组件,涵盖我们底层使用的先进模型。实际上,真正的企业应用使用大量数据集,应用分页和延迟数据加载,使用排序、过滤、选择等特性,使应用程序真正丰富和交互。因此,在大多数情况下,您不能仅依赖于简单的列表或使用默认模型包装的数据。为了获得最佳RichFaces表格体验,并具有良好的性能和可扩展性,您需要了解我们模型的基本原理。
模型示例
首先,我想介绍一个在实时演示中可用的新示例 - 可排列模型示例。它使用Hibernate实体管理器,所有分页/排序/过滤操作实际上都在数据库级别执行,而不是在包装的模型中执行。
在这篇文章中,我将指导您了解该示例中的基本点,以便您能够获取主要思想并能够处理自己的模型。
为什么我们需要模型
让我们从一个好问题开始 - 为什么RichFaces引入了具有JSF javax.faces.model.DataModel 合约的定制模型。原因很简单。与仅提供基本功能的JSF 2实现不同,RichFaces引入了新的丰富组件,并在它们之上添加了新功能。以下是一个概述
- 树组件家族。支持基于Swing的数据模型和RichFaces定制数据模型。具有附加提供程序,允许从任何自定义非层次模型声明性创建数据模型。具有选择功能,并可通过不同的模式(Ajax、客户端、服务器)使用。
- 表格组件家族。RichFaces统一了两个富表格之间的排序/过滤/分页功能。它在rich:dataTable中添加了创建主从布局的能力,并内置了折叠/展开子表格的细节。它还通过rich:extendedDataTable添加了垂直滚动和选择数据的Ajax懒加载。等等。
当然,所有这些都需要良好的模型基础。为了标准化不同组件之间的API,并为RichFaces开发者提供一个统一的合约,我们设计了我们的模型。
扩展数据模型概述
从开始,让我介绍一下我们的模型原则,然后我们将在展示演示中看到它们的实现。
扩展数据模型是一个基础模型抽象类,为所有其他模型的特定实现提供合约。让我们更详细地看看这个类
public abstract class ExtendedDataModel<E> extends DataModel<E> { public abstract void setRowKey(Object key); public abstract Object getRowKey(); public abstract void walk(FacesContext context, DataVisitor visitor, Range range, Object argument); }
好吧,我们看到那里引入了一个新的实体。它是rowKey。让我们看看这个实体的目的。在处理复杂的数据结构(如树)和使用高级UI组件功能(如表格中的过滤/排序/分页/选择)时,我们需要有一种简单的方法来识别模型中的对象。仅使用rowIndex并不能很好地解决问题,因为它需要额外的复杂查找机制(根据索引识别包装模型中的数据库对象)来完成这个任务。而使用rowKey,你可以简单地使用在数据库层面使用的相同ID。
此外,你还看到那里引入了walk()方法。RichFaces迭代组件在处理模型时使用访问者模式。因此,该方法应执行模型迭代,并调用作为参数传递的访问者以处理模型中的每个对象。
扩展数据模型的实现
- SequenceDataModel – 表格的ExtendedDataModel的默认实现。当组件不使用过滤和排序时使用。
- ArrangeableModel – 实现ExtendedDataModel的模型,它还实现了Arrangeable接口,用于添加排序和过滤支持。
- TreeSequenceKeyModel 抽象模型以及所有扩展它的模型都用于树组件家族。该模型超出了本文的范围。
示例代码
首先,我想说的是,我不会描述示例中使用的所有代码。你可以很容易地在展示的实时示例中检查它,或者从SVN仓库中获取,并在你喜欢的IDE中探索。除了这将很无聊并且会使文章变得非常大之外,我还想说的是,这篇写作的目的是在审查RichFaces数据模型使用原则时引导你走向正确的方向。
该示例功能概述
- 根据给定的过滤和排序规则从数据库中获取Person对象列表
- 使用rich:dataTable显示对象
- 添加控制排序和过滤的控件
- 添加rich:dataScroller,允许将数据从数据库分页加载。
示例代码 – 基础人员实体
我相信这将是显而易见的。我们只是使用简单且常用的类来定义人员对象。
@Entity public class Person { private String name; private String surname; private String email; @Id @GeneratedValue private Long id; //getters and setters }
示例代码 – 通用模型
现在让我们更详细地看看模型代码。
JPADataModel– 是一个通用模型,旨在为所有实现者提供统一的数据工作方法。它由应定义为在实现它的模型中工作特定实体类的类参数化。此外,它还传递了EntityManager,以便实现者类负责特定EntityManager的查找。
此外,你应该从一开始就注意抽象的getId(T t)方法,该方法也应由实现该抽象模型的模型实现。它应返回特定实体的ID,该ID将用作该模型中的rowKey。
现在让我们回顾两个最重要的方法。让我们从arrange()方法开始。它可能太简单了,所以没有列出,但无论如何
public void arrange(FacesContext context, ArrangeableState state) { arrangeableState = state; }
我只是想简单解释一下为什么我们不进行任何进一步处理,只将传递的状态简单存储在模型的属性中。如果您查看或已经查看过RichFaces源代码,您会看到默认的ArrangeableModel实现的该方法执行实际的排序和过滤。但这是因为在我们的默认模型中,我们使用包装模型(例如,作为rich:dataTable值传递的简单列表或映射)。但在我们的情况下,我们将根据当前数据表页面从数据库加载数据,并使用数据库查询进行排序和过滤。所以,我们只需要将组件传递的所有信息存储在ArrangeableState对象中,以供未来在walk()中使用。
现在让我们看看walk()方法
@Override public void walk(FacesContext context, DataVisitor visitor, Range range, Object argument) { CriteriaQuery<T> criteriaQuery = createSelectCriteriaQuery(); TypedQuery<T> query = entityManager.createQuery(criteriaQuery); SequenceRange sequenceRange = (SequenceRange) range; if (sequenceRange.getFirstRow() >= 0 && sequenceRange.getRows() > 0) { query.setFirstResult(sequenceRange.getFirstRow()); query.setMaxResults(sequenceRange.getRows()); } List<T> data = query.getResultList(); for (T t : data) { visitor.process(context, getId(t), argument); } }
组件在生命周期中调用该方法几次,并期望它遍历模型,调用DataVisitor,该访问者将处理每个对象(例如,在渲染阶段对对象的行进行编码)。
除了访问者之外,表格组件还传递了包含有关从哪一行开始获取数据(使用第一个属性定义或由rich:dataScroller在切换页面时设置)以及要获取的行数的Range对象。所以我们在这里所做的一切——只是创建具有给定排序、过滤和范围参数的查询,将数据从数据库加载到实体列表,并最终遍历该列表,将每个对象传递给DataVisitor进行处理。您可能对createSelectCriteriaQuery()方法很感兴趣,因为它执行实际的排序和过滤参数应用。但实际上,我只想把它留给你,因为JPA查询创建问题超出了这篇文章的范围,并且已经在更具体的资源中得到了很好的解释。
除了您可能对getRowCount()方法感兴趣之外
@Override public int getRowCount() { CriteriaQuery<Long> criteriaQuery = createCountCriteriaQuery(); return entityManager.createQuery( criteriaQuery).getSingleResult().intValue(); }
因此,在那里我们应用排序和过滤,创建标准以返回当前组件状态的rowCount。
示例代码——特定模型类
我们的通用模型实现放在PersonBean中,并使用以下代码定义
@ManagedBean @SessionScoped public class PersonBean implements Serializable { //... private static final class PersonDataModel extends JPADataModel<Person> { private PersonDataModel(EntityManager entityManager) { super(entityManager, Person.class); } @Override protected Object getId(Person t) { return t.getId(); } } //... private EntityManager lookupEntityManager() { FacesContext facesContext = FacesContext.getCurrentInstance(); PersistenceService persistenceService = facesContext.getApplication(). evaluateExpressionGet(facesContext, "#{persistenceService}", PersistenceService.class); return persistenceService.getEntityManager(); } public Object getDataModel() { return new PersonDataModel(lookupEntityManager()); } //... }
实际上,我们在那里所做的一切——执行实体管理器的查找,并使用实体类Person和该实体管理器启动特定的模型实现。
示例代码——页面代码
我们在那里使用了两个页面。首先是arrangeableModel-sample.xhtml
<h:form id="form"> <rich:dataTable keepSaved="true" id="richTable" var="record" value="#{personBean.dataModel}" rows="20"> <ui:include src="jpaColumn.xhtml"> <ui:param name="bean" value="#{personBean}" /> <ui:param name="property" value="name" /> </ui:include> <ui:include src="jpaColumn.xhtml"> <ui:param name="bean" value="#{personBean}" /> <ui:param name="property" value="surname" /> </ui:include> <ui:include src="jpaColumn.xhtml"> <ui:param name="bean" value="#{personBean}" /> <ui:param name="property" value="email" /> </ui:include> <f:facet name="footer"> <rich:dataScroller id="scroller" /> </f:facet> </rich:dataTable> </h:form>
那里没有什么有趣的地方。我们只是定义了一个指向我们的模型的表格,并包含列,将人员属性和bean的属性传递给它。
第二个是jpaColumn.xhtml
<ui:composition> <rich:column sortBy="#{property}" sortOrder="#{bean.sortOrders[property]}" filterValue="#{bean.filterValues[property]}" filterExpression="#{property}"> <f:facet name="header"> <h:commandLink action="#{bean.toggleSort}"> #{bean.sortOrders[property]} <a4j:ajax render="richTable" /> <f:setPropertyActionListener target="#{bean.sortProperty}" value="#{property}" /> </h:commandLink> <br /> <h:inputText value="#{bean.filterValues[property]}"> <a4j:ajax render="richTable@body scroller" event="keyup" /> </h:inputText> </f:facet> <h:outputText value="#{record[property]}" /> </rich:column> </ui:composition>
这对我们来说更有趣。您应该注意的最重要的事情是我们将Person属性名称作为sortBy和filterExpression值传递。通常,您会使用EL绑定来迭代对象属性和布尔表达式,如果您使用我们默认的模型。但对我们来说不是这样。因为我们实现了我们的模型,其中包含数据库排序和过滤——我们只需要知道属性名称,我们将在查询中应用规则,就像您在模型中看到的那样。
练习
当然,这个样本仍然不是理想的。最重要的是,你可以轻易地看出绝对没有缓存。每次调用 walk() 或 getRowCount() 时,都会查询数据库以请求数据。你应该自己添加,因为实际上没有适用于所有情况的统一方案。有人可能只需要在过滤、排序或页面更改时从数据库中读取一次数据,并确保它不会在请求之间改变。在某些情况下,你应该考虑数据可能由不同用户同时插入/删除/编辑,因此 walk() 方法获得的列表可能在不同的 walk 调用之间改变,等等。因此,这取决于你定义规则。同样的,也适用于 rowCount。非常重要的是也要尽可能缓存它,因为它在请求期间(由表格和数据滚动器)也会多次调用。
此外,你可能还想添加 Weld/Seam,以便能够以更优雅的方式绑定豆、实体和服务。
结果
即使考虑到所有这些,如果你没有至少一张结果的截图,这篇文章的演示就会显得很无聊。所以,这里是我们在执行了 按姓氏排序 和通过电子邮件进行筛选(仅获取 .com
域的电子邮件)后得到的表格。

更多信息
如往常一样,请参阅 RichFaces 文档 以获取更多信息和 JavaDoc 的引用。