带有分页的 Spring-Data FETCH JOIN 不起作用

Posted

技术标签:

【中文标题】带有分页的 Spring-Data FETCH JOIN 不起作用【英文标题】:Spring-Data FETCH JOIN with Paging is not working 【发布时间】:2014-02-28 05:52:36 【问题描述】:

我正在尝试使用 HQL 来获取我的实体以及使用 JOIN FETCH 的子实体,如果我想要所有结果,这可以正常工作,但如果我想要页面则不是这样

我的实体是

@Entity
@Data
public class VisitEntity 

    @Id
    @Audited
    private long id;

    .
    .
    .   

    @OneToMany(cascade = CascadeType.ALL,)
    private List<VisitCommentEntity> comments;

因为我有数百万次访问,所以我需要使用 Pageable,并且我想在单个数据库查询中获取 cmets,例如:

@Query("SELECT v FROM VisitEntity v LEFT JOIN FETCH v.comments WHERE v.venue.id = :venueId and ..." )
public Page<VisitEntity> getVenueVisits(@Param("venueId") long venueId,...,
        Pageable pageable);

该 HQL 调用引发以下异常:

Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElementexplicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=null,role=com.ro.lib.visit.entity.VisitEntity.comments,tableName=visitdb.visit_comment,tableAlias=comments1_,origin=visitdb.visit visitentit0_,columns=visitentit0_.visit_id ,className=com.ro.lib.visit.entity.VisitCommentEntity] [select count(v) FROM com.ro.lib.visit.entity.VisitEntity v LEFT JOIN FETCH v.comments WHERE v.venue.id = :venueId and (v.actualArrival > :date or v.arrival > :date)]
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1374)
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1310)
at org.hibernate.ejb.AbstractEntityManagerImpl.createQuery(AbstractEntityManagerImpl.java:309)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

一旦我删除分页,一切正常

@Query("SELECT v FROM VisitEntity v LEFT JOIN FETCH v.comments WHERE v.venue.id = :venueId and  ..." )
public List<VisitEntity> getVenueVisits(@Param("venueId") long venueId,...);

显然问题是来自 Spring-Data 的计数查询,但我们该如何解决呢?

【问题讨论】:

【参考方案1】:

最简单的方法是使用@Query 注释的countQuery 属性来提供要使用的自定义查询。

@Query(value = "SELECT v FROM VisitEntity v LEFT JOIN FETCH v.comments …",
       countQuery = "select count(v) from VisitEntity v where …")
List<VisitEntity> getVenueVisits(@Param("venueId") long venueId, …);

【讨论】:

嗯,很好,但第二行,countQuery = "select count(v) from VisitEntity v where …") 谢谢,已解决! :) 临时:Oliver 你能确认一下吗? ***.com/questions/21203875/… 如果您复制了实际查询并发布为countQuery,请不要忘记删除FETCHcountQuery 中不应有 FETCH 但这会导致警告:HHH000104: firstResult/maxResults specified with collection fetch; applying in memory when using pagination..【参考方案2】:

您必须为@Query 指定countQuery 参数,现在您可以使用PageList 作为返回值。

@Query(value = "SELECT v FROM VisitEntity v LEFT JOIN FETCH v.comments WHERE v.venue.id = :venueId and ...",
       countQuery = "SELECT count(v) FROM VisitEntity v LEFT JOIN v.comments WHERE v.venue.id = :venueId and ..." )
public Page<VisitEntity> getVenueVisits(@Param("venueId") long venueId,...,
        Pageable pageable);

【讨论】:

您是否必须为countQuery 获取孩子(例如v.comments)?或者以下就足够了"SELECT count(v) FROM VisitEntity v WHERE v.venue.id = :venueId and ..." 对,countQuery,不用指定fetch。【参考方案3】:

或者在最新版本的 Spring(支持 JPA 2.1 规范)中,您可以像这样使用实体图:

@EntityGraph(attributePaths = "roles")
@Query("FROM User user")
Page<User> findAllWithRoles(Pageable pageable);

当然,命名实体图也可以。

【讨论】:

我阅读了更多关于 >> baeldung.com/spring-data-jpa-named-entity-graphs 的信息。绝对是 2.1+ 的最佳解决方案【参考方案4】:

试试countProjection

@Query(value="SELECT v FROM VisitEntity v LEFT JOIN FETCH v.comments WHERE v.venue.id = :venueId and ..." ,
countProjection = "v.id")
public Page<VisitEntity> getVenueVisits(@Param("venueId") long venueId,...,
    Pageable pageable);

【讨论】:

【参考方案5】:

如果您想通过Specification 使用连接提取完全控制您的查询构建,您可以检查CriteriaQuery 返回类型并根据查询类型更改连接提取逻辑,如下所示:

public class ContactSpecification implements Specification<Contact> 
    @Override
    public Predicate toPredicate(Root<Contact> root, CriteriaQuery<?> query, CriteriaBuilder cb) 
        if(query.getResultType() == Long.class) 
            root.join(Contact_.company);
         else 
            root.fetch(Contact_.company);
        
        return cb.equal(root.get(Contact_.company).get(Company_.name), "Company 123");
    

我无法在文档中找到此信息,但从 SimpleJpaRepository.getCountQuery() 方法中,您可以看到查询 count 请求首先为 Long 返回类型构建,然后获取预期类正在运行。

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Long> query = builder.createQuery(Long.class);

Root<S> root = applySpecificationToCriteria(spec, domainClass, query);

它可能不可靠,因为它是一个可以更改的实现细节,但它可以工作。

【讨论】:

以上是关于带有分页的 Spring-Data FETCH JOIN 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

带有分页的 Spring Data 和 Native Query

SQL Server2012 T-SQL对分页的增强尝试

带有分页的 UICollectionView

带有分页的全屏 UICollectionView 内的 UIWebView

带有 uiscrollview 分页的 iOS 渐变动画

带有分页的下一页链接