Spring Boot Rest Web Service fetching multiple parameters in Get Request - java

I am creating Spring Boot Web service and I have a Model Employee
public class Employee {
private String id;
private String name;
private String designation;
private int salary;
//Has Getters and Setters
}
I want to create a Get request which will fetching and filter the List of Employees based on the parameters given by user.
For example, if the user gives name of an employee and designation of employee, the get method should filter those result. For various combination of parameters it should work.
#Override
public List<Employee> getEmployees(Map<String, Object> parameters) {
if (parameters.size() == 0)
// code to return all employees;
List<Employee> selectedEmployees = new ArrayList<Employee>();
for(Employee currentEmployee: new ArrayList<Employee>(employee.values())) {
for(Map.Entry<String, Object> check: parameters.entrySet()) {
try {
if(check.getValue() instanceof Integer) {
int condition = (int) Employee.class.getMethod("get" + check.getKey()).invoke(currentEmployee);
if((int) check.getValue() == condition)
selectedEmployees.add(currentEmployee);
} else if (check.getValue() instanceof String) {
String condition = (String) Employee.class.getMethod("get" + check.getKey()).invoke(currentEmployee);
if (((String) check.getValue()).equals(condition))
selectedEmployees.add(currentEmployee);
}
} catch(Exception e){
e.printStackTrace();
}
}
}
return selectedEmployees;
}
In order to avoid multiple if else cases I am filtering list based on String and Integer above.
I think I am making an error in the below code which sending request in Controller.
#RequestMapping(value={"/employees","/{id}/{name}/{designation}/{salary}"})
public List<Employee> getEmployeeByProperty(EmployeeRequestParameters requestParams){
//Map for storing parameters to filter the List
Map<String, Object> filterParams = new HashMap<>();
if(requestParams.getIdParam().isEmpty()) {
filterParams.put("id", Integer.parseInt(requestParams.getIdParam()));
}
if(!requestParams.getNameParam().isEmpty()) {
filterParams.put("name", requestParams.getNameParam());
}
if(!requestParams.getDesignationParam().isEmpty()) {
filterParams.put("designation", requestParams.getDesignationParam());
}
if(requestParams.getSalaryParam().isEmpty()) {
filterParams.put("salary", Integer.parseInt(requestParams.getSalaryParam()));
}
return EmployeeService.getEmployeesByProperty(filterParams);
}

If {id} field is not full, {name} or {designation} or {salary} to be null.For {name} or {designation} or {salary} to be full Because should be {id} full.
#GetMapping("/employees")
public List<Employee> getEmployeeByProperty(#RequestParam(value = "id", required=false) String id,
#RequestParam(value = "name", required=false) String name,
#RequestParam(value = "designation", required=false) String designation,
#RequestParam(value = "salary", required=false) int salary) {
//Your codes
}
Even if {id} is empty, you can use others.

Related

Postman PUT request doesn't update content but no error

