I have a predicate but it doesn't work with if I try to compare with more than one element! getStatus returns an enum list, I don't understand how I can return multiple elements from the list if there is more than one value in the request, I think my predicate is written incorrectly.
List<Predicate> predicates = new ArrayList<>();
if (!CollectionUtils.isEmpty(request.getStatus())) {
predicates.add(criteriaBuilder.equal(root.get("status"), request.getStatus()));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
I have a requirement for building a dynamic query based on filter. I have an Array which contain the filter criteria based on the entries in the Array I am building the Predicate. But with my current implementation its like the spec is not getting passed to the findall query. The response I am getting has all the data. Could someone correct me if something is missing or wrong.
My Repository interface
#Repository
public interface Company extends JpaRepository<Comp, Integer>{
List<Comp> findAll(Specification spec);
}
My Spec builder
private Specification specificationOnFilter(List<Filter> filters) {
new Specification<Comp>() {
#Override
Predicate toPredicate(Root<Comp> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
// Building predicate, adding into the predicates object
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
}
My final call is like below
MyRepository.findAll(specificationOnFilter(filter));
This line of code:
List<Predicate> predicates = new ArrayList<>(); // Building predicate, adding into the predicates object
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
Seem's to just generate predicate from empty ArrayList of predicate's. Therefore your final predicate will pass all the data since it doesn't contain it was builded from zero predicates.
predicates.toArray(new Predicate[predicates.size()]) // will be empty in your case.
You should involve List<Filter> filter from argument of your method and build predicates List<Predicate> from it instead of just creating new ArrayList<>() which is empty.
Also in your question you don't provide any information on what libraries you are using. What are Filter and Specification and Scope classes (this comes from spring cloud i guess). Therefore it's hard to reproduce issue and you will be provided with abstract answer's.
I am using JpaRepository Pageable query for pagination. All the things are working fine except the sort field case sensitive issue. Following query is used for getting list.
Pageable pageable = null;
if (paginationRequestDTO.getSortOrder().equalsIgnoreCase("desc"))
pageable = new PageRequest(page, size, Sort.Direction.DESC, sortfiled);
else
pageable = new PageRequest(page, size, Sort.Direction.ASC, sortfiled);
Page<Audi> audiPage = null;
audiencePage = audiRepository.search(paginationRequestDTO.getSearchKey(), pageable);
Audi table values are: apple,az,Ajay,Bala.
when i search with sortorder of asc and sort field name,
original output : Ajay,Bala,apple,az.
Expected output: Ajay,apple,az,Bala.
I am using mysql database. table engine - Innodb,characterst-utf8,collate-utf8_bin.
Please note that its not duplicate question.i didn't get exact answer for this question.thanks in advance.
Edited: as harsh pointed out correctly, this needs to be solved on database level, using correct collation. This is important, because you probably want to have an index on the sort column for best performance.
But there are other use cases, which could combine filtering together with sorting by something other, than a pure column value, e.g. by length of description, sum or average of a column, etc. For that reason, I am including a JPA solution:
I struggled with this recently and I am afraid, that the Pageable interface does not support this out of box.
The solution was to use EntityManager, CriteriaBuilder, CriteriaQuery, Specification and implement the paging manually. You can find the solution here.
You need to construct the Page object manually:
public Page<Audi> getPage(int pageNumber, int pageSize, String descriptionFilter, Sorting sorting) {
return new PageImpl<>(
getPageItems(pageNumber, pageSize, descriptionFilter, sorting),
PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.ASC, sorting.name())),
getTotalCount(descriptionFilter)
);
}
getPageItems selects the page using LIMIT and OFFSET
private List<Audi> getPageItems(int pageNumber, int pageSize, String descriptionFilter, Sorting sorting) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Audi> query = cb.createQuery(Audi.class);
Root<Audi> root = query.from(Audi.class);
query.where(createSpecification(descriptionFilter).toPredicate(root, query, cb));
if (sorting.equals(Sorting.descriptionCaseInsensitive)) {
query.orderBy(cb.asc(cb.lower(root.get("description"))));
} else {
throw new UnsupportedOperationException("Unsupported sorting: " + sorting.name());
}
query.select(root);
return em.createQuery(query)
.setFirstResult(pageNumber * pageSize)
.setMaxResults(pageSize)
.getResultList();
}
getTotalCount selects count(distinct(*)),
private long getTotalCount(String descriptionFilter) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Long> query = cb.createQuery(Long.class);
Root<Audi> root = query.from(Audi.class);
query.where(createSpecification(descriptionFilter).toPredicate(root, query, cb));
query.select(cb.countDistinct(root));
// getSingleResult can return null, if no rows fulfill the predicate
return Optional.ofNullable(em.createQuery(query).getSingleResult()).orElse(0L);
}
Both reuse the same predicate, which filters rows:
private Specification<Audi> createSpecification(String descriptionFilter) {
return Specification.where(
(root, query, criteriaBuilder) ->
criteriaBuilder.like(criteriaBuilder.lower(root.get("description")), "%" + descriptionFilter.toLowerCase() + "%")
);
}
There're two numeric columns in database like actual and plan.
I can add third column like difference and write WHERE part of query filtering by this column.
But instead having additional column in database, I want to calculate it in Predicate.
Something like (qProduct.actualPrice - qProduct.planPrice).gt(20L)
Entity:
public class Product {
private String type;
private Long actualPrice;
private Long planPrice;
}
Predicate:
QProduct qProduct = QProduct.product;
BooleanExpression predicate = qProduct.type.eq("pizza")
.and((qProduct.actualPrice - qProduct.planPrice).gt(20L));
Page<Product> productPage = productRepository.findAll(predicate, pageable);
Thx
You can use the subtract method of NumberExpression class
QProduct qProduct = QProduct.product;
BooleanExpression predicate = qProduct.type.eq("pizza")
.and((qProduct.actualPrice.subtract(qProduct.planPrice)).gt(20L));
Page<Product> productPage = productRepository.findAll(predicate, pageable);
I'm using Eclipselink and have a tricky problem regarding JPA NamedQueries.
My database table contains a column which is from type VARCHAR and stores a comma separated list of keywords as one String.
How can I create a NamedQuery in JPA to search theese keywords?
I'd like to give a list of Strings as a parameter and as a result I'd like to have a list of objects which keyword list contain one of the Strings from the parameter list.
Maybe like the following:
List<String> keywordList = new ArrayList<String>();
keywordList.add("test");
keywordList.add("car");
List<Object> result = em.createNamedQuery("findObjectByKeywords", Object.class)
.setParameter("keywords", keywordList)
.getResultList();
Unfortunately I'm not such a big database/SQL expert. Maybe someone of you can help me?
I hope you understand my problem.
Edit:
I am developing on Weblogic 10.3.6, which means I am not able to use JPA 2.0 features.
Edit2:
I managed to activate JPA 2.0 in my Weblogic Server with the help of Oracle Enterprise Pack for Eclipse. Problem solved, I think.
VALID FOR JPA2.0
As Bhesh commented a simple JPQL won't make it. The resulting SQL has to contain a where clause similar to following:
where keywords like '%keyword1%' or keywords like '%keyword2%' or ... or keywords like '%keywordN%'
This means: We need a loop here!
You could try to build a JPQL by yourself like Bhesh suggested in his first comment, though as he also stated it is not a brilliant idea. But don't worry - JPA provides also a Criteria API which comes handy in such situations. So, although you're not going to have a named query, you can still make it with JPA this way:
public List<YourEntity> findAllByKeywords(List<String> keywords){
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<YourEntity> query = builder.createQuery(YourEntity.class);
Root<YourEntity> root = query.from(YourEntity.class);
List<Predicate> predicates = new LinkedList<>();
for (String keyword : keywords) {
predicates.add(builder.like(root.<String>get("keywords"), "%" + keyword + "%"));
}
return entityManager.createQuery(
query.select(root).where(
builder.or(
predicates.toArray(new Predicate[predicates.size()])
)
))
.getResultList();
}
or (always slightly better with Guava)
public List<YourEntity> findAllByKeywords(List<String> keywords){
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<YourEntity> query = builder.createQuery(YourEntity.class);
final Root<YourEntity> root = query.from(YourEntity.class);
return entityManager.createQuery(
query.select(root).where(
builder.or(
transform(keywords, toPredicateFunction(builder, root)).toArray(new Predicate[]{})
)
))
.getResultList();
}
private Function<String, Predicate> toPredicateFunction(final CriteriaBuilder builder, final Root<YourEntity> root) {
return new Function<String, Predicate>() {
#Override
public Predicate apply(String input) {
return builder.like(root.<String>get("keywords"), "%" + input + "%");
}
};
}