Spring Data JPA 分页 HHH000104

4

我拿到了这个代码仓库:

@Query(value = "select distinct r from Reference r " +
        "inner join fetch r.persons " +
        "left outer join fetch r.categories " +
        "left outer join fetch r.keywords " +
        "left outer join fetch r.parentReferences",
        countQuery = "select count(distinct r.id) from Reference r " +
                "inner join r.persons " +
                "left outer join r.categories " +
                "left outer join r.keywords " +
                "left outer join r.parentReferences")
Page<Reference> findsAllRelevantEntries(Pageable pageable);

当我在该查询上运行测试时,我得到了这个Hibernate警告:
HHH000104:使用集合获取指定了firstResult/maxResults;将应用于内存中!

@Test
void testFindAllRelevantAsync() throws ExecutionException, InterruptedException {
    CompletableFuture<Page<Reference>> all = referenceService.findAllRelevantAsync(PageRequest.of(1, 20));
    CompletableFuture.allOf(all).join();
    assertThat(all.get()).isNotNull();
    assertThat(all.get()).isNotEmpty();
}

代码仓库被封装在一个未展示的服务方法中。该服务方法只是将调用从服务传递到代码仓库,然后再传回来。

此外,生成的 SQL 查询不会生成一个 limit 子句。尽管它会触发两个查询。

其中一个是用于计数,另一个是用于获取所有记录。
因此,它获取所有记录并在内存中应用分页。
这导致查询执行非常缓慢。

如何使该查询支持分页?

编辑

我知道经常建议使用以下解决方案: 如何避免在 Hibernate 中使用集合获取时出现“firstResult/maxResults 指定错误;正在应用于内存!”的警告?

有没有一种方式可以使用 Spring Data JPA 实现分页? 我既不想硬编码一个 EntityManager,也不想 扩展 BasicTransformerAdapter 的代码。


谢谢,但请看我的编辑。 - Thomas Lang
3个回答

3

您可以使用基于两个查询方法的通用/可重用方法。

一个SQL查询用于检索实体的ID,第二个查询带有一个IN谓词,包括来自第二个查询的ID。

实现自定义Spring Data JPA Executor:

@NoRepositoryBean
public interface AsimioJpaSpecificationExecutor<E, ID extends Serializable> extends JpaSpecificationExecutor<E> {

  Page<ID> findEntityIds(Pageable pageable);
}


public class AsimioSimpleJpaRepository<E, ID extends Serializable> extends SimpleJpaRepository<E, ID>
        implements AsimioJpaSpecificationExecutor<E, ID> {

  private final EntityManager entityManager;
  private final JpaEntityInformation<E, ID> entityInformation;

  public AsimioSimpleJpaRepository(JpaEntityInformation<E, ID> entityInformation, EntityManager entityManager) {
    super(entityInformation, entityManager);
    this.entityManager = entityManager;
    this.entityInformation = entityInformation;
  }

  @Override
  public Page<ID> findEntityIds(Pageable pageable) {
    CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
    CriteriaQuery<ID> criteriaQuery = criteriaBuilder.createQuery(this.entityInformation.getIdType());
    Root<E> root = criteriaQuery.from(this.getDomainClass());

    // Get the entities ID only
    criteriaQuery.select((Path<ID>) root.get(this.entityInformation.getIdAttribute()));

    // Update Sorting
    Sort sort = pageable.isPaged() ? pageable.getSort() : Sort.unsorted();
    if (sort.isSorted()) {
      criteriaQuery.orderBy(toOrders(sort, root, criteriaBuilder));
    }

    TypedQuery<ID> typedQuery = this.entityManager.createQuery(criteriaQuery);

    // Update Pagination attributes
    if (pageable.isPaged()) {
      typedQuery.setFirstResult((int) pageable.getOffset());
      typedQuery.setMaxResults(pageable.getPageSize());
    }

    return PageableExecutionUtils.getPage(typedQuery.getResultList(), pageable,
      () -> executeCountQuery(this.getCountQuery(null, this.getDomainClass())));
  }

  protected static long executeCountQuery(TypedQuery<Long> query) {
    Assert.notNull(query, "TypedQuery must not be null!");

    List<Long> totals = query.getResultList();
    long total = 0L;

    for (Long element : totals) {
      total += element == null ? 0 : element;
    }

    return total;
  }
}

你可以在https://tech.asimio.net/2021/05/19/Fixing-Hibernate-HHH000104-firstResult-maxResults-warning-using-Spring-Data-JPA.html阅读更多关于使用Spring Data JPA修复Hibernate HHH000104 firstResult maxResults警告的内容。

1

我自己找到了一个解决方法。基于这个:
如何在使用Hibernate时避免警告“使用集合获取firstResult/maxResults; 在内存中应用!”?

首先:通过分页获取Ids:

@Query(value = "select distinct r.id from Reference r " +
        "inner join r.persons " +
        "left outer join r.categories " +
        "left outer join r.keywords " +
        "left outer join r.parentReferences " +
        "order by r.id",
        countQuery = "select count(distinct r.id) from Reference r " +
                "inner join r.persons " +
                "left outer join r.categories " +
                "left outer join r.keywords " +
                "left outer join r.parentReferences " +
                "order by r.id")
Page<UUID> findsAllRelevantEntriesIds(Pageable pageable);

第二步:使用Ids进行一个in查询。
@Query(value = "select distinct r from Reference r " +
        "inner join fetch r.persons " +
        "left outer join fetch r.categories " +
        "left outer join fetch r.keywords " +
        "left outer join fetch r.parentReferences " +
        "where r.id in ?1 " +
        "order by r.id",
        countQuery = "select count(distinct r.id) from Reference r " +
                "inner join r.persons " +
                "left outer join r.categories " +
                "left outer join r.keywords " +
                "left outer join r.parentReferences ")
@QueryHints(value = {@QueryHint(name = "hibernate.query.passDistinctThrough", value = "false")},
        forCounting = false)
List<Reference> findsAllRelevantEntriesByIds(UUID[] ids);

注意:我得到的是一个`List`而不是一个`Pageable`,所以你需要自己构建你的`Pageable`,像这样:
private Page<Reference> processResults(Pageable pageable, Page<UUID> result) {
    List<Reference> references = referenceRepository.findsAllRelevantEntriesByIds(result.toList().toArray(new UUID[0]));
    return new PageImpl<>(references, pageable, references.size());
}

这段代码看起来不太好,并且有两个语句,但是它使用了limit进行查询,因此仅获取所需记录。

1
首先获取ID再进行主查询的方法可行但不太高效。我认为这是Blaze-Persistence的完美应用案例。
Blaze-Persistence是建立在JPA之上的查询构建器,支持许多高级DBMS功能。它提供的分页支持可以处理您可能遇到的所有问题。
它还具有Spring Data集成,因此您可以像现在一样使用相同的代码,只需添加依赖项并进行设置:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-setup Blaze-Persistence有许多不同的分页策略可供配置。默认策略是将ID查询内联到主查询中。类似于这样:
select r 
from Reference r 
inner join r.persons
left join fetch r.categories 
left join fetch r.keywords
left join fetch r.parentReferences 
where r.id IN (
  select r2.id
  from Reference r2
  inner join r2.persons
  order by ...
  limit ...
)
order by ...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接