I'm using postman with springboot i have already used the GET/POST/DELETE requests and they all work fine but PUT request doesn't update content .
In intellij i'm using these files :
Student.java(with it's setters and getters) :
#Entity
#Table
public class Student {
#Id
#SequenceGenerator(
name="student_sequence",
sequenceName="student_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "student_sequence"
)
private Long id;
private String name;
private LocalDate dob;
private String email;
#Transient
private Integer age;
StudentController.java :
#PutMapping(path ="{studentId}")
public void updateStudent(
#PathVariable("studentId") Long studentId,
#RequestParam(required = false) String name,
#RequestParam(required = false) String email)
{
studentService.updateStudent(studentId,name,email);
}
StudentService.java :
#Transactional
public void updateStudent(Long studentId,String name, String email)
{
Student student = studentRepository.findById(studentId)
.orElseThrow(() -> new IllegalStateException(
"student with id="+studentId+"does not exist"));
if (name !=null && name.length()>0 && !Objects.equals(student.getName(),name))
{
student.setName(name);
}
if (email !=null && email.length()>0 && !Objects.equals(student.getEmail(),email))
{
Optional<Student> studentOptional= studentRepository.findStudentByEmail(email);
if (studentOptional.isPresent())
{
throw new IllegalStateException("email taken");
}
student.setEmail(email);
}
}
These are the students that i have in database
And basically i want to update the name and email of the student with id=1.
That is postman header
And that is postman not showing any error after sending request
using #RequestParam(required = false) String name the params are expected as header or query param. You're sending a request body, so use a pojo instead...
class StudentDto {
public String name;
//...
}
and the controller ...
#PutMapping(path ="{studentId}")
public void updateStudent(
#PathVariable("studentId") Long studentId,
#RequestBody StudentDto) {
//...
}
To leave it be and make it work, you have to put your data as query params, lik ethis
PUT http://localhost:8080/api/v1/student/1?name=newName&email=newEmailToSet
So after this answer https://stackoverflow.com/a/72698172/19354780
i have tried changing StudentController to :
#PutMapping(path = "{studentId}")
public void updateStudent(
#PathVariable("studentId") Long studentId,
#RequestBody Student new_student) {
studentService.updateStudent(studentId, new_student);
}
and StudentService to :
#Transactional
public void updateStudent(Long studentId,Student new_student)
{
Student student = studentRepository.findById(studentId)
.orElseThrow(() -> new IllegalStateException(
"student with id="+studentId+"does not exist"));
if (new_student.getName() !=null && new_student.getName().length()>0 && !Objects.equals(student.getName(),new_student.getName()))
{
student.setName(new_student.getName());
}
if (new_student.getEmail() !=null && new_student.getEmail().length()>0 && !Objects.equals(student.getEmail(),new_student.getEmail()))
{
Optional<Student> studentOptional= studentRepository.findStudentByEmail(new_student.getEmail());
if (studentOptional.isPresent())
{
throw new IllegalStateException("email taken");
}
student.setEmail(new_student.getEmail());
}
}
and it worked but without the exceptions .

How to read data from nested array in Firestore

I have the below structure in my Firestore and I want to read data and store it in ArrayList like I have "amountArrayList" which will read data from the "transactions" field in Firestore, I want to read all the "amount" fields from "transactions" field and make array list of it so that I can show it in list manner.
My code
Map<String, Object> map = document.getData();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getKey().equals("transactions")) {
System.out.println(entry.getValue().toString());
}
}
Output
[{transactionType=Credit, amount=3000, dateToStr=17/12/2021, timeToStr=08:06:10, description=}, {transactionType=Credit, amount=2000, dateToStr=17/12/2021, timeToStr=08:06:50, description=}]
Since transactions is an array field, the value you get from entry.getValue() is a List of objects. Since each of these objects in the JSON has properties, they each will be a Map<String, Object> again in the Java code.
A simple way to print the amounts would be something like:
List transactions = document.get("transactions");
for (Object transaction: transactions) {
Map values = (Map)transaction;
System.out.println(values.get("amount")
}
While Frank van Puffelen's answer will work perfectly fine, there is a solution in which you can directly map the "transactions" array into a list of custom objects. Assuming that you have a class declaration that looks like this:
class User {
public String balance, email, firstname, lastname, password, username;
public List<Transaction> transactions;
public User(String balance, String email, String firstname, String lastname, String password, String username, List<Transaction> transactions) {
this.balance = balance;
this.email = email;
this.firstname = firstname;
this.lastname = lastname;
this.password = password;
this.username = username;
this.transactions = transactions;
}
}
And one that looks like this:
class Transaction {
public String amount, dateToStr, description, timeToStr, transactionType;
public Transaction(String amount, String dateToStr, String description, String timeToStr, String transactionType) {
this.amount = amount;
this.dateToStr = dateToStr;
this.description = description;
this.timeToStr = timeToStr;
this.transactionType = transactionType;
}
}
To get the list, it will be as simple as:
docRef.get().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
List<Transaction> transactions = document.toObject(User.class).transactions;
List<String> amountArrayList = new ArrayList<>();
for(Transaction transaction : transactions) {
String amount = transaction.amount;
amountArrayList.add(amount);
}
// Do what you need to do with your amountArrayList
}
}
});
You can read more info in the following article:
How to map an array of objects from Cloud Firestore to a List of objects?.
I found the best way to map the data from/to hashmap to send/retrieve it from FirebaseFirestore
First, you need to create an extension class to handle the mapping
fun <T> T.serializeToMap(): Map<String, Any> {
return convert()
}
inline fun <reified T> Map<String, Any>.toDataClass(): T = convert()
inline fun <A, reified B> A.convert(): B =
Gson().fromJson(Gson().toJson(this), object : TypeToken<B>() {}.type)
Don't forget to add Gson
implementation 'com.google.code.gson:gson:2.9.0'
Then, you can use these functions for mapping the result from firebase
if (task.isSuccessful) {
val list: MutableList<YourObject> = ArrayList()
for (document in task.result) {
list.add(document.data.toDataClass())
}
// do whatever you want with the list :)
} else {
// handle the error
}

Find string with maximum length of given attribute as string in java 8

