我正在尝试使用HQL使用JOIN FETCH获取我的实体以及子实体,如果我想要所有结果,这是正常工作但如果我想要一个页面则不是这样的
我的实体是
@Entity
@Data
public class VisitEntity {
@Id
@Audited
private long id;
.
.
.
@OneToMany(cascade = CascadeType.ALL,)
private List<VisitCommentEntity> comments;
}
因为我有数百万次访问,我需要使用Pageable,我想在单个数据库查询中获取注释,如:
@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 [FromElement{explicit,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的count查询,但是我们如何解决呢?
答案 0 :(得分:50)
最简单的方法是使用countQuery
注释的@Query
属性来提供要使用的自定义查询。
@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, …);
答案 1 :(得分:4)
或者在最新版本的Spring(支持JPA 2.1规范)中,您可以使用如下的实体图:
@EntityGraph(attributePaths = "roles")
@Query("FROM User user")
Page<User> findAllWithRoles(Pageable pageable);
当然命名实体图也可以。
答案 2 :(得分:2)
您必须为countQuery
指定@Query
参数,现在您可以使用Page
或List
作为返回值。
@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);
答案 3 :(得分:0)
尝试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);
答案 4 :(得分:0)
如果您想通过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()
方法中,您可以看到查询计数请求的方法:首先为Long返回类型生成计数,然后为期望的类获取数据。
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Long> query = builder.createQuery(Long.class);
Root<S> root = applySpecificationToCriteria(spec, domainClass, query);
它是不可靠的,因为它是可以更改的实现细节,但它可以工作。