Vaadin14 Grid使用DataProvider时全选功能异常
Vaadin 14 Grid多选状态异常问题
问题现象
在Vaadin 14(14.1.17版本)中为开启多选模式的Grid配置自定义DataProvider时,出现两类异常:
- 点击表头全选按钮后,列表行未在UI上呈现选中高亮状态,但后端调用
grid.getSelectedItems()可正常获取全量条目,即后端选中逻辑正常,仅UI状态未同步 - 若先手动选中单行再点击全选按钮,
grid.getSelectedItems()不会覆盖此前手动选中的条目,最终返回的选中项总数为Grid总条目数+1,选中状态未正确重置
相关实现代码
FormEntity实体类
@Entity @EqualsAndHashCode @Table(name = "forms", schema = "click", catalog = "") public class FormEntity { private long id; private String userId; private String requestorType; private String confirmationPhone; private Timestamp submittedDate; private int status; private Timestamp approvedDate; private String comments; private String organization; private PatientEntity patientEntity; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) public long getId() { return id; } public void setId(long id) { this.id = id; } @Basic @Column(name = "user_id", nullable = true, length = 70) public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } @Basic @Column(name = "requestor_type", nullable = false, length = 50) public String getRequestorType() { return requestorType; } public void setRequestorType(String requestorType) { this.requestorType = requestorType; } @Basic @Column(name = "confirmation_phone", nullable = true, length = 50) public String getConfirmationPhone() { return confirmationPhone; } public void setConfirmationPhone(String confirmationPhone) { this.confirmationPhone = confirmationPhone; } @Basic @Column(name = "submitted_date", nullable = false) public Timestamp getSubmittedDate() { return submittedDate; } public void setSubmittedDate(Timestamp submittedDate) { this.submittedDate = submittedDate; } @Basic @Column(name = "status", nullable = false, length = 11) public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } @Basic @Column(name = "approved_date", nullable = false) public Timestamp getApprovedDate() { return approvedDate; } public void setApprovedDate(Timestamp approvedDate) { this.approvedDate = approvedDate; } @Basic @Column(name = "comments", nullable = true) public String getComments() { return comments; } public void setComments(String comments) { this.comments = comments; } @Basic @Column(name = "organization", nullable = true) public String getOrganization() { return organization; } public void setOrganization(String organization) { this.organization = organization; } @ManyToOne @JoinColumn(name = "patient_id", referencedColumnName = "id", nullable = false) public PatientEntity getPatientEntity() { return patientEntity; } public void setPatientEntity(PatientEntity patientEntity) { this.patientEntity = patientEntity; } }
DataProvider实现
private ConfigurableFilterDataProvider<FormEntity, Void, Map<String, Object>> getFilteringDataProvider(OrganizationDto organizationDto) { DataProvider<FormEntity, Map<String, Object>> dataProvider = DataProvider.fromFilteringCallbacks( query -> { Map.Entry<String, String> sortOrder = null; List<QuerySortOrder> sortOrderList = query.getSortOrders(); if (!sortOrderList.isEmpty()) { QuerySortOrder querySortOrder = sortOrderList.get(0); sortOrder = new AbstractMap.SimpleEntry<>(querySortOrder.getSorted(), querySortOrder.getDirection().name()); } Map<String, Object> filter = query.getFilter().orElse(null); List<FormEntity> rv = null; rv = registrationService.fetchForms(organizationDto.getIdentifier(), filter, query.getOffset(), query.getLimit(), sortOrder).getContent(); System.out.println("List size:" + rv.size()); return rv.stream(); }, query -> { Map<String, Object> filter = query.getFilter().orElse(null); long count = 0; // 统计匹配当前过滤条件的总条目数 count = registrationService.fetchForms(organizationDto.getIdentifier(), filter, 0, 1, null).getTotalElements(); System.out.println("Count:" + count); return (int) count; } ); return dataProvider.withConfigurableFilter(); }
问题根因
Grid的多选状态判断完全依赖实体类的equals()和hashCode()方法。当前实体类使用Lombok的@EqualsAndHashCode默认配置,会基于类下所有字段(包括关联的PatientEntity对象)生成相等性判断逻辑:
- 每次DataProvider回调查询返回的FormEntity实例,哪怕主键id相同,也会因为关联对象的引用差异、JPA代理对象状态差异,导致
equals()判定为不相等。前端渲染行时,无法在选中集合中匹配到当前行对应的实例,因此不会显示选中样式。 - 点击全选时,因为新查询出来的条目和之前手动选中的条目
equals()判定不相等,选中集合无法移除旧的手动选中条目,最终出现总选中数=总条目数+1的问题。
修复方案
JPA实体的相等性判断永远只需要基于主键实现,不要使用全字段生成equals/hashCode:
- 修改实体类的
@EqualsAndHashCode注解配置,仅将主键id纳入相等性判断:
@Entity // 关闭全字段equals生成,开启显式标记字段纳入判断 @EqualsAndHashCode(onlyExplicitlyIncluded = true) @Table(name = "forms", schema = "click", catalog = "") public class FormEntity { // 其余代码保持不变 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) @EqualsAndHashCode.Include // 仅将id作为equals/hashCode的判断依据 public long getId() { return id; } // 其余方法保持不变 }
- 若使用JPA懒加载,避免将未初始化的代理关联对象传入Grid,可提前初始化关联字段,或直接使用只包含业务字段的DTO作为Grid的数据源,进一步避免相等性判断异常。
内容的提问来源于stack exchange,提问作者despoina.k
相关产品推荐
相关产品推荐

