Java分页结果一致性问题及解决方案
在开发分页功能时,我们经常会遇到一个看似简单但影响用户体验的问题:相同查询条件多次查询返回的结果不一致。这在使用HashSet和并行流时尤其常见。以下是一个完整的解决方案示例。
核心问题
/**
* 人工置数配置表服务实现类
*
* 注意:本类中大量使用LinkedHashSet而非HashSet,原因如下:
* 1. HashSet无序性会导致分页结果不稳定,相同查询条件可能返回不同顺序的结果
* 2. LinkedHashSet保持插入顺序,确保分页结果的一致性
* 3. 在分页场景下,顺序一致性对用户体验至关重要
*
* 注意:避免使用parallelStream(),因为:
* 1. 并行流会导致结果不稳定,相同输入可能产生不同数量的结果
* 2. 在分页场景下,结果的一致性比性能更重要
* 3. 并行流与ArrayList等非线程安全集合的组合会导致并发问题
*/
问题代码
/**
* 构建总键集合
*/
private Set<String> buildTotalKeys(List<EquipmentTempDTO> equipmentTempDTOS, ControlSettingQuerryDTO querryDto) {
Set<String> totalKeys = new HashSet<>();
Set<String> totalKeysTemp = equipmentTempDTOS.parallelStream()
.flatMap(equipmentTempDTO -> {
// 解析模型模版
if (ObjectUtil.isNotEmpty(objectCase)) {
XmlModelUtil object = new XmlModelUtil();
XmlTypeDto result = new XmlTypeDto();
object.parseXMLToModel(objectCase, result);
List<String> keys = new ArrayList<>();
List<ModelXML> objectModelDigital = result.getObjectModelDigital();
if (CollUtil.isNotEmpty(objectModelDigital)
&& (1 == querryDto.getPointType() || 0 == querryDto.getPointType())) {
objectModelDigital.parallelStream()
.filter(modelXML -> Boolean.TRUE.equals(modelXML.getDisplay()))
.map()
.forEach(keys::add);
}
List<ModelXML> objectModelAnalog = result.getObjectModelAnalog();
if (CollUtil.isNotEmpty(objectModelAnalog)
&& (2 == querryDto.getPointType() || 0 == querryDto.getPointType())) {
objectModelAnalog.parallelStream()
.filter(modelXML -> Boolean.TRUE.equals(modelXML.getDisplay()))
.map()
.forEach(keys::add);
}
return keys.stream();
}
return Stream.empty();
})
.collect(Collectors.toSet());
synchronized (totalKeys) {
totalKeys.addAll(totalKeysTemp);
}
return totalKeys;
}
### 解决方案实现
```java
/**
* 构建总键集合
* 使用LinkedHashSet保持元素的插入顺序,确保分页结果的一致性
*/
private Set<String> buildTotalKeys(List<EquipmentTempDTO> equipmentTempDTOS, ControlSettingQuerryDTO querryDto) {
// 使用LinkedHashSet保持插入顺序,避免HashSet的无序性导致分页结果不稳定
Set<String> totalKeys = new LinkedHashSet<>();
// 使用顺序流避免并发问题,确保结果的一致性
Set<String> totalKeysTemp = equipmentTempDTOS.stream()
.flatMap(equipmentTempDTO -> {
// 解析模型模版
if (ObjectUtil.isNotEmpty(objectCase)) {
// 使用线程安全的集合来收集结果
List<String> keys = new ArrayList<>();
if (CollUtil.isNotEmpty(objectModelDigital)
&& (1 == querryDto.getPointType() || 0 == querryDto.getPointType())) {
// 使用顺序流避免并发问题
objectModelDigital.stream()
.filter(modelXML -> Boolean.TRUE.equals(modelXML.getDisplay()))
.map()
.forEach(keys::add);
}
if (CollUtil.isNotEmpty(objectModelAnalog)
&& (2 == querryDto.getPointType() || 0 == querryDto.getPointType())) {
// 使用顺序流避免并发问题
objectModelAnalog.stream()
.filter(modelXML -> Boolean.TRUE.equals(modelXML.getDisplay()))
.map()
.forEach(keys::add);
}
return keys.stream();
}
return Stream.empty();
})
// 按字符串排序以确保一致性
.sorted()
.collect(Collectors.toCollection(LinkedHashSet::new));
totalKeys.addAll(totalKeysTemp);
return totalKeys;
}
关键要点总结
-
使用LinkedHashSet代替HashSet:
- LinkedHashSet保持元素插入顺序
- 确保相同查询条件返回一致的排序结果
- 对分页功能至关重要
-
避免使用并行流:
- parallelStream()虽然能提升性能,但会破坏结果一致性
- 在分页场景中,一致性比性能更重要
- 并行处理与非线程安全集合结合易引发并发问题
-
添加排序操作:
- 使用
.sorted()确保元素按固定顺序排列 - 即使数据源顺序发生变化,最终结果仍然一致
- 使用
实际效果
通过以上修改,可以确保:
- 相同查询条件下分页结果始终保持一致
- 用户刷新页面或重新查询时不会看到"跳跃"的数据
- 提升用户体验和系统可靠性
172万+

被折叠的 条评论
为什么被折叠?



