Here is a custom query that returns the authors who sold at least one book:
public class ExtendedAuthorRepositoryImpl implements ExtendedAuthorRepository {
@PersistenceContext protected EntityManager entityManager;
@Override
public Page<AuthorDAO> findAuthorsWithSoldBooks(
String queryExpression, String sortExpression, @NonNull Pageable pageable) {
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
final CriteriaQuery<AuthorDAO> query = builder.createQuery(AuthorDAO.class);
final Root<AuthorDAO> root = query.from(AuthorDAO.class);
final Join<AuthorDAO, BookDAO> book =
root.join(AuthorDAO_.BOOKS, JoinType.INNER);
final List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(builder.greaterThan(book.get("soldCount"), 0));
// fetch the authors as per the page limit
final List<T> result =
entityManager
.createQuery(query)
.setFirstResult((int) pageable.getOffset())
.setMaxResults(pageable.getPageSize())
.getResultList();
// create count query
final CriteriaQuery<Long> countQuery = builder.createQuery(Long.class);
final Root<AuthorDAO> rootCount = countQuery.from(AuthorDAO.class);
countQuery
.select(builder.count(rootCount))
.where(builder.and(predicates.toArray(new Predicate[predicates.size()])));
// fetch the count of all authors as per given criteria
final Long authorCount = entityManager.createQuery(countQuery).getSingleResult();
// return the page
return new PageImpl<>(result, pageable, authorCount);
}
Here is the query generated by Hibernate (I run it on SQL Developer and it worked as expected):
select
c1_0.id,
c1_0.name,
c1_0.surname,
c1_0.birth_date,
c1_0.biography
from author c1_0
join book c2_0 on c1_0.id=c2_0.author_id
where c2_0.sold_count > 0
offset ? rows fetch first ? rows only
Here is the result of the query above on SQL Developer:
1 John Smith 1970-08-08 Biography 1
2 Peter Williams 1966-01-15 Biography 2
3 Arthur Mitchel 1981-04-22 Biography 3
4 Dave Bronson 1988-09-01 Biography 4
Finally, in my test, I tried to retrieve 2 pages of size 2:
final int pageSize = 2;
final int totalPages = 2;
final List<Author> content = IntStream.range(0, totalPages)
.mapToObj(page -> authorApi.getAuthorsWithSoldBooks(null, null, page, pageSize))
.map(AuthorsPaged::getContent)
.flatMap(List::stream)
.toList();
assertThat(content).hasSize(4); // this fails as content has size 3
The problem is that in the first call to findAuthorsWithSoldBooks()
, getResultList()
returns only the first record
1 John Smith
instead of the first 2 records
1 John Smith
2 Peter Williams
Then, in the second call to findAuthorsWithSoldBooks()
, getResultList()
returns the first 2 records, again
1 John Smith
2 Peter Williams
instead of the last 2 records
3 Arthur Mitchel
4 Dave Bronson
Am I missing something?
UPDATE
Here is the source of authorApi.getAuthorsWithSoldBooks
:
public AuthorsPaged getAuthorsWithSoldBooks(
final String queryExpression,
final String sortExpression,
final Integer page,
final Integer size) {
final Page<AuthorDTD> AuthorPage = authorRepository.findAuthorsWithSoldBooks(
queryExpression,
sortExpression,
PageRequest.of(page, size));
return authorConverter.convert(AuthorPage);
}
Logs:
16:49:23.334 TRACE [POLICY_DFLT|1|0|190101|190101] - [org.hibernate.orm.jdbc.bind] - binding parameter [1] as [INTEGER] - [0]
16:49:23.334 TRACE [POLICY_DFLT|1|0|190101|190101] - [org.hibernate.orm.jdbc.bind] - binding parameter [2] as [INTEGER] - [2]
16:49:23.459 TRACE [POLICY_DFLT|1|0|190101|190101] - [org.hibernate.orm.jdbc.bind] - binding parameter [1] as [INTEGER] - [2]
16:49:23.459 TRACE [POLICY_DFLT|1|0|190101|190101] - [org.hibernate.orm.jdbc.bind] - binding parameter [2] as [INTEGER] - [2]
11