Spring-Data FETCH JOIN with Paging is not working - java

I am trying to use HQL fetching my entity along with sub-entities using JOIN FETCH, this is working fine if I want all the results but it is not the case if I want a Page
My entity is
#Entity
#Data
public class VisitEntity {
#Id
#Audited
private long id;
.
.
.
#OneToMany(cascade = CascadeType.ALL,)
private List<VisitCommentEntity> comments;
}
and because I have millions of visits I need to use Pageable and I want to Fetch the comments in a single database query like :
#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);
That HQL call throws the following exception:
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)
and once I remove the paging everything works fine
#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,...);
Obviously the problem is the count query from Spring-Data, but how can we fix it?

The easiest way is to use the countQuery attribute of the the #Query annotation to provide a custom query to be used.
#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, …);

Alternatively in newest versions of Spring (supporting JPA 2.1 specification) you can use entity graph like this:
#EntityGraph(attributePaths = "roles")
#Query("FROM User user")
Page<User> findAllWithRoles(Pageable pageable);
Of course named entity graphs work as well.

You have to specify countQuery param for #Query and now you can use Page or List as return value.
#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);

If you want completely control your query build by Specification with join fetch you can check CriteriaQuery return type and change join fetch logic according to query type like this:
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");
}
}
I was not able to find this info in documentation, but from SimpleJpaRepository.getCountQuery() method you can see query for count request first build for Long return type, and later fetch for expected class is running.
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Long> query = builder.createQuery(Long.class);
Root<S> root = applySpecificationToCriteria(spec, domainClass, query);
It can be not reliable since it is an implementation details which can be changed, but it works.

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

Related

Pagination on Native sql query gives : unexpected token: ON

While writing a pagination query in repository as below code segment, I'm getting following error.
Code
#Repository
public interface Aaaa extends PagingAndSortingRepository<TxnDealerInventoryItem, Long> {
#Query(value = "SELECT EM.PART_NO, EM.PART_NAME FROM TXN_DEALER_INVENTORY_ITEM E INNER JOIN MST_PRODUCT EM ON E.PRODUCT_ID = EM.PRODUCT_ID WHERE E.ACCOUNT_ID= :accountId AND EM.ALLOW_SERIAL_NUM = :isSerialized ORDER BY ?#{#pageable}",
countQuery = "SELECT COUNT(*) FROM TXN_DEALER_INVENTORY_ITEM E INNER JOIN MST_PRODUCT EM ON E.PRODUCT_ID = EM.PRODUCT_ID WHERE E.ACCOUNT_ID= :accountId AND EM.ALLOW_SERIAL_NUM = :isSerialized",
nativeQuery = true)
Page<Object[]> getNonSerializedDeviceList(#Param("accountId") Long accountId, #Param("isSerialized") String isSerialized, Pageable pageable);
}
Error
HQL: SELECT COUNT(*) FROM TXN_DEALER_INVENTORY_ITEM E INNER JOIN MST_PRODUCT EM ON E.PRODUCT_ID = EM.PRODUCT_ID WHERE E.ACCOUNT_ID= :accountId AND EM.ALLOW_SERIAL_NUM = :isSerialized
2023-02-10 18:52:52,753 ERROR [org.hibernate.hql.internal.ast.ErrorCounter] (http-/127.0.0.1:8881-1) line 1:76: unexpected token: ON
Native Query doesn't have any error when run from sql developer.
Framework versions are as follows, Unfortunately I'cant update these any further as there are limitations in deployment environment. You inputs are highly welcome on this !!
<spring.version>4.3.30.RELEASE</spring.version>
<spring.data.version>1.11.23.RELEASE</spring.data.version>
<hibernate.version>4.2.18.Final</hibernate.version>
PS : for testing purpose when I change the SQL to a very basic like a select *, It gives following error.
org.hibernate.hql.internal.ast.QuerySyntaxException: TXN_DEALER_INVENTORY_ITEM is not mapped [SELECT COUNT(*) FROM TXN_DEALER_INVENTORY_ITEM E ]
Got the problem resolved by removing the count query and make the return type to a List of object Array instead of Page as below code segment.
#Repository
public interface Aaaa extends PagingAndSortingRepository<TxnDealerInventoryItem, Long> {
#Query(value = "SELECT EM.PART_NO, EM.PART_NAME FROM TXN_DEALER_INVENTORY_ITEM E INNER JOIN MST_PRODUCT EM ON E.PRODUCT_ID = EM.PRODUCT_ID WHERE E.ACCOUNT_ID= :accountId AND EM.ALLOW_SERIAL_NUM = :isSerialized ORDER BY ?#{#pageable}",
nativeQuery = true)
List<Object[]> getNonSerializedDeviceList(#Param("accountId") Long accountId, #Param("isSerialized") String isSerialized, Pageable pageable);
}
Finally having good old Paginations :) Thanks everyone who looked into this.

How to find all with eager relationship with specification and pagable in JPA?

