Spring And Kotlin Query - spring-boot

How can I achieve this query
select *
from table t
where name like '%Ami%'
order by (name = 'Ami') desc, length(col);
(just the sort part)
Using springframework Sort..
What I tried is
Sort.by(Sort.Direction.DESC, "name") // But I need to sort by name = 'Ami'
Sort.by(Sort.Direction.DESC, "name" = 'Ami'") // throws an error
JpaSort.unsafe(Sort.Direction.DESC, "name" = 'Ami'") // throws an error

Looks like the documentation has an example almost identical to your question:
https://docs.spring.io/spring-data/jpa/docs/2.4.5/reference/html/#jpa.query-methods.sorting
However, using Sort together with #Query lets you sneak in
non-path-checked Order instances containing functions within the ORDER
BY clause. This is possible because the Order is appended to the given
query string. By default, Spring Data JPA rejects any Order instance
containing function calls, but you can use JpaSort.unsafe to add
potentially unsafe ordering.

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

Mapping many-to-many IN statement into JPA (Spring Boot)

I have created two entities in JPA, Listing and ItemType - these exist in a many-to-many relationship (Hibernate auto-generates a junction table). I'm trying to find the best way to create a query which accepts a dynamic list of item type Strings and returns the IDs of all listings which match the specified item types, but I am a recent initiate in JPA.
At present I'm using JpaRepository to create relatively simple queries. I've been trying to do this using CriteriaQuery but some close-but-not-quite answers I've read elsewhere seem to suggest that because this is in Spring, this may not be the best approach and that I should be handling this using the JpaRepository implementation itself. Does that seem reasonable?
I have a query which doesn't feel a million miles away (based on Baeldung's example and my reading on WikiBooks) but for starters I'm getting a Raw Type warning on the Join, not to mention that I'm unsure if this will run and I'm sure there's a better way of going about this.
public List<ListingDTO> getListingsByItemType(List<String> itemTypes) {
List<ListingDTO> listings = new ArrayList<>();
CriteriaQuery<Listing> criteriaQuery = criteriaBuilder.createQuery(Listing.class);
Root<Listing> listing = criteriaQuery.from(Listing.class);
//Here Be Warnings. This should be Join<?,?> but what goes in the diamond?
Join itemtype = listing.join("itemtype", JoinType.LEFT);
In<String> inClause = criteriaBuilder.in(itemtype.get("name"));
for (String itemType : itemTypes) {
inClause.value(itemType);
}
criteriaQuery.select(listing).where(inClause);
TypedQuery<Listing> query = entityManager.createQuery(criteriaQuery);
List<Listing> results = query.getResultList();
for (Listing result : results) {
listings.add(convertListingToDto(result));
}
return listings;
}
I'm trying to understand how best to pass in a dynamic list of names (the field in ItemType) and return a list of unique ids (the PK in Listing) where there is a row which matches in the junction table. Please let me know if I can provide any further information or assistance - I've gotten the sense that JPA and its handling of dynamic queries like this is part of its bread and butter!
The criteria API is useful when you need to dynamically create a query based on various... criteria.
All you need here is a static JPQL query:
select distinct listing from Listing listing
join listing.itemTypes itemType
where itemType.name in :itemTypes
Since you're using Spring-data-jpa, you just need to define a method and annotate it with #Query in your repository interface:
#Query("<the above query>")
List<Listing> findByItemTypes(List<String> itemTypes)

Spring Data Rest - Sort by multiple properties

I have an entity as below
Class Person{
String id;
String name;
String numberOfHands;
}
With Spring Data Rest (Gosling Release Train), I'm able to specify
localhost/Person?sort=name,asc
for sorting name name ascending. Now, in a case where I need to sort by numberOfHands descending and name ascending. I'm able to specify
localhost/Person?sort=numberOfHands,name,asc
But, I'm not able to specify
localhost/Person?sort=numberOfHands,desc,name,asc
Is there a way to specify multiple sort order?
Thanks!
Solution (tl;dr)
When wanting to sort on multiple fields you simply put the sort parameter multiple times in the URI. For example your/uri?sort=name,asc&sort=numberOfHands,desc. Spring Data is then capable of constructing a Pageable object with multiple sorts.
Explanation
There is not really a defined standard on how to submit multiple values for a parameter in a URI. See Correct way to pass multiple values for same parameter name in GET request.
However there is some information in the Java Servlet Spec which hints on how Java servlet containers parse request parameters.
The getParameterValues method returns an array of String objects containing all the parameter values associated with a parameter name. ... - Java Servlet Spec, section 3.1
The sample further in that section states (although it mixes request and body data)
For example, if a request is made with a query string of a=hello and a post body of a=goodbye&a=world, the resulting parameter set would be ordered a=hello, goodbye, world.
This sample shows that when a parameter (a in the example) is presented multiple times the results will be aggregated into a String[].
Here is how to construct the multi Sort object manually/programatically.
Sort sort = Sort.by(
Sort.Order.asc("name"),
Sort.Order.desc("numberOfHands"));
return personRepository.findAll(sort);
Note: This solution does not directly solve the original question asked, but may help visitors that landed on this question while searching for a solution how to sort on multiple properties from a backend perspective / in a somewhat "hardcoded" way. (this solution does not require/take any URI parameters)
When dynamic fields are there then you simply do match with fields and add in sorting list like.
List<Sort.Order> sorts= new ArrayList<>();
if (sort == "name" && sortingOrder.equalsIgnoreCase("DESC")) {
sorts.add(new Sort.Order(Sort.Direction.DESC,"name"));
} else if (sort == "numberOfHands" && sortingOrder.equalsIgnoreCase("DESC")) {
sorts.add(new Sort.Order(Sort.Direction.DESC,"numberOfHands"));
}
return personRepository.findAll(Sort.by(sorts));
If you are using Pagination then directly add in PageRequest Request.
return personRepository.findPersons(PageRequest.of(pageNo, pageSize, Sort.by(sorts)));

Converting Object to Class object

in my Spring MVC project i m using Hibernate, by using Criteria API i am applying Group BY and Order BY clause. Query get executed on DB successfully and it brings result also but its an array of Object--
Here is code of Criteria API
Criteria criteria = session.createCriteria(DashboardSubindicatorSubmission.class, "DashboardSubindicatorSubmission")
.setProjection(Projections.projectionList()
.add(Projections.sum("InputValue").as("InputValue"))
.add(Projections.groupProperty("fkAccademicYearId"))
.add(Projections.groupProperty("fkAssessmentPlanID"))
.add(Projections.groupProperty("fkSubindicatorID"))
.add(Projections.groupProperty("InputTitle")))
.addOrder(Order.asc("fkAccademicYearId"))
.addOrder(Order.asc("fkAssessmentPlanID"))
.addOrder(Order.asc("InputTitle"));
List<DashboardSubindicatorSubmission> dashboardSubindicatorSubmissionList = (List<DashboardSubindicatorSubmission>)criteria.list();
session.flush();
transaction.commit();
return dashboardSubindicatorSubmissionList;
I am casting criteria.list() to List<DashboardSubindicatorSubmission> but when i try to do dashboardSubindicatorSubmissionList.get(i) on controller it gives me exception java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to mkcl.accreditation.model.DashboardSubindicatorSubmission.
i come to know that, though i m casting it to List<DashboardSubindicatorSubmission> still its an list of object[] thats why i cant do dashboardSubindicatorSubmissionList.get(i) because it returns me object of DashboardSubindicatorSubmission. (Correct me if i am wrong)
So how can i convert my result into list of DashboardSubindicatorSubmission class?
Does setResultTransformer() helps me in this case?
You have two options. When you use projections, Hibernate doesn't know how to respect each field because it uses the name of each field to build objects and he doesn't know the names yet.
Thus, your first option is to name the fields grouped so that they match the names of object properties. This is necessary even if the string you use in projection is already the name of the object field. Something like:
.add(Projections.groupProperty("fkAccademicYearId"), "fkAccademicYearId") // same value
.add(Projections.groupProperty("fkAssessmentPlanID"), "other") // other value
The second option is to do what you yourself suggested, create your own implementation of ResultTransformer. I reckon this a interesting option if you want to extract other object of this query, as when you make a report.

Specifying a list of fields in HQL doesn't seem to work

I have the following HQL in Hibernate using Spring MVC.
List<Colour>list=session.createQuery("from Colour order by colourId desc")
.setFirstResult((currentPage-1)*rowsPerPage)
.setMaxResults(rowsPerPage).list();
It works and returns a list of rows from the colour table (actually operates upon the Colour entity (POJO) that I can understand) in Oracle 10g.
What if I need to retrieve a list fields, I'm trying the following.
List<Colour>list=session.createQuery("colourId, colourName, colourHex from Colour order by colourId desc")
.setFirstResult((currentPage-1)*rowsPerPage)
.setMaxResults(rowsPerPage).list();
It ends with an excpetion
java.lang.IllegalArgumentException: node to traverse cannot be null!
In some articles, it was mentioned that the following version of HQL should (or may) work
List<Colour>list=session.createQuery("select colourId, colourName, colourHex from Colour order by colourId desc")
.setFirstResult((currentPage-1)*rowsPerPage)
.setMaxResults(rowsPerPage).list();
but unfortunately, it also didn't work for me. Using the createSQLQuery() method to execute native SQL would work but I want to stick to the createQuery() method with HQL unless it's absolutely necessary. How can I specify a list of fields in HQL?
I agree with yorkw's comment. If you select properties in your query then you cannot ask for a List<Colour> object to be returned from a call to .list().
Instead you should do this
List<Object[]> rows = session.createQuery("select c.colourId, c.colourName, c.colourHex " +
" from Colour c " +
" order by c.colourId desc").list();
Then iterate over the list object and instantiate your objects. Or whatever you need to do.
for ( Object[] row : rows ) {
Long colourId = (Long)row[0];
// ... etc
}
Why don't you try creating a map? Something like this:
SELECT NEW MAP( colour.colourId AS id
, colour.colourName AS name ...)
FROM Colour colour
ORDER BY colour.colourId
I use the alias for Colour "colour" so hibernate knows from which entity is the property I am referencing, I am implying all those properties are from the same entity, if not, then check your referencing!

Resources