【问题标题】:Spring Data JPA Pagination HHH000104Spring Data JPA 分页 HHH000104
【发布时间】:2021-02-24 04:49:44
【问题描述】:

我得到了这个存储库代码:

@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 specified with collection fetch; applying in memory!

@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 子句。虽然它确实触发了两个查询。

一个用于count,另一个用于获取所有记录。
因此它会获取所有记录并在内存中应用分页。
这会导致查询执行速度非常慢。

我怎样才能使分页与这个查询一起工作?

编辑

我知道这里经常被建议作为解决方案: How can I avoid the Warning "firstResult/maxResults specified with collection fetch; applying in memory!" when using Hibernate?

有没有办法使用 Spring Data JPA 实现分页? 我不想硬连线EntityManager,我也不想 从BasicTransformerAdapter扩展代码

【问题讨论】:

标签: spring-boot hibernate jpa spring-data-jpa


【解决方案1】:

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

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

实现自定义 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阅读更多内容

【讨论】:

    【解决方案2】:

    我自己找到了解决方法。基于此:
    How can I avoid the Warning "firstResult/maxResults specified with collection fetch; applying in memory!" when using Hibernate?

    首先:通过分页获取ID:

    @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);
    

    第二:使用 Id 进行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&lt;Reference 而不是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 进行查询,所以只获取需要的记录。

    【讨论】:

      【解决方案3】:

      先获取 id 然后进行主查询的方法有效,但效率不高。我认为这是Blaze-Persistence 的完美用例。

      Blaze-Persistence 是基于 JPA 的查询构建器,它支持 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 ...
      

      【讨论】:

        猜你喜欢
        • 2013-09-15
        • 2016-02-01
        • 2017-04-04
        • 2012-05-25
        • 2016-11-10
        • 2016-12-01
        • 1970-01-01
        • 1970-01-01
        • 2014-09-20
        相关资源
        最近更新 更多