Spring Data JPA Use SpEL syntax to conditionally build query - spring

I tried to build a query depending on a boolean #Param, and the difficulty is that I build the end of the query without using a classical operator ( = , <, > ...).
For the example, let's assume I want to fetch all my Sales objects that are not related to an Account object (if I pass false in #Param) or that are related to an Account (if I pass true in the #Param) :
#Query("SELECT sale ....
WHERE sale.account :#{isbound ? NOT NULL : IS NULL}")
public List<Sale> getSales(#Param("isbound") boolean isBound);
I tried a few syntaxs based on the official Spring documentation (https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions), but all their examples are working with an operator before the expression, like this: entity = #{the_expression}.
Does somebody once tried this and can give me the good way to write this ? thx !

Refactor your query code like bellow:
#Query("SELECT sale ....
WHERE (true = :isbound and sale.account is not null)
or (false = :isbound and sale.account is null)")
public List<Sale> getSales(#Param("isbound") boolean isBound);

Related

Spring Data JPA #Query with Specification

I have a requirement to create a REST api. Api allows user to provide dynamic search criteria in URL. For example, let say I have NOTES table with column as Note_ID, NOTE_TEXT, STATUS, PERSON_ID. This table is used to keep notes of every person.
Now I want my REST api to be as https://server:host/MyApi/Notes?search=NoteText=='My Java adventure'. API should provide all notes having NOTE_TEXT as 'My Java adventure'. Similarly user can provide status also in url and also he can use operators as LIKE. I was able to do it via rsql parser as mentioned in https://www.baeldung.com/rest-api-search-language-rsql-fiql
Now I have additional requirement that based on user security person_id filter should be applied on query automatically.
I found that we can't have findBy method which can take Specification, Pageable and extra personId. For example I can't have a repository function as
findByPersonId(Specification spec, Pageable page, Long personId);
I thought of using SpEL to use it, but then I found that if we use #Query annotation on findBy method, Specifications are ignored.
Seems like there is no way I can have Specification and #Query both. I need to add more clauses using specification only. In reality my where clause is very complex which I have to append and getting it with Specification seems to be difficult. Its something like
Select * from NOTES where exists (select 'x' from ABC a where n.person_id = a.person_id)
Is there a way I can write #Query and also have Specification working on top of it?
Ideally I have achieve a query like
select * from test.SCH_FORUM_THREAD t
where exists (select 'x' from test.FORUM_THREAD_ACCESS fta, school.SCH_GROUP_PERSON gp
where gp.GROUP_ID = fta.GROUP_ID
and t.THREAD_ID = fta.THREAD_ID
and gp.PERSON_ID = :personId)
or exists (select 'x' from test.FORUM_THREAD_ACCESS fta
where fta.THREAD_ID = t.THREAD_ID
and fta.PERSON_ID = :personId);
So there are two exists clauses with or condition. I was able to make second exists by following How to write query(include subquery and exists) using JPA Criteria Builder
Now struggling with first exists as it has join also. Any idea how to do that with Specification.
Also as there are two exists, does that mean I need two specifications. Can I achieve it in one specification.
I was able to resolve it by creating a complex specification code. Something like
#Override
public Predicate toPredicate(Root<ForumThread> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Subquery<ForumThread> subQuery = query.subquery(ForumThread.class);
Root<ForumThread> subRoot = subQuery.from(ForumThread.class);
Join<ForumThreadAccess, GroupPerson> fragpjoin = subRoot.join("groupPersons");
Predicate threadPredicate = builder.equal(root.get("threadId"), subRoot.get("threadId"));
Predicate personPredicate = builder.equal(fragpjoin.get("personId"), personId);
subQuery.select(subRoot).where(threadPredicate, personPredicate);
Predicate existsGroupPredicate = builder.exists(subQuery);
Subquery<ForumThreadAccess> subQuery1 = query.subquery(ForumThreadAccess.class);
Root<ForumThreadAccess> subRoot1 = subQuery1.from(ForumThreadAccess.class);
Predicate threadPredicate1 = builder.equal(root.get("threadId"), subRoot1.get("threadId"));
Predicate personPredicate1 = builder.equal(subRoot1.get("personId"), personId);
subQuery1.select(subRoot1).where(threadPredicate1, personPredicate1);
Predicate existsPersonPredicate = builder.exists(subQuery1);
return builder.or(existsGroupPredicate,existsPersonPredicate);
}
To make it work your entities should also have proper #OneToMany and #ManyToMany in place.
Thanks

How to query upon the given parameters in Mongo repository

I have a mongo repostiory query as below, If we provide both name and price it gives the response. I want to get response if we only give name or price or both. how to make those parameters optional. if we provide both name and price i want to retrieve aggregated result unless i want to search just from the given field. Much appreciate you help.
List<Response> findByNameAndPrice(String name, int price)
Either you may need to implement custom JPA query or need to use QueryDSL in such scenarios.
1) Custom JPA Query like, you may need to change the query if you want to ad new optional parameters.
#Query(value = "{$or:[{name: ?0}, {?0: null}], $or:[{price: ?1}, {?1: null}]}")
List<Response> findByNameAndPrice(String name, Integer price)
2) QueryDSL Approach where you can add as many optional parameters, no need to modify your code. It will generate query automatically.
Refer this link for more : Spring Data MongoDB Repository - JPA Specifications like
I don't believe you'll be able to do that with the method name approach to query definition. From the documentation (reference):
There is a JIRA ticket regarding this which is still under investigation by the Spring team.
You can try this way
In repository
List<Response> findByName(String name)
List<Response> findByPrice(int price)
List<Response> findByNameAndPrice(String name, int price)
In your service file
public List<Response> findByNameAndPrice(String name, int price){
if(name == null ){
return repository.findByName(name);
}
if( price == 0){
return repository.findByPrice(price);
}
return repository.findByNameAndPrice(name, price);
}
Here I found a simple solution using mongodb regex,we can write a query like this,
#Query("{name : {$regex : ?0}, type : {$regex : ?1})List<String> findbyNameAndType(String name, String type)
The trick is if we want to query upon the given parameter, we should set other parameters some default values. As an example here thinks we only provide name. Then we set the type to select its all possible matches by setting its default param as ".*".
This regex pattern gives any string match.

