Move recurring #RequestParams somewhere before Controller - java

I have multiple endpoints in my RestControllers that follow some similar signature:
#GetMapping
public SomeItem get(#RequestParam String sortBy,
#RequestParam String sortField,
#RequestParam int pageNumber,
#RequestParam int pageSize) {
QueryOptions queryOptions = QueryOptions.of(sortyBy, sortField, pageNumber, pageSize);
// ...
}
I was wondering if there is a way of removing this code duplication from all the different methods and move the QueryOptions construction somewhere before the RestController method, so that I could use a method like the following:
#GetMapping
public SomeItem get(QueryOptions queryOptions) {
// ...
}
How can I do this? Maybe adding a filter in the filterchain?

It turns out that this is supported out of the box:
#Getter
#Setter
public class QueryOptions {
private String pageNumber;
private String pageSize;
private String orderBy;
private String sortField;
}
And then you can accept this Class in the Controller method:
#GetMapping
public SomeItem get(QueryOptions queryOptions) {
// ...
}

Related

Search based on filters like name, price etc [duplicate]

This question already has answers here:
Dynamic spring data jpa repository query with arbitrary AND clauses
(4 answers)
Closed 4 months ago.
I am implementing a product search feature where user can search for products based on name, brand, and price.
I have written different endpoints for searching for different combination and I hate the code and I cannot add additional filters easily and have to create all the combination for any additional filter I have to add.
My Product Repository -
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.vaibhavshrivastava.productbrowser.model.Product;
public interface ProductRepository extends JpaRepository<Product, Integer> {
//
public List<Product> findByName(String name);
public List<Product> findByNameAndProductCode(String name, int productCode);
public List<Product> findByNameAndBrand(String name, String brand);
public List<Product> findByNameAndBrandAndProductCode(String name, String brand, int productCode);
public List<Product> findByBrand(String brand);
public List<Product> findByProductCode(int productCode);
public List<Product> findByBrandAndProductCode(String brand, int productCode);
// public int getPrice(int productCode);
public Optional<Product> findByProductCode(Integer productCode);
}
My Product Controller -
#RestController
#CrossOrigin
#RequestMapping("/products")
public class ProductController {
#Autowired
ProductRepository productRepository;
#Autowired
ProductService productService;
#GetMapping("/nameandbrand")
public ResponseEntity<List<Product>> getProductsByNameAndBrand(#RequestParam String name,
#RequestParam String brand) {
return new ResponseEntity<>(productRepository.findByNameAndBrand(name, brand), HttpStatus.OK);
}
#GetMapping("/nameandproductcode")
public ResponseEntity<List<Product>> getProductsByNameAndProductCode(#RequestParam String name,
#RequestParam int productCode) {
return new ResponseEntity<>(productRepository.findByNameAndProductCode(name, productCode), HttpStatus.OK);
}
#GetMapping("/name")
public ResponseEntity<List<Product>> getProductsByName(#RequestParam String name) {
return new ResponseEntity<>(productRepository.findByName(name), HttpStatus.OK);
}
#GetMapping("/nameandbrandandproductcode")
public ResponseEntity<List<Product>> getProductsByNameOrBrandOrProductCode(#RequestParam String name, #RequestParam String brand, #RequestParam int productCode){
return new ResponseEntity<>(productRepository.findByNameAndBrandAndProductCode(name, brand, productCode), HttpStatus.OK);
}
#GetMapping("/brand")
public ResponseEntity<List<Product>> getProductsByBrand(#RequestParam String brand){
return new ResponseEntity<>(productRepository.findByBrand(brand), HttpStatus.OK);
}
#GetMapping(name="/productcode")
public ResponseEntity<Optional<Product>> getProductsByProductCode(#RequestParam Integer productCode){
return new ResponseEntity<>(productRepository.findByProductCode(productCode), HttpStatus.OK);
}
#GetMapping("/brandandproductcode")
public ResponseEntity<List<Product>> getProductsByBrandAndProductCode(#RequestParam String brand, #RequestParam int productCode){
return new ResponseEntity<>(productRepository.findByBrandAndProductCode(brand, productCode), HttpStatus.OK);
}
#GetMapping("/{pid}/details")
public ResponseEntity<Product> getProductDetails(#PathVariable("pid") Integer productCode){
System.out.println("PPPPPPPPPPRDDDDDDDCTTTT CODEEEEE" + productCode);
Product selectedProduct = productRepository.findByProductCode(productCode).orElseThrow();
return new ResponseEntity<>(selectedProduct, HttpStatus.OK);
}
#GetMapping("/")
public List<Product> getProducts(){
return productService.getProducts();
}
}
I have not yet added the Price filter and I have to add it so I have to make all the combinations to search with price filter too.
What is the best way to implement something like this?
I am sending parameters using angular on the frontend.
How to transform this bad code into something in which I can add additional filters easily.
My Product Entity have these fields -
#Entity
public class Product {
#Id
private int productCode;
private String name;
private String brand;
private String description;
private int price;
private String img;
(Not included hash and getters and setters etc)
create one endpoint with Your custom filter object containing filter fields and then build Specification using that filter, then query database using that Specification

#DefaultValue return empty string

I get request like
http://localhost:8080?sortBy=&sortDir=&page=10
javax.ws.rs.DefaultValue understand
My controller looks like
public class MyController {
public Response test(#BeanParam Pageable params){
...
}
}
#Data
public class Pageable {
#QueryParam("sortBy")
#DefaultValue("1")
private String sortBy;
#QueryParam("sortDir")
#DefaultValue("ASC")
private String sortDir;
private int page;
}
And under the hood you can find, that java check if parameter == null it put my default value, but in my request I have "" - empty string.
How to workaround situation with DefaultValue?
Can simply assign default value like below :
#Data
public class Pageable {
#QueryParam("sortBy")
private String sortBy = "1";
#QueryParam("sortDir")
private String sortDir = "ASC";
private int page;
}

how to implement a spring boot controller to return the result of a query as json

I am trying to a write a spring boot controller which can return the result of a native query as json. I will be passing the query as input parameter and the return must be result of the query. Is there a way to do this? I know the http rpc help on this. The query can be anything and the system must accept it and must respond with the result as json.
For example if I pass the request as select * from employee it must respond with result of query as json.
Simply make every function returning:
Map<String, Object>
It will automatically map the object property and value. That means a json object is an instance of Map. If you are managing an array of it, enclose it with a List:
List<Map<String, Object>>
and finally the ResponseEntity becomes:
ResponseEntity<List<Map<String, Object>>>
You could actually use Spring JDBC for that,
Repo
#Repository
public class FooRepo {
#Autowire
private JdbcTemplate jdbcTemplate;
public Object returnDataForQuery(String sql) {
return jdbcTemplate.queryForObject(sql, Object.class); // You could define a proper class if you know the return Type else returning plain object is more then enough
// return jdbcTemplate.queryForList(sql, Object.class) Incase Multiple Data
}
}
Model
public class FooDto {
private String query;
// Getter, Setter & No Args Constructor (or) Lombok
}
Controller
#Autowire
private FooRepo fooRepo;
#PostMapping(value = "/postData", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity postData(#RequestBody FooDto foo) {
return ResponseEntity.ok(fooRepo.returnDataForQuery(foo.getQuery);
}
This is just a overview, you could bend it.As for your result output concern you ResponseEntity will take care of it
SpringBoot
//Controller Class
#RestController
#RequestMapping("/employee")
public class EmployeeController {
#Autowired
private EmployeeService employeeService;
#GetMapping("/all")
public List<Employee> getAllEmplpyee() {
logger.info("get All Employeee");
return employeeService.getAllEmployeeService();
}
}
//ServiceImpl
#Service
public class EmployeeService {
private static final Logger logger = LoggerFactory.getLogger(EmployeeService.class);
#Autowired
private EmployeeRepository employeeRepository;
public List<Employee> getAllEmployeeService() {
logger.info(getClass().getName()," invked getAllEmployee");
List<Employee> empBo = employeeRepository.findAll();
return copyPropertiesValues(empBo);
}
}
//DAO
#Component
public interface EmployeeRepository extends JpaRepository<Employee, String>{
}
//Model
#Entity
#Table(name = "employees")
public class Employee {
#Id
#Column(name = "employeeNumber",nullable=false)
private String employeeNumber;
#Column(nullable=false)
private String lastName;
#Column(nullable=false)
private String firstName;
#Column(nullable=false)
private String extension;
#Column(nullable=false)
private String email;
#Column( nullable=false)
private String officeCode;
#Column(nullable=false)
private String reportsTo;
#Column(nullable=false)
private String jobTitle;
//GETTER SETTER
}
//application.properties
spring.jpa.hibernate.ddl-auto=update
spring.jpa.open-in-view=true
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.type.descriptor.sql=trace
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=****

Spring map GET request parameters to POJO automatically

I have method in my REST controller that contains a lot of parameters. For example:
#RequestMapping(value = "/getItem", method = RequestMethod.GET)
public ServiceRequest<List<SomeModel>> getClaimStatuses(
#RequestParam(value = "param1", required = true) List<String> param1,
#RequestParam(value = "param2", required = false) String param2,
#RequestParam(value = "param3", required = false) List<String> param3,
#RequestParam(value = "param4", required = false) List<String> param4,
#RequestParam(value = "param5", required = false) List<String> param5) {
// ......
}
and I would like to map all GET request parameters to a POJO object like:
public class RequestParamsModel {
public RequestParamsModel() {
}
public List<String> param1;
public String param2;
public List<String> param3;
public String param4;
public String param5;
}
I need something like we can do using #RequestBody in REST Controller.
Is it possible to do in Spring 3.x ?
Thanks!
Possible and easy, make sure that your bean has proper accessors for the fields. You can add proper validation per property, just make sure that you have the proper jars in place. In terms of code it would be something like
import javax.validation.constraints.NotNull;
public class RequestParamsModel {
public RequestParamsModel() {}
private List<String> param1;
private String param2;
private List<String> param3;
private String param4;
private String param5;
#NotNull
public List<String> getParam1() {
return param1;
}
// ...
}
The controller method would be:
import javax.validation.Valid;
#RequestMapping(value = "/getItem", method = RequestMethod.GET)
public ServiceRequest<List<SomeModel>> getClaimStatuses(#Valid RequestParamsModel model) {
// ...
}
And the request, something like:
/getItem?param1=list1,list2&param2=ok
Are you trying to do
#RequestMapping(value = "/getItem", method = RequestMethod.GET)
public ServiceRequest<List<SomeModel>> getClaimStatuses(#ModelAttribute RequestParamsModel requestParamModel) {
...
}

How to validate two or more beans in a Spring Controller method with Hibernate Validator (JSR 303)

I have two classes (Beans)
public class BeanOne {
#Min(1)
private Integer idBeanOne;
#NotBlank
private String nameBeanOne;
#NotNull
#Min(1)
private Integer idOther;
// ... Getters and Setters
}
public class BeanTwo {
#Min(1)
private Integer idBeanTwo;
#NotBlank
private String nameBeanTwo;
#NotNull
#Min(1)
private Integer idOtherTwo;
// ... Getters and Setters
}
Controller of Spring
// Method in Controller
#RequestMapping(value = "/name.html", method = RequestMethod.POST)
public #ResponseBody
Map<String, Object> submitInsert(#Valid BeanOne one,
#Valid BeanTwo two, BindingResult result) {
if (result.hasErrors()) {
// Errores
} else {
// :D
}
}
Is there any way that I can validate two or more beans? I have successfully validated a single bean, but I have not been successful in validating two or more beans. How can I do this?
thanks: D
thanks: D
After many attempts to validate two or more beans with JSR303, come to this solution.
public class BeanOne {
#Valid
private BeanTwo beanTwo;
// other beans to validate
#Valid
private BeanN beanN;
#Min(1)
private Integer idBeanOne;
#NotBlank
private String nameBeanOne;
#NotNull
#Min(1)
private Integer idOther;
// ... Getters and Setters
}
public class BeanTwo {
#Min(1)
private Integer idBeanTwo;
#NotBlank
private String nameBeanTwo;
#NotNull
#Min(1)
private Integer idOtherTwo;
// ... Getters and Setters
}
// Controller Spring
#Controller
public class XController {
#Autowired
private Validator validator;
#RequestMapping(value = "/name.html", method = RequestMethod.POST)
public #ResponseBody Map<String, Object>
submitInsert(BeanOne beanOne, BeanTwo beanTwo, BindingResult result) {
beanOne.setBeanTwo(beanTwo);
// beanOne.setBeabN(beanN);
validator.validate(beanOne, result);
if (result.hasErrors()) {
// Errores
} else {
// :D
}
}
// more code ...
}
But now I have another problem :(
I have this file Messages.properties
typeMismatch.java.lang.Integer = Must specify an integer value.
typeMismatch.java.lang.Long = Must specify an integer value.
typeMismatch.java.lang.Float = Must specify a decimal value.
typeMismatch.java.lang.Double=Must specify a decimal value.
This file helps me to catch exceptions, when a field expects a number, and the user enters text
This works perfectly for the first bean (BeanOne) but not for nested beans (BeanTwo, BeanN)
I hope they can help me: D
thanks

Categories

Resources