Order results based on count using spring boot specification API - spring

Consider the entity below.
PS: The model has more fields but for the question to be short I have posted only the relevant fields
Class Employee {
private String name;
private String country;
private String region;
private String department
#OneToMany
private Set<Skill> skills;
}
Class Skill {
private name;
}
I am using spring boot Specification API to filter employees on different fields like region, country, and so on.
public class EmployeeSpec implements Specification<Employee> {
#Override
public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
String fielName = //some field name
String fieldValue = //some field value
switch (fielName ) {
case "country":
return cb.equal(root.get("country"), fieldValue);
case "department":
return cb.equal(root.get("department"), fieldValue);
case "region":
return cb.equal(root.get("region"), fieldValue);
}
}
I want to order the results such that employee with maximum skills comes first. I am not sure how to implement this using Specification.

You can use CriteriaBuilder.size(..). For your case, the code will look like:
cq.orderBy(cb.desc(cb.size(root.get("skills"))));

Related

Spring JPA CriteriaQuery groupBy based on only one key in a Composite Primary Key (#EmbeddedId)

I am trying to write a criteriaQuery and group results based on emailId, which is one of the keys of a composite PK, embedded into one of my Entity classes.
The method that returns the specification is as follows :
public static Specification < User > getSpecification(Integer id) {
return (root, query, criteriaBuilder) - > {
var predicates = new ArrayList<Predicate()>;
predicates.add(criteriaBuilder.equal(root.get("indexId"), id));
query.groupBy(root.get("details").get("emailId"));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
The entity class:
public class User {
#EmbeddedId private Details details;
private String name;
private String status;
}
#Embeddable
public class Details {
private String emailId;
private Integer branchId;
}
I have skipped some annotations.
I want to group the results in a way where emailId remains unique, even if branchId changes. In essence for data where there are 3 rows of same emailId but different branchId, I should only fetch 1 result.
It seems it throws an error and asks for both components of the composite key to be passed in the query.groupBy statement.
If you could please help me figure out the issue.

How to Enable/Disable Entity Relations in Runtime?

I have a Spring Boot app that has basic CRUD services. For the read services I want to see their relations as well. There is no problem for implementing relations by #ManyToOne, #OneToOne, etc. annotations like this example.
My problem is I want to enable this relations based on a parameter in list service or I could use another endpoint as well. How can I achieve this? Any suggestions are welcome.
parameter version could be like ->
/employe/list?includeRelations=true
endpoint version could be like ->
/employee/list/byRelations
My entities are like;
#Entity
#Table(name = "employee")
public class Employee{
private long id;
private String name;
private Address address;
// getter setters
}
#Entity
#Table(name = "address")
public class Address {
private long id;
private String name;
private String postalCode;
// getter setters
}
EDIT
e.g.
without includeRelations=true '/employee/list' service should return this;
{
"id": 1,
"name": "Jane"
}
with includeRelations=true '/employee/list' service should return this;
{
"id": 1,
"name": "Jane"
"address": {
"id":1,
"name": "HOME",
"postalCode": "11111"
}
}
its some sudo code for your understanding . you can use Query Parameter and In Condition you call repo what you want :
for my scenario i want different response short, medium and long
#RequestMapping(value = "/getContacts", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, headers = "Accept=application/json")
public String getContact(#RequestBody ContactItemRequestInfo contactItemRequestInfo,
#RequestParam(required = false) String key,
String Contact)
{
if(key.equals("medium"))
{
return Contact="{\"responseCode\":\"02\",\"responseDescription\":\"Success\",\"totalCount\":2,\"contacts\":[{\"id\":114,\"firstName\":\"ali\",\"lastName\":\"kamran\"},{\"id\":115,\"firstName\":\"usman\",\"lastName\":\"khan\",\"middleName\":\"saad\"}]}";
}
else if(key.equals("long"))
{
return Contact="{\"responseCode\":\"03\",\"responseDescription\":\"Success\",\"totalCount\":2,\"contacts\":[{\"id\":114,\"firstName\":\"ali\",\"lastName\":\"kamran\"},{\"id\":115,\"firstName\":\"usman\",\"lastName\":\"khan\",\"middleName\":\"saad\"}]}";
}
else
{
return Contact="{\"responseCode\":\"00\",\"responseDescription\":\"Success\",\"totalCount\":2,\"contacts\":[{\"id\":114,\"firstName\":\"ali\",\"lastName\":\"kamran\"},{\"id\":115,\"firstName\":\"usman\",\"lastName\":\"khan\",\"middleName\":\"saad\"}]}";
}
}
It will be helpful for you !!
One of the ways would be to have different data transfer objects to return, depending on the REST request.
Let's assume you have the following classes, apart from entities.
class EmployeeDto {
private Long id;
private String name;
}
class EmployeeAddressDto {
private Long id;
private String name;
private AddressDto address;
}
class AddressDto {
private Long id;
private String name;
private int postalCode;
}
Then in a controller you would do something like this.
#GetMapping("/employee/list")
public ResponseEntity<?> getEmployees(#RequestParam int detailed) {
if (detailed) {
return employeeService.getDetailedEmployeeList();
} else {
return employeeService.getEmployeeList();
}
}
Service inteface would look like this.
interface EmployeService() {
List<EmployeeDto> getEmployeeList();
List<EmployeeAddressDto> getDetailedEmployeeList();
}
You would also need to handle entities to transfer objects conversions.
You can annotate the relations with fetchType=Lazy . Then invoke the getters to manually load the needed relations.
Another option is to eagerly load all relationships, but annotate the response with #JsonView and exclude the relations you don't need.

Spring boot + DocumentDB custom Query for searching documents

I have Spring Boot + DocumentDB application in which we need to implement a search API, The Search fields are part of nested Json are as below:
{
"id":"asdf123a",
"name":"XYZ",
add{
"city":"ABC"
"pin":123456,
}
}
I need to search with name="XYZ" and city="ABC", I'm trying with below code but somehow not able to retrieve the record.
But I'm able to retrieve with just by Name or ID, but not with name and city or just city which is part of nested JSON.
Employee{
private String id;
private String name;
private Address add
/*Getter and Setters {} */
}
Address{
private String city;
private Long pin;
/*Getter and Setters {} */
}
public class EmployeeController {
EmployeeRepository repository;
#Autowire
EmployeeController (EmployeeRepository repository){
this.repository = repository;
}
#GetMapping(value = "/search", produces = APPLICATION_JSON_VALUE_WITH_UTF8)
public ResponseEntity<?> Search (#RequestParam ("name")String name,
#RequestParam("city") String city){
return new ResponseEntity <> (repository
.findByNameLikeAndAddressCityLike(
name, city),
HttpStatus.OK
);
}
}
#Repository
public interface EmployeeRepository extends DocumentDbRepository<Employee,
String> {
Optional<Employee>findByNameLike(String name); // Perfectly working
Optional<Employee>findByAddressCityLike(String city); // Not working
Optional<Employee>findByNameLikeAndAddressCityLike(String name, String
city); // Not Working
}
Also Just like Spring JPA we use #Query to fire custom/ native query are there any DocumentDB Annotation present if so please guide me with example or Docuemnt. Looking for help

Spring data jpa Specification: How to filter a parent object by its children object property

My entity classes are following
#Entity
#table
public class User {
#OneToOne
private UserProfile userProfile;
// others
}
#Entity
#Table
public class UserProfile {
#OneToOne
private Country country;
}
#Entity
#Table
public class Country {
#OneToMany
private List<Region> regions;
}
Now I want to get all the user in a particular region. I know the sql but I want to do it by spring data jpa Specification. Following code should not work, because regions is a list and I am trying to match with a single value. How to fetch regions list and compare with single object?
public static Specification<User> userFilterByRegion(String region){
return new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.equal(root.get("userProfile").get("country").get("regions").get("name"), regionalEntity);
}
};
}
Edit: Thanks for the help. Actually I am looking for the equivalent criteria query for the following JPQL
SELECT u FROM User u JOIN FETCH u.userProfile.country.regions ur WHERE ur.name=:<region_name>
Try this. This should work
criteriaBuilder.isMember(regionalEntity, root.get("userProfile").get("country").get("regions"))
You can define the condition for equality by overriding Equals method(also Hashcode) in Region class
Snippet from my code
// string constants make maintenance easier if they are mentioned in several lines
private static final String CONST_CLIENT = "client";
private static final String CONST_CLIENT_TYPE = "clientType";
private static final String CONST_ID = "id";
private static final String CONST_POST_OFFICE = "postOffice";
private static final String CONST_INDEX = "index";
...
#Override
public Predicate toPredicate(Root<Claim> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<Predicate>();
// we get list of clients and compare client's type
predicates.add(cb.equal(root
.<Client>get(CONST_CLIENT)
.<ClientType>get(CONST_CLIENT_TYPE)
.<Long>get(CONST_ID), clientTypeId));
// Set<String> indexes = new HashSet<>();
predicates.add(root
.<PostOffice>get(CONST_POST_OFFICE)
.<String>get(CONST_INDEX).in(indexes));
// more predicates added
return return andTogether(predicates, cb);
}
private Predicate andTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.and(predicates.toArray(new Predicate[0]));
}
If you are sure, that you need only one predicate, usage of List may be an overkill.