how to use SpringData findAll() between select when using an object as major condition?

So there's a method in SpringData findAll(Pageable pageable,Condition condition) ;,
usually I use it like findAll(pageable,myobject) .
The question is when it comes to select some records between some certain field range ,like select out objects whose createDate are between A and B , how to use findAll()?
I tried findAllByCreateDateAfterAndCreateDateBefore(Pageable pageable,Date a,Date b);
But here I can't put myObj as a condtion into the method , and it caused a lot trouble when some fields in the myObj are not sure if it would be used as a condition.
You can simply use JPA Specification to do this, then :
fun findAll(spec: Specification<YourObject>, pageable: Pageable): Page<YourObject>
Here simple example how it should be use :
JPA Specification example

Getting dynamic property predicate by property name with queryDSL

I using Query DSL generated entity EntitySerializer in order to query JPA entities using QueryDSL (integrated with SpringData).
I’m receiving from the client property names and I want to create predicates for the properties that can be added (AND) to additional predicats.
I don’t know how to get from the EntitySerializer the predicate/Path that matches my property name. For example, let’s say we have a Person entity (with auto generated QPerson class) with a “name” property that I want to filter on (at the end I want to create a generic method). Here is the generic method:
Public Predicat getPredicatByPropertyName(String propertyName) {
QPerson p = QPerson.person;
person.getPredicat(“propertyName”).like(“tom”);
}
To create a String typed property just use the following snippet
new StringPath(p, propertyName)
which can then be used like this to create a Predicate instance
new StringPath(p, propertyName).like("tom")
I did it slightly different since as Timo said didn't work straightforward, here is it:
query.from(play);
query.where( Expressions.stringPath(play, "name").eq("New play") );
I know it could also be achieved by doing it separately:
StringPath column = Expressions.stringPath(play, "name");
query.from(play);
query.where( column.eq("New play") );
As of QueryDSL 5.0, I found two ways to do this independent of the column class:
First way: using just reflection:
Field pathField = p.getClass().getField(reportField.getFieldName());
ComparableExpressionBase<?> path = (ComparableExpressionBase<?>)
pathField.get(p);
Note: I use the ComparableExpressionBase class because it is extendend by all "path classes" that I found and it also can be used in select, orderBy and other functions to build the query
Second way: using reflection and ExpressionUtils:
Class<?> pParameterClass = (Class<?>) ((ParameterizedType) p.getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
Class<?> pathClass = pParameterClass.getDeclaredField(pathName).getDeclaringClass();
Path<?> path = ExpressionUtils.path(pathClass, p, pathName);
Note: using this way we first get the class of the entity of the table see this answer for an explanation on how to get a parametrized type. Next, we get the class of the path and finally we get the path with the ExpressionUtils.path method.

NHibernate.Linq And CompareTo String

I'm using Linq and Hibernate and trying to compare to Strings one from a variable and the other from a Class Linked to Hibernate, the code:
bindingSource.DataSource = (from search in Repository.GetAll()
where search.cod_coluna.CompareTo(CurrentRecord.cod_coluna) > 0
orderby search.cod_coluna select search).Take(1);
And i get an Exception in Runtime, QueryException: Cannot use subqueries on a criteria without a projection.
What i do now?
I think the problem is that CompareTo isn't something that can be mapped to SQL.
What type is cod_coluna? You may be able to use == or != if you don't really need CompareTo.

Resources