I have product and kind object with Many To Many relationship. I can get products with eager relationship by:
#Query(
value = "select distinct product from Product product left join fetch product.kinds",
countQuery = "select count(distinct product) from Product product"
)
Page<Product> findAllWithEagerRelationships(Pageable pageable);
or products by specification and pagable by:
#Transactional(readOnly = true)
public Page<Product> findByCriteria(ProductCriteria criteria, Pageable page) {
log.debug("find by criteria : {}, page: {}", criteria, page);
final Specification<Product> specification = createSpecification(criteria);
return productRepository.findAll(specification, page);
}
But when I try to combine them to one,
#Query(
value = "select distinct product from Product product left join fetch product.kinds",
countQuery = "select count(distinct product) from Product product"
)
Page<Product> findAll(Specification<Product> specification, Pageable pageable);
I got error:
Caused by: org.hibernate.HibernateException: firstResult/maxResults specified with collection fetch. In memory pagination was about to be applied. Failing because 'Fail on pagination over collection fetch' is enabled.
So what should I do?
I found the temporary solution. I changed #ManyToMany annotation in entity class to
#ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
then my code work exception. Try this if you want. But I'm not sure about performance with this solution. I need to confirm it.
Update: This solution can resolve load data problem but I will not able to edit data

How select distinct value with pageable Spring data JPA?

I want to make a distinct select in my table with pagination, but it is claiming this error. Does anyone know how to solve it?
org.postgresql.util.PSQLException: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
#Query(value = "SELECT DISTINCT budget.* FROM budget LEFT JOIN user_budget ON budget.id = user_budget.budget_id ORDER BY budget.created DESC, ?#{#pageable}",
countQuery = "SELECT DISTINCT count(*) FROM budget LEFT JOIN user_budget ON budget.id = user_budget.budget_id",
nativeQuery = true)
public Page<Budget> findAllByfilter(Pageable pageable);

Hibernate query cache with Spring Data JPA and Stream is not working

I'm using Hibernate query cache on my Spring Data JPA repository, and all works if I use List as return type, if I use Stream seems that hibernate not cache results:
Not working code:
#Query("select E from Entity E where E.id = :id E.name asc")
#QueryHints({#QueryHint(name = "org.hibernate.cacheable", value = "true")})
Stream<Entity> getProvinceList(#Param("id") String id);
Working code:
#Query("select E from Entity E where E.id = :id E.name asc")
#QueryHints({#QueryHint(name = "org.hibernate.cacheable", value = "true")})
List<Entity> getProvinceList(#Param("id") String id);
Closing or not the stream in the client code, makes no differences.
Anyone has the same problem? Actually I solved using list and convert it to stream in the client code.

CRudRepository native query could not return Result Set

I have a simple test query inside a CrudRepository interface that should return a List of entities.
public interface TestRepository extends CrudRepository<Test, TestId> {
#Query(value = "SELECT p FROM test p ", nativeQuery = true)
public List<Test> getTests();
}
When I test this I get the exception:
org.springframework.dao.InvalidDataAccessResourceUsageException: could
not extract ResultSet
If I don't use native query it works, but I want to use native query because I want to extend the select.
In order to make your query work :-
#Query(value = "SELECT * FROM TEST ", nativeQuery = true)
public List<Test> getTests();
The reason is simply because you are writing native query."SELECT p FROM test p" is not a native query
2 problems
in native SQL use native SQL :)
#Query(value = "SELECT p.* FROM test p ", nativeQuery = true)
your native Query returns an Object[] or a List of Object[].
You can change that, if you provide additional mapping information to the EntityManager.
By doing this you can tell the EntityManager to map the result into managed entities, scalar values of specific types or POJOs.
The simplest way to map the result of a native query into a managed entity is to select all properties of the entity and provide its as a parameter to the createNativeQuery method.
(sorry for using other examples)
Query q = em.createNativeQuery("SELECT a.id, a.version, a.firstname, a.lastname FROM Author a", Author.class);
List<Author> authors = q.getResultList();
All other mappings, like the following one which maps the query result into a POJO, need to be defined as SQLResultSetMappings.
#SqlResultSetMapping(
name = "AuthorValueMapping",
classes = #ConstructorResult(
targetClass = AuthorValue.class,
columns = {
#ColumnResult(name = "id", type = Long.class),
#ColumnResult(name = "firstname"),
#ColumnResult(name = "lastname"),
#ColumnResult(name = "numBooks", type = Long.class)}))
To use this mapping, you need to provide the name of the mapping as a parameter to the createNativeQuery method.
Query q = em.createNativeQuery("SELECT a.id, a.firstname, a.lastname, count(b.id) as numBooks FROM Author a JOIN BookAuthor ba on a.id = ba.authorid JOIN Book b ON b.id = ba.bookid GROUP BY a.id", "AuthorValueMapping");
List<AuthorValue> authors = q.getResultList();
#Query(value = "SELECT * FROM test p ", nativeQuery = true)

Categories