I am trying to find string with max length of a given attribute of java. I will pass the attribute name as string into the method which will return me the string value of max length.
class Employee {
private String name;
private String designation;
private List<Address> address;
private ContactInfo contactInfo;
....
getter setter
}
class Address {
private String city;
private String state;
private String country;
......
getter setter
}
class ContactInfo {
private String mobileNumber;
private String landlineNumber;
....
getter setter
}
I have some data just like below:
ContactInfo contactInfo = new ContactInfo("84883838", "12882882");
Address address1 = new Address("city111", "state111", "country111");
Address address2 = new Address("city111111", "state11112", "country1112");
Employee employee1 = new Employee("xyz", "uyyy", List.of(address1, address2), contactInfo);
private String findStringWithMaxLength(String attribute) {
return employeeList.stream()
....
}
In above case, if I provide attribute value as "city" then it should return me the value "city111111" because of maximum string length.
If we have child objects and list of objects, how do I traverse with the given attribute.
You can create a method that take a list of employees and a function to get the specific attribute like this:
private String findStringWithMaxLength(List<Employee> employees, Function<Employee, String> function) {
return employees.stream()
.map(function)
.max(Comparator.comparing(String::length))
.orElseThrow(() -> new IllegalArgumentException("Empty list"));
}
and to call the method you can use:
findStringWithMaxLength(employees, Employee::getName)
findStringWithMaxLength(employees, Employee::getDesignation)
findStringWithMaxLength(employees, Employee::getAddress)
Note that the method will throw an exception if the list is empty, if you wont throw an exception, then you can replace it with orElse(withDefaultValue)
You can do it using reflection but here is a better "typesafe" way.
Let the class:
#Getter
#Setter
#AllArgsConstructor
static class Employee {
private String name;
private String designation;
private String address;
}
with getters and let the list
static List<Employee> employeeList = asList(
new Employee("xyz1", "abc1234", "address 123"),
new Employee("xyz123", "abc123", "address 1234"),
new Employee("xyz1234", "abc12", "address 12")
);
then, you can define a generic function able to traverse any String field
static Optional<String> findStringWithMaxLength(Function<Employee, String> getter) {
return employeeList.stream().map(getter).max(Comparator.comparingInt(String::length));
}
now, you can apply every getter to that function
for(Function<Employee, String> getter: Arrays.<Function<Employee, String>>asList(
Employee::getName,
Employee::getDesignation,
Employee::getAddress))
System.out.println(findStringWithMaxLength(getter));
with output
Optional[xyz1234]
Optional[abc1234]
Optional[address 1234]
(the optional is required since the list could be empty).
The given answers work fine. I'd like to use an enum in this case. If a method changes in the Employee class, you only have to change the enum, not every call using it:
enum EmployeeField {
NAME(Employee::getName),
DESIGNATION(Employee::getDesignation),
ADDRESS(Employee::getAddress);
private final Function<Employee, String> getter;
EmployeeField(Function<Employee, String> getter) {
this.getter = getter;
}
public Function<Employee, String> getGetter() {
return getter;
}
}
private static final List<Employee> employeeList = Arrays.asList(
new Employee("xyz1", "abc", "address 1"),
new Employee("xyz123", "abc", "address 1"),
new Employee("xyz1234", "abc", "address 1")
);
public static void main(String[] args) {
Optional<String> longestName = findStringWithMaxLength(EmployeeField.NAME);
if (longestName.isPresent()) {
System.out.println("Longest name = " + longestName.get());
} else {
System.out.println("No longest name");
}
}
private static Optional<String> findStringWithMaxLength(EmployeeField employeeField) {
return employeeList.stream()
.map(employee -> employeeField.getGetter().apply(employee))
.max(Comparator.comparing(String::length));
}
EDIT for your city use case, something along those lines:
Add an enum AddressField on the same model as the EmployeeField
enum AddressField {
CITY(Address::getCity);
....
}
then add a method
private static Optional<String> findStringWithMaxLength(List<Address> addressList, AddressField addressField) {
return addressList.stream()
.map(employee -> addressField.getGetter().apply(employee))
.max(Comparator.comparing(String::length));
}
and then add a CITY enum to your EmployeeField enum:
LANDLINE_NUMBER(employee -> employee.getContactInfo().getLandlineNumber()),
CITY(employee -> findStringWithMaxLength(employee.getAddress(), AddressField.CITY).get());

Returning boolean instead of list

