1

我在 Criteria API 查询中实现了可分页功能,并且我注意到在查询执行期间内存使用量增加。我还使用 spring-data-jpa 方法查询来返回相同的结果,但是在处理完每个批次后都会清理内存。我尝试从 EntityManager 中分离、刷新、清除对象,但内存使用量会继续上升,偶尔会下降,但不如方法查询那么多。我的问题是,如果对象被分离,什么会导致这种内存使用以及如何处理它?

Criteria API 可分页的内存使用情况: 在此处输入图像描述

方法查询的内存使用情况:

在此处输入图像描述

代码

由于我也在更新从数据库中检索到的实体,因此我使用保存最后处理实体的 ID 的方法,因此当实体获得更新时,查询不会跳过下一个选定页面。下面我提供的代码示例并非来自我正在开发的真实应用程序,但它只是重现了我遇到的问题。

存储库代码:

@Override
public Slice<Player> getPlayers(int lastId, Pageable pageable) {
    List<Predicate> predicates = new ArrayList<>();

    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Player> criteriaQuery = criteriaBuilder.createQuery(Player.class);
    Root<Player> root = criteriaQuery.from(Player.class);

    predicates.add(criteriaBuilder.greaterThan(root.get("id"), lastId));

    criteriaQuery.where(criteriaBuilder.and(predicates.toArray(Predicate[]::new)));
    criteriaQuery.orderBy(criteriaBuilder.asc(root.get("id")));

    var query = entityManager.createQuery(criteriaQuery);

    if (pageable.isPaged()) {
        int pageSize = pageable.getPageSize();
        int offset = pageable.getPageNumber() > 0 ? pageable.getPageNumber() * pageSize : 0;

        // Fetch additional element and skip it based on the pageSize to know hasNext value.
        query.setMaxResults(pageSize + 1);
        query.setFirstResult(offset);

        var resultList = query.getResultList();

        boolean hasNext = pageable.isPaged() && resultList.size() > pageSize;
        return new SliceImpl<>(hasNext ? resultList.subList(0, pageSize) : resultList, pageable, hasNext);
    } else {
        return new SliceImpl<>(query.getResultList(), pageable, false);
    }
}

遍历可分页:

@Override
public Slice<Player> getAllPlayersPageable() {
    int lastId = 0;
    boolean hasNext = false;
    Pageable pageable = PageRequest.of(0, 200);
    do {
        var players = playerCriteriaRepository.getPlayers(lastId, pageable);

        if(!players.isEmpty()){
            lastId = players.getContent().get(players.getContent().size() - 1).getId();

            for(var player : players){
                System.out.println(player.getFirstName());
                entityManager.detach(player);
            }
        }
        hasNext = players.hasNext();
    } while (hasNext);
    return null;
}
4

1 回答 1

0

我认为您在这里遇到了与使用 JPA Criteria API 以及如何处理数值有关的查询计划缓存问题。Hibernate 会将所有数值作为文字呈现到中间 HQL 查询字符串中,然后编译该字符串。可以想象,每次“滚动”到下一页都将是一个新的查询字符串,因此您会逐渐填满查询计划缓存。

一种可能的解决方案是使用像Blaze-Persistence这样的库,它具有自定义JPA Criteria API 实现Spring Data 集成,可以避免这些问题,同时由于更好的分页实现而提高查询性能。

您的所有代码都将保持不变,您只需要包含集成并按照设置部分中的说明对其进行配置。

于 2021-09-10T08:51:24.230 回答