Spring Specification Criteria Multiple Joins ? How?

I got stuck using a Spring Project with Spring Data + specification + criteria api.
I will try to simulate the situation with general entities we used write to get easy example.
The Entities:
Consider all attributes of the each entity is passed on the constructor showed below
Country(Long id, String name, String iso)
State(Long id, String name, String iso)
City(Long id, String name, String iso)
This is my repository:
public interface CityRepository extends PagingAndSortingRepository<City, Integer>, JpaSpecificationExecutor<City> {
}
As you can see, I don't need to implement anything on the repository
This is my service
#Service
#Transactional
public class CityService {
#Autowired
private CityRepository cityRepository;
#Transactional(readOnly = true)
public CityListVO findByNameLike(String name, PageRequest pageRequest) {
name = "%" + name + "%";
if (pageRequest == null) {
List<City> result = cityRepository.findAll(fillGridCriteria(name));
return new CityListVO(1, result.size(), result);
} else {
Page<City> result = cityRepository. findAll(fillGridCriteria(name), pageRequest);
return new CityListVO(result.getTotalPages(), result.getTotalElements(), result.getContent());
}
}
private static Specification<City> fillGridCriteria(String name) {
return new Specification<City>() {
#Override
public Predicate toPredicate(
Root<City> root,
CriteriaQuery<?> query,
CriteriaBuilder builder) {
/*
The current return I can do a like by name, and it works fine.
My problem is if for any reason I need to do multiple joins like the folow jpql:
select ci FROM City ci, State st, Country co where ci.st = st AND st.co = co AND co.name = 'Canada';
How to do this from here ? Inside this method.
How is gonna be the return for this method ?
*/
return builder.like(root.get("name"), name.trim());
}
};
}
}
Let's assume you want all the cities that their country's name like name and you have a relational Model in which :
Country(Long id, String name, String iso)
State(Long id,Long country, String name, String iso)
City(Long id, Long state, String name, String iso)
Predicate:
private static Specification<City> fillGridCriteria(String name) {
return new Specification<City>() {
#Override
public Predicate toPredicate(
Root<City> root,
CriteriaQuery<?> query,
CriteriaBuilder builder) {
return
builder.like(root.get("state").get("country").get("name"), name.trim());
}
};
}

Resources