QuerydslPredicate with PagingAndSortingRepository - spring

I am new to learning QueryDslPredicate and PagingAndSortingRepository
I want to use use the predicate along with sorting and pagination in my findAll request. How can this be done ?
QTestOrder qTestOrder = QTestOrder.testOrder;
BooleanExpression isUser = qTestOrder.user.eq(userDAO.getUserByPhone(Long.parseLong(params.getFirst(Constant.USER))));
BooleanExpression isId = qTestOrder.id.eq(new Long(params.getFirst(Constant.ID)));
BooleanExpression isStatus = qTestOrder.testStatus.eq(testStatusDAO.getByRank(Integer.parseInt(params.getFirst(Constant.STATUS))));
testOrderRepo.findAll(isUser.or(isId).or(isStatus),qTestOrder.orderCreationTime.desc());
So above returns me list of testOrders, I now want to do pagination over the, using query like below . How do I return all paginated and sorted users based on my query. Is there any method for that or workaround ?
Pageable page = new PageRequest(p, Constant.DEFAULT_PAGE_SIZE);
Page<TestOrder> l = testOrderRepo.findAll(page);

You can implement QueryDslPredicateExecutor<T> in your repository interfaces. This interface provides a method with following signature:
Page<T> findAll(Predicate predicate, Pageable pageable);
Which Returns a page of entities matching the given Predicate.
Suppose you have domain entity, say TestOrder, then you can define a TestOrderRepository like following:
public interface TestOrderRepository extends QueryDslPredicateExecutor<TestOrder> {}
and finally you can:
Pageable pagable = new QPageRequest(1, 10, your_order_specifier)
testOrderRepository.findAll(isUser.or(isId).or(isStatus), pageable);

Related

Hibernate Criteria FetchMode.JOIN is doing lazy loading

I have a paginated endpoint which internally uses Hibernate Criteria to fetch certain objects and relations. The FetchMode is set as FetchMode.JOIN.
When I am trying to hit the endpoint, the request seems to work fine for few pages but is then erring out with :
could not initialize proxy - no Session
Method is as as below:
#Override
public Page<Person> findAllNotDeleted(final Pageable pageable)
{
final var criteria = createCriteria();
criteria.add(Restrictions.or(Restrictions.isNull(DELETED), Restrictions.eq(DELETED, false)));
criteria.setFetchMode(PERSON_RELATION, FetchMode.JOIN);
criteria.setFetchMode(DEPARTMENT_RELATION, FetchMode.JOIN);
criteria.setFirstResult((int) pageable.getOffset());
criteria.setMaxResults(pageable.getPageSize());
criteria.addOrder(asc("id"));
final var totalResult = getTotalResult();
return new PageImpl<>(criteria.list(), pageable, totalResult);
}
private int getTotalResult()
{
final Criteria countCriteria = createCriteria();
countCriteria.add(Restrictions.or(Restrictions.isNull(DELETED), Restrictions.eq(DELETED, false)));
return ((Number) countCriteria.setProjection(Projections.rowCount()).uniqueResult()).intValue();
}
Also, the call to findAllNotDeleted is done from a method anotated with #Transactional.
Not sure what is going wrong.
Any help would be highly appreciated.
EDIT
I read that FetchMode.Join does not work with Restrictions. So I tried implementing it using CriteriaBuilder but again stuck with the issue.
#Override
public Page<Driver> findAllNotDeleted(final Pageable pageable)
{
final var session = getCurrentSession();
final var builder = session.getCriteriaBuilder();
final var query = builder.createQuery(Person.class);
final var root = query.from(Driver.class);
root.join(PERSON_RELATION, JoinType.INNER)
.join(DEPARTMENT_RELATION,JoinType.INNER);
//flow does not reach here.....
var restrictions_1 = builder.isNull(root.get(DELETED));
var restrictions_2 = builder.equal(root.get(DELETED), false);
query.select(root).where(builder.or(restrictions_1,restrictions_2));
final var result = session.createQuery(query).getResultList();
return new PageImpl<>(result, pageable, result.size());
}
The flow does not seem to reach after root.join.
EDIT-2
The relations are as follows:
String PERSON_RELATIONSHIP = "person.address"
String DEPARTMENT_RELATION = "person.department"
and both person, address, department themselves are classes which extend Entity
I guess the associations you try to fetch i.e. PERSON_RELATION or DEPARTMENT_RELATION are collections? In such a case, it is not possible to directly do pagination on the entity level with Hibernate. You would have to fetch the ids first and then do a second query to fetch just the entities with the matching ids.
You could use Blaze-Persistence on top of Hibernate though which has a special pagination API that does these tricks for you behind the scenes. Here is the documentation about the pagination: https://persistence.blazebit.com/documentation/core/manual/en_US/index.html#pagination
There is also a Spring Data integration, so you could also use the Spring Data pagination convention along with Blaze-Persistence Entity-Views which are like Spring Data Projections on steroids. You'd use Page<DriverView> findByDeletedFalseOrDeletedNull(Pageable p) with
#EntityView(Driver.class)
interface DriverView {
Long getId();
String getName();
PersonView getPersonRelation();
DepartmentView getDepartmentRelation();
}
#EntityView(Person.class)
interface PersonView {
Long getId();
String getName();
}
#EntityView(Department.class)
interface DepartmentView {
Long getId();
String getName();
}
Using entity views will only fetch what you declare, nothing else. You could also use entity graphs though:
#EntityGraph(attributePaths = {"personRelation", "departmentRelation"})
Page<Driver> findByDeletedFalseOrDeletedNull(Pageable p);