user class with fields id, name and types
public class User {
private String id;
private String name;
private List<Types> types;
}
Types class with fields id, name , linkedTo
{
private String id;
private String name;
private String linkedTo;
}
Implemented a method to get types from user and return it as list
private List<Types> getType(List<User> Users) {
return Users
.stream()
.map(User::gettypes)
.flatMap(List::stream)
.collect(Collectors.toList());
}}
Now, have to map to response body. For that I have response class:
public class UserResponse {
private String id;
private String type;
private String name;
private String linkedTo;
}
Have to map the fields from Types to user response to return it as list
private List<UserResponse> getUserResponse(UserRequest request) {
List<User> Users = userServiceClient.getById();
List<UserResponse> userResponses = new ArrayList<>();
List<Types> TypesList = getType(Users);
if (!typesList.isEmpty()) {
return typesList.stream()
.map(type -> userResponses.add(new UserResponse( type.getGuid(),type.getName(),type.getName(),type.getLinkedTo())))
.collect(Collectors.toList());
}
return collections.emptylist();
}
Here is the problem, I'm not able to return it as list instead getting a boolean..
Is there any efficient way to do this? The code reviewer doesn't want me to use for each and return the list
A better appraoch would be
private List<UserResponse> getUserResponse(UserRequest request) {
List<User> Users = userServiceClient.getById();
List<Types> TypesList = getType(Users);
return typesList.stream()
.map(type -> new UserResponse(type.getId(), type.getName(), type.getName(), type.getLinkedTo()))
.collect(Collectors.toList());
}
Reason:
If typesList is empty, this would return empty List, as Collections.emptyList in your code.
It maps to the UserResponse objects and collects them to return the List<UserResponse> while using the Stream for it without the explicit instantiation and .add call.

Handle optional parameters in QueryDSL

