Primefaces Lazy Datatable关联表排序/过滤问题(Eclipselink)
Got it, let's tackle this PrimeFaces Lazy DataTable + EclipseLink issue for joined table sorting and filtering. This is a pretty common requirement when dealing with lazy-loaded JPA tables, so here's a step-by-step breakdown that works reliably:
1. Build a Custom LazyDataModel
Your beanReceipts.lazyModel needs to extend PrimeFaces' LazyDataModel and override the load method—this is where all the pagination, sorting, and filtering logic lives. Here's an implementation tailored for EclipseLink's Criteria API:
public class ReceiptLazyDataModel extends LazyDataModel<Receipt> { private final EntityManager em; private List<Receipt> datasource; private long rowCount; public ReceiptLazyDataModel(EntityManager em) { this.em = em; } @Override public List<Receipt> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) { // Initialize Criteria API components CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Receipt> cq = cb.createQuery(Receipt.class); Root<Receipt> receiptRoot = cq.from(Receipt.class); // Join the associated table (example: Receipt → Customer) Join<Receipt, Customer> customerJoin = receiptRoot.join("customer", JoinType.INNER); // Use LEFT JOIN instead if receipts can exist without a customer // Handle filter conditions Predicate predicate = cb.conjunction(); for (Map.Entry<String, Object> filter : filters.entrySet()) { String filterKey = filter.getKey(); Object filterValue = filter.getValue(); if (filterValue != null && !filterValue.toString().isBlank()) { // Check if filter targets a joined table field (e.g., "customer.name") if (filterKey.contains(".")) { String[] fieldParts = filterKey.split("\\."); String joinedEntityField = fieldParts[1]; predicate = cb.and(predicate, cb.like(cb.lower(customerJoin.get(joinedEntityField)), "%" + filterValue.toString().toLowerCase() + "%")); } else { // Filter on main table field (e.g., receipt number) predicate = cb.and(predicate, cb.like(cb.lower(receiptRoot.get(filterKey)), "%" + filterValue.toString().toLowerCase() + "%")); } } } cq.where(predicate); // Handle sorting if (sortField != null) { if (sortField.contains(".")) { // Sort on joined table field (e.g., "customer.name") String[] sortParts = sortField.split("\\."); String joinedEntityField = sortParts[1]; if (sortOrder == SortOrder.ASCENDING) { cq.orderBy(cb.asc(customerJoin.get(joinedEntityField))); } else { cq.orderBy(cb.desc(customerJoin.get(joinedEntityField))); } } else { // Sort on main table field if (sortOrder == SortOrder.ASCENDING) { cq.orderBy(cb.asc(receiptRoot.get(sortField))); } else { cq.orderBy(cb.desc(receiptRoot.get(sortField))); } } } // Fetch paginated data TypedQuery<Receipt> dataQuery = em.createQuery(cq); dataQuery.setFirstResult(first); dataQuery.setMaxResults(pageSize); datasource = dataQuery.getResultList(); // Calculate total row count (critical for pagination) CriteriaQuery<Long> countQuery = cb.createQuery(Long.class); Root<Receipt> countRoot = countQuery.from(Receipt.class); countRoot.join("customer", JoinType.INNER); // Match join type from data query countQuery.select(cb.count(countRoot)).where(predicate); rowCount = em.createQuery(countQuery).getSingleResult(); this.setRowCount((int) rowCount); return datasource; } // Override these to ensure correct row selection @Override public Receipt getRowData(String rowKey) { for (Receipt receipt : datasource) { if (receipt.getId().toString().equals(rowKey)) { return receipt; } } return null; } @Override public Object getRowKey(Receipt receipt) { return receipt.getId(); } }
2. Update Your DataTable Configuration
In your XHTML, explicitly define sortBy and filterBy using the full path to joined entity fields. For example:
<h:form id="form"> <p:dataTable value="#{beanReceipts.lazyModel}" paginator="true" rows="10" paginatorPosition="bottom" paginatorTemplate="{RowsPerPageDropdown} {FirstPageLink} {PreviousPageLink} {CurrentPageReport} {NextPageLink} {LastPageLink}" rowsPerPageTemplate="5,10,15" var="item" emptyMessage="#{bundle['NoData']}" reflow="true" rowStyleClass="centered" styleClass="centered" lazy="true" resizableColumns="true"> <!-- Main table column --> <p:column headerText="Receipt Number" sortBy="#{item.number}" filterBy="#{item.number}"> <h:outputText value="#{item.number}" /> </p:column> <!-- Joined table column (Customer name) --> <p:column headerText="Customer Name" sortBy="#{item.customer.name}" filterBy="#{item.customer.name}"> <h:outputText value="#{item.customer.name}" /> </p:column> <!-- Add other columns as needed --> </p:dataTable> </h:form>
3. Wire Up the Model in Your Managed Bean
Make sure your managed bean initializes the custom lazy model with a properly injected EntityManager:
@ManagedBean @ViewScoped public class BeanReceipts { @PersistenceContext private EntityManager em; private LazyDataModel<Receipt> lazyModel; @PostConstruct public void init() { lazyModel = new ReceiptLazyDataModel(em); } // Getter for the model public LazyDataModel<Receipt> getLazyModel() { return lazyModel; } }
4. Key Tips & Troubleshooting
- Join Type Consistency: Use the same join type (INNER/LEFT) in both the data query and count query to avoid mismatched row counts.
- Case Insensitivity: The example uses
toLowerCase()for case-insensitive filtering—adjust this if you need case-sensitive behavior. - Field Path Validation: Double-check that
sortBy/filterBypaths match your entity class structure (e.g.,item.customer.namerequiresReceiptto have acustomerfield, which has anamefield). - Performance: Add database indexes on frequently filtered/sorted joined fields to speed up queries, especially with large datasets.
This implementation works smoothly with PrimeFaces 8+ and EclipseLink 2.7+ versions.
内容的提问来源于stack exchange,提问作者Ali Yucel Akgul