Spring Data JDBC - Pageable on custom Query

On my Project I have a Repository that extends CrudRepository. Inside there I have a custom query:
public interface CustomerRepository extends CrudRepository<Customer, Long> {
#Query("select * from person where firstname = :firstname")
List<Customer> findByFirstname(#Param("firstname") String firstname, Pageable pageable);
}
in my Service-Class I try to put the List in a Pageable - Object like:
... getPageableCustomer(String firstname, Pageable pageable){
// => By using "Sol" I got 90 matching entries
List<Customer> custList = customerRepository.findByFirstname(firstname, pageable);
Page<Customer> custPage = new PageImpl<Customer>(custList, pageable, custList.size());
return custPage;
}
the return value includes the complete List "custList". What would be the best way to get a pageable object with specified offset and size?
One option could be to use
customer.subList(fromIndex, toIndex)
but that feels wrong. Also because of Loading all Data inside the list instead of just getting data by size and offset as parameterized with pageable.
Remark: In case of using Page inside the Repository I ´ll get org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 88
There is also a open Improvement on Jira that could be found here:
https://jira.spring.io/browse/DATAJDBC-554?filter=-3
hope for some help...
I got a response on the JIRA-Issue from Dirk Luijk (Thx Dirk :))
https://jira.spring.io/browse/DATAJDBC-554?filter=-3
interface FooRepository extends PagingAndSortingRepository<FooEntity, Long> {
List<FooEntity> findAllByBar(String bar, Pageable pageable);
Long countAllByBar(String bar);
}
And then combining those 2 queries like this:
List<FooEntity> fooList = repository.findAllByBar("...", pageable);
Long fooTotalCount = repository.countAllByBar("...");
Page<FooEntity> fooPage = PageableExecutionUtils.getPage(fooList, pageable, () -> fooTotalCount);
"the mistake in your workaround is your custom query. In Spring Data JDBC 2.0 you don't need to use that, except for special queries but they won't support pageables."
Possible Parameters could be found:
https://docs.spring.io/spring-data/jdbc/docs/current/reference/html/#jdbc.query-methods
Thx Dirk,
I also find a workaround to get it running with a custom Query. Just use limit, offset and orderBy as additional Parameter like so:
#Query("select * from person where firstname = :name order by :order limit :size offset :offset")
List<Customer> findByFirstNameCustomQuery(#Param("name") String name, Pageable page, #Param("offset") long offset,
#Param("size") long size, #Param("order") String order);
And than change the call inside the Service like:
List<Customer> custList = customerRepository.findByFirstNameCustomQuery(firstname, pageable, ....

Combine provided queryDSL predicates with predefined queries

In context of Spring Data REST I would like to expose search/filter functionality on the result of the single query.
If my query was findAll that would be out of the box (it would be enough to extend QueryDslPredicateExecutor). But how can I combine my custom query (findMyNotes) with additional filtering provided by the user (that are marked as done). I mean something like:
#Entity
class Note {
#Id
private UUID id;
private UUID personId;
private String status;
private String text;
}
#RepositoryRestResource(path = "notes", itemResourceRel = "notes", collectionResourceRel = "notes")
interface NoteRepository extends JpaRepository<Note, UUID>, QueryDslPredicateExecutor<Note> {
#RestResource(path="notes/my")
#Query("select n from Note n where n.personId=:creatorId")
Page<Note> findDone(#Param("creatorId") UUID creatorId, Predicate predicate, Pageable pageable);
}
I didn't find anything out of the box. Therefore I was thinking to use AOP and:
have multiple paths (multiple #RestResource ?) that map to org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(Predicate, org.springframework.data.domain.Pageable)
have my own annotation that would point me to the method that returns the original queryDSL Predicate (contents of #Query in my example)
by AOP calculate AND of those two predicates and pass it as a parameter to findAll method
The problem is that I don't know how to do the first point (apart from hacking it in servlet filters) - map multiple paths to the same findAll method.
Or alternatively is there maybe something working out of the box that I missed?

How can I use Spring's pagination (using Pageable) while writing a dynamic query using QueryDSL?

I am trying to use pagination with QueryDSL - using the com.mysema.querydsl package.
All my Querydsl query types look like this -
#Generated("com.mysema.query.codegen.EntitySerializer")
public class QCountry extends EntityPathBase<Country> {...}
Currently, my repository implementation class looks something like this -
#Override
public Page<Country> findPaginatedCountries(String country, Optional<String> status, Pageable pageable) {
QCountry qCountry= QCountry.someObject;
QActiveCountry qActiveCountry = QActiveCountry.activeCountry;
JPAQuery jpaQuery = new JPAQuery(entityManager);
QueryBase queryBase = jpaQuery.from(qCountry).innerJoin(qActiveCountry).fetch()
.where(qCountry.codeLeft.country.upper().eq(country.toUpperCase()))
.where(qCountry.codeRight.country.upper().eq(country.toUpperCase()));
if(status.isPresent()){
queryBase = queryBase.where(qActiveCountry.id(qCountry.active.id))
.where(qActiveCountry.status.upper().eq(status.get().toUpperCase()));
}
.......}
Now, I want this dynamic query to return a paginated response. I want to use Spring's pagination to do that and not manually set offset, size etc.
I know I can use QueryDslRepositorySupport class - as implemented here - https://github.com/keke77/spring-data-jpa-sample/blob/master/spring-data-jpa/src/main/java/com/gmind7/bakery/employee/EmployeeRepositoryImpl.java
Sample code from the above link -
#Override
public Page<Employees> QFindByOfficeCode(long officeCode, Pageable pageable) {
//JPAQuery query = new JPAQuery(em);
JPQLQuery query = from(QEmployees.employees).where(QEmployees.employees.officeCode.eq(officeCode));
query = super.getQuerydsl().applyPagination(pageable, query);
SearchResults<Employees> entitys = query.listResults(QEmployees.employees);
return new PageImpl<Employees>(entitys.getResults(), pageable, entitys.getTotal());
}
However, to do that -
I need to pass JPQLQuery object to the applyPagination method. How can I do that without changing my code (Ofcourse, the repository class will extend QueryDslRepositorySupport class). Currently, I am using JPAQuery as you can see.
OR
I probably need to change my QueryDSL types by having them extend EntityPath instead of EntityPathBase so that I can use JPQLQuery.from() to generate the query and then use the applyPagination method, which requires a JPQLQuery object. However, my Q classes are extending EntityPathBase class instead. Should I be use com.querydsl package instead of com.mysemsa.querydsl package to generate query types?
OR
Other option is to use the following - http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/querydsl/QueryDslPredicateExecutor.html#findAll-com.querydsl.core.types.Predicate-org.springframework.data.domain.Pageable-
Code snippet below -
Page<T> page = QueryDslPredicateExecutor.findAll(org.springframework.data.querydsl.Predicate predicate, Pageable pageable)
However, I am making joins between two tables and then filtering results with a where clause (as you can see above in my code). How can I pass a predicate object in the findAll method above? Not sure how to include a join in it.
Please let me know if the problem is not clear, I can add more details.
EDIT: There is a many to one relationship between Country and ActiveCountry.
Country class has an ActiveCountry reference. And we have to do a join between both ids. Is is possible that Country can have null ActiveCountry. Therefore, we want an inner join - only non null values for active country
#ManyToOne
#JoinColumn(name="id")
ActiveCountry active;
Step 1: Annotate the entity class with #QueryEntity
#Entity
#QueryEntity
public class Country {}
This seems to have been addressed already since the question shows Q classes.
Step 2: Have the repository interface extend QueryDslPredicateExecutor
public interface CountryRepository
extends PagingAndSortingRepository<Country, Long>
, QueryDslPredicateExecutor<Country> {
}
Step 3: Invoke the Page<T> findAll(Predicate query, Pageable page) method provided by QueryDslPredicateExecutor
public Page<Country> getCountries(String country, Optional<String> status, Pageable page) {
QCountry root = QCountry.country;
BooleanExpression query = root.codeLeft.country.equalsIgnoreCase(country);
query = query.and(root.codeRight.country.equalsIgnoreCase(country));
if (status.isPresent()) {
query = query.and(root.active.status.equalsIgnoreCase(status));
}
return countryRepository.findAll(query, page);
}

How to write a findByCritera1InAndCritera2In using (spring data) CrudRepository with parameters being optional

I'm using spring boot with spring data, specifically the class PagingAndSortingRepository that extends CrudRepository.
I need a query which returns all entries of a table if matches either of the four lists ignoring it when it is null (or empty).
If I use findByTypeInAndLocaleInAndCategoryInAndTagIn and one of the lists is empty, the result is empty as well. So I ended up writing several finders and depending on which lists are empty using a different one. Is it possible to combine this in one finder?
So e.g. if I use findByTypeAndLocale I'd like to match all values of type if the list type is empty.
Happy about any hints.
#Repository
public interface FeedRepository extends PagingAndSortingRepository<FeedEntry, Long>, JpaSpecificationExecutor<FeedEntry> {
public List<FeedEntry> findByGuid(String guid);
public Page<FeedEntry> findAll(Pageable pageable);
public Page<FeedEntry> findByLocale(List<LocaleEnum> type, Pageable pageable);
public Page<FeedEntry> findByType(List<FeedTypeEnum> type, Pageable pageable);
public Page<FeedEntry> findByTypeAndLocale(FeedTypeEnum type, LocaleEnum locale, Pageable pageable);
public Page<FeedEntry> findByTypeInAndLocaleIn(List<FeedTypeEnum> type,List<LocaleEnum> locale, Pageable pageable);
public Page<FeedEntry> findByTypeInAndLocaleInAndCategoryIn(List<FeedTypeEnum> type,List<LocaleEnum> locale, List<String> category, Pageable pageable);
public Page<FeedEntry> findByTypeInAndLocaleInAndTagIn(List<FeedTypeEnum> type,List<LocaleEnum> locale, List<String> tag, Pageable pageable);
public Page<FeedEntry> findByTypeInAndLocaleInAndCategoryInAndTagIn(List<FeedTypeEnum> type,List<LocaleEnum> locale, List<String> category, List<String> tag, Pageable pageable);
}
You cannot do it as you are doing. If the list is empty means there isn't any value which matches with your condition query.
To do what you are looking for you need to do it with QBE (Query By example) which is compatible with CrudRepository
QBE doc
Why? You need a dynamic query and as the doc says:
Query by Example (QBE) is a user-friendly querying technique with a
simple interface. It allows dynamic query creation and does not
require to write queries containing field names. In fact, Query by
Example does not require to write queries using store-specific query
languages at all.
An example of the doc:
public interface PersonRepository extends JpaRepository<Person, String> { … }
public class PersonService {
#Autowired PersonRepository personRepository;
public List<Person> findPeople(Person probe) {
return personRepository.findAll(Example.of(probe));
}
}
**Making example for your case...
I'm affraid it is not possible at the moment.
IMHO the best way you can achieve your goal is to use Spring Data JPA Specifications (http://docs.spring.io/spring-data/jpa/docs/1.10.2.RELEASE/reference/html/#specifications) and manually check every parameter for not null value...

Resources