JPA CriteriaQuery реализует Spring Data Pageable.getSort()
Я использовал JPA CriteriaQuery для создания своего динамического запроса и передачи объекта Spring Data Pageable:
sort=name,desc
В бэкэнде у меня есть метод в моем репозитории для поддержки динамического запроса:
public Page<User> findByCriteria(String username, Pageable page) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> iRoot = cq.from(User.class);
List<Predicate> predicates = new ArrayList<Predicate>();
if (StringUtils.isNotEmpty(username)) {
predicates.add(cb.like(cb.lower(iRoot.<String>get("username")), "%" + username.toLowerCase() + "%"));
}
Predicate[] predArray = new Predicate[predicates.size()];
predicates.toArray(predArray);
cq.where(predArray);
TypedQuery<User> query = em.createQuery(cq);
int totalRows = query.getResultList().size();
query.setFirstResult(page.getPageNumber() * page.getPageSize());
query.setMaxResults(page.getPageSize());
Page<User> result = new PageImpl<User>(query.getResultList(), page, totalRows);
return result;
}
Примечание: я поставил только один параметр для демонстрационной цели.
Однако возвращаемые данные не отсортированы, поэтому я хотел бы спросить, есть ли способ реализовать Pageable в CriteriaQuery.
5 ответов
Вы можете добавить сортировку с помощью QueryUtils из spring:query.orderBy(QueryUtils.toOrders(pageable.getSort(), root, builder));
И вопрос Оп, и ответ Анджело не будут работать в реальном сценарии. Если эта таблица/запрос возвращает тысячи записей, этот подход будет работать медленно, и вы также можете создать проблемы с памятью.
Почему?
- int totalRows = query.getResultList().size();
- новый PageImpl(query.getResultList(), page, totalRows);
оба набора результатов будут запускать один и тот же запрос дважды и поддерживать полные данные таблицы/запроса в этих коллекциях, просто для подсчета или нарезки, что не имеет смысла, когда вам нужно показать несколько записей во внешнем интерфейсе.
Мое предложение для Spring Data - расширить интерфейс JpaSpecificationExecutor в вашем репозитории.
@Repository
public interface UserRepository extends JpaRepository<User, Integer>,
JpaSpecificationExecutor {
}
Этот интерфейс позволит вашему репозиторию использовать метод findAll(Specification, Pageable) .
После этого все достаточно просто. Вам нужно только создать замыкание Java для внедрения предикатов в динамический запрос jpa.
public Page<User> listUser( String name, int pageNumber, int pageSize ) {
Sort sort = Sort.by("id").ascending();
Pageable pageable = PageRequest.of(pageNumber, pageSize, sort);
return this.reservationRepository.findAll((root, query, builder) -> {
List<Predicate> predicates = new ArrayList<>();
if name!= null ) {
predicates.add(builder.equal(root.get("name"), name));
}
// More simple/complex conditions can be added to
// predicates collection if needed.
return builder.and(predicates.toArray(new Predicate[]{}));
}, pageable);
}
Как вы можете видеть здесь, это решение поддерживает сортировку/фильтрацию и разбиение на страницы на стороне базы данных.
В вашем запросе критериев я не вижу никакой сортировки, которую я написал бы следующим образом:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> iRoot = cq.from(User.class);
List<Predicate> predicates = new ArrayList<Predicate>();
if (StringUtils.isNotEmpty(username)) {
predicates.add(cb.like(cb.lower(iRoot.<String>get("username")), "%" + username.toLowerCase() + "%"));
}
Predicate[] predArray = new Predicate[predicates.size()];
predicates.toArray(predArray);
cq.where(predArray);
List<Order> orders = new ArrayList<Order>(2);
orders.add(cb.asc(iRoot.get("name")));
orders.add(cb.asc(iRoot.get("desc")));
cq.orderBy(orders);
TypedQuery<User> query = em.createQuery(cq);
Page<User> result = new PageImpl<User>(query.getResultList(), page, totalRows);
return result;
Я не проверял это, но это должно работать
Angelo
Полный пример, который используетJpaSpecificationExecutor
и инкапсулировать его в классе:
Сначала определите репозиторий как расширение JpaRepository и JpaSpecificationExecutor дляSomeEntity
:
interface SomeEntityRepositoryPaged extends JpaRepository<SomeEntity, Integer>, JpaSpecificationExecutor<SomeEntity> {
Page<SomeEntity> findAll(Specification<SomeEntity> spec, Pageable pageable);
}
Класс FindByCriteria:
class FindByCriteria{
FindByCriteria() {
ApplicationContext appCtx = ApplicationContextUtils.getApplicationContext();
repository = appCtx.getBean(SomeEntityRepositoryPaged.class);
}
private SomeEntityRepositoryPaged repository;
public Page<SomeEntity> searchBy(Client client, SearchParams params,Pageable page){
return repository.findAll(getCriteria(params), page);
}
private Specification<SomeEntity> getCriteria(SearchParams params){
return (paymentRoot, query, cb)->
{
List<Predicate> predicates = new ArrayList<>(5);
filterByParams(params, cb, paymentRoot, predicates);
query.where(predicates.toArray(new Predicate[] {}));
//IMPORTANT: the order specify should be deterministic, meaning rows can never change order in 2 subsequent calls.
List<Order> orders = new ArrayList<>(1);
orders.add(cb.asc(paymentRoot.get("date")));
query.orderBy(orders);
return null;
};
}
private void filterByParams(SearchParams params, CriteriaBuilder cb, Root<SomeEntity> payment,
List<Predicate> predicates) {
if (params.getInitialDate() != null)
predicates.add(cb.greaterThan(payment.get("paymentDate"), params.getInitialDate()));
if (params.getFinalDate() != null)
predicates.add(cb.lessThanOrEqualTo(payment.get("paymentDate"), params.getFinalDate()));
if (params.getState() != null)
predicates.add(cb.equal(payment.get("state"), params.getState()));
//... add here any criteria needed bases on params object
}
}
}
Чтобы вызвать простой вызов:
@Test pagedQueryTest
{
var page = PageRequest.of(2, 10);
var params = new SearchParams (...);
var list = new FindByCriteria().searchBy(params, page);
assert...
//The generated native query should be optimized.
//For mssql the native query should end with "offset ? rows fetch first ? rows only"
//and that means the store engine will only fetch the rows after first parameter (=page*size), and to the max of the page size.
}
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> iRoot = cq.from(User.class);
List<Predicate> predicates = new ArrayList<Predicate>();
if (StringUtils.isNotEmpty(username)) {
predicates.add(cb.like(cb.lower(iRoot.<String>get("username")), "%" +
username.toLowerCase() + "%"));
}
Predicate[] predArray = new Predicate[predicates.size()];
predicates.toArray(predArray);
cq.where(predArray);
cq.orderBy(cb.desc(user.get("name")));
TypedQuery<User> query = em.createQuery(cq);
Page<User> result = new PageImpl<User>(query.getResultList(), page, totalRows);
return result;