迁移到RichFaces 4 - RichFaces数据模型。

发布者:    |      

概述

我相信这可能是大家最期待的文章之一。它将深入探讨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属性名称作为sortByfilterExpression值传递。通常,您会使用EL绑定来迭代对象属性和布尔表达式,如果您使用我们默认的模型。但对我们来说不是这样。因为我们实现了我们的模型,其中包含数据库排序和过滤——我们只需要知道属性名称,我们将在查询中应用规则,就像您在模型中看到的那样。

练习

当然,这个样本仍然不是理想的。最重要的是,你可以轻易地看出绝对没有缓存。每次调用 walk()getRowCount() 时,都会查询数据库以请求数据。你应该自己添加,因为实际上没有适用于所有情况的统一方案。有人可能只需要在过滤、排序或页面更改时从数据库中读取一次数据,并确保它不会在请求之间改变。在某些情况下,你应该考虑数据可能由不同用户同时插入/删除/编辑,因此 walk() 方法获得的列表可能在不同的 walk 调用之间改变,等等。因此,这取决于你定义规则。同样的,也适用于 rowCount。非常重要的是也要尽可能缓存它,因为它在请求期间(由表格和数据滚动器)也会多次调用。

此外,你可能还想添加 Weld/Seam,以便能够以更优雅的方式绑定豆、实体和服务。

结果

即使考虑到所有这些,如果你没有至少一张结果的截图,这篇文章的演示就会显得很无聊。所以,这里是我们在执行了 按姓氏排序 和通过电子邮件进行筛选(仅获取 .com 域的电子邮件)后得到的表格。

更多信息

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


回到顶部