I am using QueryDSL with SpringData.
I have Table say, Employee and I have created entity class say, EmployeeEntity
I have written following service method
public EmployeeEntity getEmployees(String firstName, String lastName)
{
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanExpression query = null;
if(firstName != null)
{
query = employee.firstName.eq(firstName);
}
if(lastName != null)
{
query = query.and(employee.lastName.eq(lastName)); // NPException if firstName is null as query will be NULL
}
return empployeeDAO.findAll(query);
}
As in above I commented the NPException. How to use QueryDSL for optional Parameters in QueryDSL using Spring Data?
Thank you :)
BooleanBuilder can be used as a dynamic builder for boolean expressions:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanBuilder where = new BooleanBuilder();
if (firstName != null) {
where.and(employee.firstName.eq(firstName));
}
if (lastName != null) {
where.and(employee.lastName.eq(lastName));
}
return empployeeDAO.findAll(where);
}
BooleanBuilder is good. You can also wrap it and add "optional" methods in order to avoid the if conditions:
For example, for "and" you can write: (Java 8 lambdas are used)
public class WhereClauseBuilder implements Predicate, Cloneable
{
private BooleanBuilder delegate;
public WhereClauseBuilder()
{
this.delegate = new BooleanBuilder();
}
public WhereClauseBuilder(Predicate pPredicate)
{
this.delegate = new BooleanBuilder(pPredicate);
}
public WhereClauseBuilder and(Predicate right)
{
return new WhereClauseBuilder(delegate.and(right));
}
public <V> WhereClauseBuilder optionalAnd(#Nullable V pValue, LazyBooleanExpression pBooleanExpression)
{
return applyIfNotNull(pValue, this::and, pBooleanExpression);
}
private <V> WhereClauseBuilder applyIfNotNull(#Nullable V pValue, Function<Predicate, WhereClauseBuilder> pFunction, LazyBooleanExpression pBooleanExpression)
{
if (pValue != null)
{
return new WhereClauseBuilder(pFunction.apply(pBooleanExpression.get()));
}
return this;
}
}
#FunctionalInterface
public interface LazyBooleanExpression
{
BooleanExpression get();
}
And then the usage would be much cleaner:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
return empployeeDAO.findAll
(
new WhereClauseBuilder()
.optionalAnd(firstName, () -> employee.firstName.eq(firstName))
.optionalAnd(lastName, () -> employee.lastName.eq(lastName))
);
}
It is possible also to use jdk's Optional class
This is Java 101 actually: check for null and initialize the query instead of concatenating predicates. So a helper method like this could do the trick:
private BooleanExpression createOrAnd(BooleanExpression left, BooleanExpression right) {
return left == null ? right : left.and(right);
}
Then you can simply do:
BooleanExpression query = null;
if (firstName != null) {
query = createOrAnd(query, employee.firstName.eq(firstName));
}
if (lastName != null) {
query = createOrAnd(query, employee.lastName.eq(lastName));
}
…
Note, that I use createOrAnd(…) even in the first clause simply for consistency and to not have to adapt that code in case you decide to add a new clause even before the one for firstName.
if you check the QueryDSL implementation of null:
public BooleanExpression and(#Nullable Predicate right) {
right = (Predicate) ExpressionUtils.extract(right);
if (right != null) {
return BooleanOperation.create(Ops.AND, mixin, right);
} else {
return this;
}
}
which is supposedly what you want.
I faced same problem and here comes another version of Timo Westkämper
's accepted answer using the Optional.
default Optional<Correlation> findOne(
#Nonnull final String value, #Nullable final String environment,
#Nullable final String application, #Nullable final String service) {
final QSome Some = QSome.some;
final BooleanBuilder builder = new BooleanBuilder();
ofNullable(service).map(some.service::eq).map(builder::and);
ofNullable(application).map(some.application::eq).map(builder::and);
ofNullable(environment).map(some.environment::eq).map(builder::and);
builder.and(some.value.eq(value));
return findOne(builder);
}
For any one wants to build predicate based on dynamic request parameters map instead of specific ones can use the following simple format,
public List<User> searchUser(Map<String, Optional<String>> requestParams ){
QUser qUser = Quser.qUser;
BooleanBuilder builder = new BooleanBuilder();
requestParams.forEach( (String key, String value) -> {
if(!value.isEmpty()) {
StringPath column = Expressions.stringPath(qUser, key);
builder.and(column.eq(value));
}
});
}
And here is my controller
#RequestMapping(value = "", method = RequestMethod.GET)
public ResponseEntity<List<User>> searchUser(
#RequestParam() Map<String, Optional<String>> requestParams) {
List<User> userList = userService.searchUser(requestParams);
if(userList!=null)
return new ResponseEntity<>(userList, HttpStatus.OK);
else
return new ResponseEntity<>(userList, HttpStatus.INTERNAL_SERVER_ERROR);
}
Base on what you need i would do this
public List<EmployeeEntity> getEmployees(Optional<String> firstName, Optional<String> lastName)
{
BooleanExpression queryPredicate = QEmployeeEntity.employeeEntity.firstName.containsIgnoreCase(firstName.orElse("")).and(QEmployeeEntity.employeeEntity.lastName.containsIgnoreCase(lastName.orElse("")));
return empployeeDAO.findAll(queryPredicate);
}
First of all you should return a List of EmployeeEntity. Second, its better to use optional than checking if its null, and you may pass Java 8's Optional values obtained from optional RequestParam ones like this:
#RequestMapping(value = "/query", method = RequestMethod.GET)
public ModelAndView queryEmployee(#RequestParam(value = "firstName", required = false) Optional<String> firstName, #RequestParam(value = "lastName", required = false) Optional<String> lastName)
{
List<EmployeeEntity> result = getEmployees(firstName, lastName);
....
}
And a very important thing is to use the containsIgnoreCase function in the predicate: its better than a typical like cause its case insensitive.
In my opinion you should use some approach like this:
#Controller
class UserController {
#Autowired UserRepository repository;
#RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, #QuerydslPredicate(root = User.class) Predicate predicate,
Pageable pageable, #RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
look it at here.
This is a very simple way to deal with optional parameters, I use it in my project :
public List<ResultEntity> findByOptionalsParams(String param1, Integer param2) {
QResultEntity qResultEntity = QResultEntity.resultEntity;
final JPQLQuery<ResultEntity> query = from(qResultEntity);
if (!StringUtils.isEmpty(param1)) {
query.where(qResultEntity.field1.like(Expressions.asString("%").concat(param1).concat("%")));
}
if (param2 != null) {
query.where(qResultEntity.field2.eq(param2));
}
return query.fetch();
}
There is another way using Optional without BooleanBuilder although the resulting query might be a bit verbose:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanExpression where = ofNullable(firstName).map(employee.firstName::eq).orElse(Expressions.TRUE)
.and(ofNullable(lastName).map(employee.lastName::eq).orElse(Expressions.TRUE));
return empployeeDAO.findAll(where);
}
Taking that idea and adding a helper function improves readability though:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanExpression where = optionalExpression(firstName, employee.firstName::eq)
.and(optionalExpression(lastName, employee.lastName::eq));
return empployeeDAO.findAll(where);
}
public static <T> BooleanExpression optionalExpression(T arg, Function<T, BooleanExpression> expressionFunction) {
if (arg == null) {
return Expressions.TRUE;
}
return expressionFunction.apply(arg);
}

Categories