当前位置: 首页 > 知识库问答 >
问题:

Spring数据JPA分页HHH000104

狄雅珺
2023-03-14

我得到了存储库代码:

@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警告:
hh000104: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查询不会生成限制子句。尽管它确实触发了两个查询。

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

如何使用此查询进行分页?

编辑

我知道这里经常建议这样做作为解决方案:如何避免出现警告“firstResult/maxResults指定集合获取;在内存中应用!”使用Hibernate时?

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


共有3个答案

冯峻
2023-03-14

先获取ID,然后进行主查询的方法很有效,但效率不高。我认为这是Blaze持久性的完美用例。

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 ...
陆宏扬
2023-03-14

我自己找到了一个解决办法。基于此:
如何避免出现警告“使用集合获取指定的firstResult/maxResults;在内存中应用!”使用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在查询中执行查询

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

注意:我得到一个列表

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());
}

这看起来不太好,并且有两个语句,但它使用限制进行查询,因此只获取所需的记录。

易和怡
2023-03-14

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

一个SQL查询用于检索实体的ID,另一个查询使用包含第二个查询中ID的IN谓词。

实现一个自定义的Spring Data JPA执行器:

@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

 类似资料:
  • 我有一个简单的JpaRepository和一个finder,它返回按名为“number”的属性降序排列的记录。“number”属性也是我的实体的@Id。这很好,但是有数千条记录,所以我想返回一个页面而不是列表。 如果我将查找器更改为以下内容,则排序不再起作用。我尝试过使用可分页参数的排序功能,但不起作用。还删除了OrderByNumberDesc,但结果相同。 EDIT-添加控制器方法 以下是我的

  • 问题内容: 我正在使用Spring Data JPA,当我用来定义一个 WITHOUT 的查询时,它可以工作: 但是,如果我添加第二个参数,则将无法正常工作,Spring将解析该方法的名称,然后抛出 异常 。这是错误吗? 问题答案: 在Spring论坛上提出了一个类似的问题,指出要应用分页,必须派生第二个子查询。因为子查询引用的是相同的字段,所以您需要确保查询对引用的实体/表使用别名。这意味着您在

  • 我在Spring boot版本1.3.6中使用Spring Data JPA。 null 这将返回父实体,而不是子实体。 知道怎么做吗?

  • 我使用了spring boot(1.3.5)、spring-data、spring-data-jpa、JPA(hibernate/hsqldb)。 代码: 控制器: 我试着 也是,但不起作用。 浏览器输出: SQL无效!额外的“:”和重复的“ASC ASC”。 控制台输出:

  • 我有一个简单的查询如下“select * from USERS”。我还使用Pageable来启用分页。 此查询可能具有基于给定参数是否为 null 的可选谓词。 例如,如果给定了“code”参数且该参数不为空,则查询变为“select * from USERS where code =:code”; 据我所知,我不能使用@Query注释来实现这一点。我可以实现一个定制的存储库,并使用EntityM

  • 我在Spring数据(JPA Hibernate MySQL)应用程序中使用NamedNativeQueries和SqlResultSetMappings,我已经成功地完成了分页,但没有完成排序。 我尝试了两种形式的查询: 第二种方法是在第二个查询中简单地使用计数符号,而不是使用#页面符号。 这两种方法都适用于分页,但忽略了排序。 这是存储库: 可分页页面的组装方式如下: 没有抛出错误,但排序没有