Spring Data Rest - Sort by multiple properties - spring

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)));

Related

Spring And Kotlin Query

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.

Using Spring MongoTemplate to update nested arrays in MongoDB

Can anyone help with a MongoTemplate question?
I have got a record structure which has nested arrays and I want to update a specific entry in a 2nd level array. I can find the appropriate entry easier enough by the Set path needs the indexes of both array entries & the '$' only refers to the leaf item. For example if I had an array of teams which contained an array of player I need to generate an update path like :
val query = Query(Criteria.where( "teams.players.playerId").`is`(playerId))
val update = Update()
with(update) {
set("teams.$.players.$.name", player.name)
This fails as the '$' can only be used once to refer to the index in the players array, I need a way to generate the equivalent '$' for the index in the teams array.
I am thinking that I need to use a separate Aggregate query using the something like this but I can't get it to work.
project().and(ArrayOperators.arrayOf( "markets").indexOf("")).`as`("index")
Any ideas for this Mongo newbie?
For others who is facing similar issue, One option is to use arrayFilters in UpdateOptions. But looks like mongotemplate in spring does not yet support the use of UpdateOptions directly. Hence, what can be done is:
Sample for document which contain object with arrays of arrayObj (which contain another arrays of arrayObj).
Bson filter = eq("arrayObj.arrayObj.id", "12345");
UpdateResult result = mongoTemplate.getDb().getCollection(collectionName)
.updateOne(filter,
new Document("$set", new Document("arrayObj.$[].arrayObj.$[x].someField"), "someValueToUpdate"),
new UpdateOptions().arrayFilters(
Arrays.asList(Filters.eq("x.id, "12345))
));

Sorting by case using Hibernate

We have a typeahead that allows our customers to do a global search of their clients. Based on a 'filterText', we want to retrieve all the clients where any of the following fields contain the filterText: clientName, clientStreet, clientCity... but now there's a requirement and we want to prioritize the results where the clientName contains the filterText. (They should be shown first)
We currently create a customerSpecification, and the Predicate that it's being used is the following one:
return criteriaBuilder.or(
criteriaBuilder.like(customerName, likeFilter),
criteriaBuilder.like(customerStreet, likeFilter),
criteriaBuilder.like(customerCity, likeFilter),
criteriaBuilder.like(customerState, likeFilter),
criteriaBuilder.like(customerZip, likeFilter),
criteriaBuilder.like(customerCountry, likeFilter),
tempPred
);
and then we use it to get the Page with all the results
customerRepository.findAll(customerSpecification, pageable);
How can we introduce this new requirement? Any approaches?
Some pages suggest to use selectCase, doing something like this:
Expression<Object> caseExpression = criteriaBuilder.selectCase()
.when(criteriaBuilder.like(root.get(CustomerEntity_.name), likeFilter), 1)
.otherwise(2);
Order order = criteriaBuilder.desc(caseExpression);
criteriaQuery.orderBy(order);
But I can't find the way to make it works. I also found some talks about PageRequest, which receives a Sort parameter, but I didn't find how it would help, as this object is too simple for what we're looking for.
Thanks

Spring Data Mongodb - findBy query with OR clause on same field

I'm working on mongo with spring data and using the query "findBy" based on property fields.
My goal will be find same value across multiple properties by OR clause like this:
List<Event> findByCreatorOrOrganizersOrGuests(User user);
this query involves 3 properties: "creator", "organizers" and "guests" that are a single value (creator) and 2 lists (organizers and guests) that contains the same class type (User)
at compile time I receive this error:
org.springframework.data.repository.query.ParameterOutOfBoundsException: Invalid parameter index! You seem to have declare too little query method parameters!
Do I need to repeat the same parameter 3 times? There's no workaround?
thanks
The using of Mongo queries for lists are not so efficient even if you define it with an index.
Another important thing is that the query:
List<Event> findByCreatorOrOrganizersOrGuests(User user);
includes some syntax errors (The MongoDB expects to get 3 parameters as variables int the ( ) relatively to the findBy keys, the return value should be inserted into a list object, and I am not sure that the Or in the find query is legal syntax.
As a concept the DB documents should have a good ability to be searched, cause it is the most expensive action.
When find using OR in MongoDB the direct query should looks like that:
def events = find({ $or: [ { user.username: { $lt: 20 } }, { price: 10 } ] } )
While searching in a list this is should be different, so please follow the links I attached.
When I try to think of the use you are looking for, I would suggest a different aspect. If the events is what interest you please read about the AbstractPersistenceEventListener. By define the appropriate
void onPostInsert(entity) {}
void onPostUpdate(entity) {}
void onPostDelete(entity) {}
You get all the events for each of the objects you want to listen to.
Here are 2 a great examples:
Example 1
Example 2

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.

Resources