How to filter a PageRequest in spring - spring

When I make a request about all the news that I have in my database I use a PageRequest like this:
public Page<StatusUpdate> getPageSiteUser(int pageNumber) {
PageRequest request = new PageRequest(pageNumber-1, pageSize, Sort.Direction.DESC, "added");
return statusUpdateDao.findAll(request);
}
And it shows all the news in each page sorted in perfect order.
But now, I want to select the news created by one user, in the same Pageable format (not all the news) and I do not find how to do it, so I must be making a stupid mistake somewhere... it should be something like...
public Page<StatusUpdate> findMyStatusUpdates(Long user_id, int pageNumber) {
PageRequest request = new PageRequest(pageNumber-1, pageSize, Sort.Direction.DESC, "added");
return statusUpdateDao.findAll(request);
}
Please, answer with a link to the the theory if you can. The documentation talks about sorting, not making the actual selection (enter link description here)

You can combine a query, to filter by the user, with a pageable request. The query can be created by Spring based on the method name.
Take a look to the documentation:
Spring data JPA Query creation
Using Pageable, Slice and Sort in query methods
In your case, without seeing your code, I guess you only need to include in your repository a method like:
public interface StatusUpdateRepository extends Repository<StatusUpdate, Long> {
//finBy<column_name>
Page<User> findByUser(Long userId, Pageable pageable);
}

You can also send a PageRequest to an existing filter in your repo. You can create a repository method, filter them according to your business need.
Let's say you would like to find posts by supplying a user, status attributes and then order them by their creation date in descending order.
One might not desire all posts being included in the Page object while sending them to a Pager, before adding to the Model model.
This is simply equivalent to a sql query as such:
select * from Post where user=user and status=status order by createdAt desc
--
public interface BlogRepository extends PagingAndSortingRepository<Post, Long> {
Page<Post> findByUserAndStatusOrderByCreatedAtDesc(PageRequest pageRequest,
User user, boolean status);
}
and in your controller, use its implementation(postService) along with a new PageRequest object like:
Page<Post> filteredPosts = postService.findByUserAndStatusOrderByCreatedAtDesc(PageRequest.of(evalPage, evalPageSize), user, status);
this works fine for 2.x.x; whereas you better use below for 1.x.x
Page<Post> filteredPosts =
postService.findByUserAndStatusOrderByCreatedAtDesc(new PageRequest(evalPage,
evalPageSize), user, status);
This repo guided me up to a certain point on this matter with paging logic etc.
Just had to change from ModelView to Model in the controller's argument and returned String (template's name) instead.

Related

How to pass pageable for native query?

I was doing a project and there i had a requirement of using pageable object and recieved page object from JPA.
Does anyone have any idea on how to use this?
#Repository
public interface CustomerRepository extends JpaRepository<Customer,Long>{
#Query("SELECT * FROM WHERE name=?1 AND surname=?2 ", nativeQuery = true)
List<Customer> findAllByNameAndSurname(String name,String surname);
}
I want a page List for result fetch from this query.
Spring Data JPA and native queries with pagination is not supported in Spring. According to documentation , Spring Data JPA repositories can be used with native queries and pagination. But, in real life Spring Data JPA (1.10.2.RELEASE) requires some additional fix.
You have to use this if you want pagination support.
List<Customer> customers = customerRepository.findAllByNameAndSurname(name,username);
PagedListHolder<Customer> pages = new PagedListHolder(customers);
pages.setPage(currentPageNumber); //set current page number
pages.setPageSize(pageSize); // set the size of page
pages.getPageList(); // return the list of items(or in your case List<Customer> ) of current page
Try this:
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
Page<Customer> findAllByNameEqualsAndSurnameEquals(String name, String surname, Pageable pageable);
}
I am pretty sure JpaRepository can handle your Pageable parameter.
Also, method name has to be as I mentioned as spring creates queries based on method name (uses reflection).
If you really need to execute NATIVE QUERY you will have to find other solution but I do not recommend the one provided by Dasari Swaroop Kumar as it just queries all objects from database and then kinda filters it in memory.
Also to that native query solution - you can extend your method definition to additional 2 parameters for page and pageSize and append them to your native query and leave repository to return plain List and then create PageImpl object in the layer that calls your CustomerRepository object.

How to access PostgreSQL RETURNING value in Spring Boot DAO?

I want to return the auto-generated id of entity. PostgeSQL is able to automaticaly selects certain column via RETURNING, but I have a hard time trying to find how to retrieve this value in Spring Boot.
I would want something like:
public int createUser(User user) {
String sql = "INSERT INTO user (name, surname) VALUES (?,?) RETURNING id";
return jdbcTemplate.update(sql,
user.getName(),
user.getSurname(),
resultSet -> resultSet.getInt("id")
);
}
I know it's straightforward in Hibernate, then whether you use a Repository class or an EntityManager, the save method returns the saved entity, so you can just do:
int id = userRepository.save(user).getId();
Or is there a reason you want to persist it the way you do?

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, ....

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

Resources