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.
Related
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]));
Having the following request class receiving :
public class WRequest{
Integer Id
String name;
List<Integer> RelatedIds;
}
I try to build a queryDsl predicate in order to pass it to spring data findAll() method:
BooleanBuilder builder = new BooleanBuilder();
Optional.ofNullable(wrequest.getId())
.map(QWRequest.wrequest.id::eq).ifPresent(builder::and);
Optional.ofNullable(wrequest.getName())
.map(QWRequest.wrequest.name::eq).ifPresent(builder::and);
The above snippet of code works only for String/Integer paramters, however I am unable to build the Predicate to include in the results any of the values in the RelatedIds List. How to create a QueryDSL predicate from an object that has a List field?
QWRequest class is the autogenerated QueryDSL query type from for the Wrequest class.
I have a repo with own Specification implementation with toPredicate method as main query construction and I try to add order by expression:
public Predicate toPredicate(#NotNull Root<Strategy> root,
#NotNull CriteriaQuery<?> query,
#NotNull CriteriaBuilder builder) {
Predicate predicate = builder.conjunction();
List<Expression<Boolean>> exps = predicate.getExpressions();
... adding different expressions to exps.add(...)
// I get an id for descending sort due to Postgres just increment it.
Order orderBy = builder.desc(root.get("id"));
Expression<?> expression = orderBy.getExpression();
// Problem here.
exps.add(expression);
return predicate;
}
Expression from orderBy.getExpression() is <?> generic but original expressions list expect <Boolean> type. How to connect them?
Specification is only intended for encoding where-clauses. If you want to order your result use a Sort instance as an additional parameter.
Sorting with pagination means sorting should be field of Pageable like this:
Pageable pagination = PageRequest.of(pageNumber, pageSize, Sort.by("id").descending());
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 + "%");
}
};
}
I've created a Repository that extends CrudRepository,
this repository has a method with an #Query notation:
Code:
#Query("select itemType, count(*) as count from Item where User_id = :userId group by itemType")
List<Map<String, Long>> countItemsForUser(#Param("userId") Long userId);
The issue I'm having is that this return a ArrayList of Object(s) and not a List of Map.
I've read somewhere that JPA can't return a Map so that's why I stuff the result in a List>.
I don't know what's the best way to work around this issue or to quickly access the result data.
I've tried casting but that didn't work out either:
for(Object item: items) {
Map<String,Long> castedItem = (HashMap<String,Long>)item;
}
See this example in official documentation of Hibernate.Here
for (Object item:items) {
Object[] tuple = (Object[]) item;
String itemType = (String)tuple[0];
Long count = (Long) tuple[1];
}
Most simple way is to use interface. To let Spring wire query alias
to the interface getter. Example can be found here: https://www.baeldung.com/jpa-queries-custom-result-with-aggregation-functions
also there is #SqlResultSetMapping. See:
JPA- Joining two tables in non-entity class