I have created Post, Put and Delete Request in my controller in spring boot.
I have added validations in my model and also added #Valid parameter in method on controller.
I want to what else I am supposed to add for validation for Post, Put and Delete operation?
public class Employee {
#NotNull(message = "Employee Id can not be null")
private Integer id;
#Min(value = 2000, message = "Salary can not be less than 2000")
#Max(value = 50000, message = "Salary can not be greater than 50000")
private Integer salary;
#NotNull(message = "designation can not be null")
private String designation;
}
My Post Method is :
#PostMapping("/employees")
public ResponseEntity<Void> addEmployee(#Valid #RequestBody Employee newEmployee) {
Employee emp= service.addEmployee(newEmployee);
if (emp== null) {
return ResponseEntity.noContent().build();
}
return new ResponseEntity<Void>(HttpStatus.CREATED);
}
My Put Method is :
#PutMapping("/employees/{id}")
public ResponseEntity<Vehicle> updateEmployee(#Valid #RequestBody Employee updateEmployee) {
Employee emp= service.EmployeeById(updateEmployee.getId());
if (null == emp) {
return new ResponseEntity<Employee>(HttpStatus.NOT_FOUND);
}
emp.setSalary(updateEmployee.getSalary());
emp.setDesignation(updateEmployee.getDesignation());
service.updateEmployee(emp);
return new ResponseEntity<Employee>(emp, HttpStatus.OK);
}
Delete Method
#DeleteMapping("/employees/{id}")
public ResponseEntity<Employee> deleteEmployee(#Valid #PathVariable int id) {
Employee emp = service.getEmployeeById(id);
if (null == employee) {
return new ResponseEntity<Employee>(HttpStatus.FOUND);
}
service.deleteEmployee(id);
return new ResponseEntity<Employee>(HttpStatus.NO_CONTENT);
}
What is your specific problem?
Please refer to the following source for further reading.
Validation in Spring Boot
To your question about PUT - update does not work properly ?
Although, code looks ok. But if you are using JPA, then please remember JPA has delay data writing to database mechanism meaning it does not write data to database right away. And if you want JPA to write/save your data right away then you will have to call respository.saveAndFlush() - to force the JPA to write all data in session.
So, instead of calling the repository.saveAndFlush() every time you save data, you can simply return the same request object in this case "updateEmployee" instead of "emp" object for updating record e.g. :
return new ResponseEntity(updateEmployee, HttpStatus.OK);
POST : You should not use "#NotNull(message = "Employee Id can not be null")" on private Integer id since you are using same object for both POST and PUT method because #Valid will validate all fields in class.
Related
I have an objects in java backend that i want to change status property on:
#Entity
public class Employee {
#Id #GeneratedValue long id;
private String name;
private String status;
}
I want to set the property status to "Checked" for a targeted object chosen with id with this putmapping:
#PutMapping("/api/users/id")
public Employee changeStatus(#RequestParam Long id)
{
Employee newEmployee = userRepository.getById(id);
newEmployee.setStatus("Checked");
return userRepository.save(newEmployee);
}
I want to do it from my frontend through :
public changeStatus(id: number): Observable<any>
{
return this.http.put<any>(${this.apiServerUrl}/users/id, id)
}
Nothing happens in backend, no errors or anything shows up. What am i missing? I suspect i do something wrong in the frontend call, but i cant figure it out.
Backend-frontend connections seems to work because i can get all data from my backend and see it in frontend with theese two endpoints
Backend:
#RequestMapping("/api/users") public List<Employee> getUsers()
{
return (List<Employee>) userRepository.findAll();
}
Frontend:
public getUsers(): Observable<any>
{
return this.http.get<any>(${this.apiServerUrl}/users)
}
Thanks in advance!
Are you subscribing to this call on the FE ? the angular HttpClient returns an Observable, if you are not subscribing, i.e. if you are not doing something like this:
getUsers().subscribe((data) => {
console.log('whatever');
})
The http request will never be send, that's how observable works, they only get "executed" when someone is "listening"
Seeing how you try to pass id as pathVariable your endpoint should also accept it as such. For that refactor your endpoint. Frontend looks fine.
#PutMapping("/api/users/{id}")
public Employee changeStatus(#PathVariable Long id)
{
Employee newEmployee = userRepository.getById(id);
newEmployee.setStatus("Checked");
return userRepository.save(newEmployee);
}
Remove one extra id from url.
public changeStatus(id: number): Observable<any>
{
const url = this.apiServerUrl + /users/ + id;
return this.http.put<any>(url);
}
Otherwise try to get rid of any types and use strong typing - that's the benefit TypeScript gives us.
I'm a newbie in spring boot and I'm creating the method deleteById and I want check that user exist before.
How can i do to return a diferent message if the user exist or not?
Controller
#GetMapping(value = "/delete/{id}")
public ResponseEntity<Personaje> deleteCharacter(#PathVariable("id") long id){
Personaje p = this.characterService.deteleCharacter(id);
return new ResponseEntity<Personaje>(p, HttpStatus.OK);
}
Service
public Character deteleCharacter(long id) {
Character ch = this.repository.findById(id);
for example, if the id no exits, return a message "the id not found"
In the response entity, you can have something like this:
return new ResponseEntity<>("The ID is not found", HttpStatus.NOT_FOUND)
As for the return statement, you can leave it as a generic so from ResponseEntity<Personaje> you instead do it ResponseEntity<>.
This way you can return a generic response entity, and then populate it with data if needed.
I want to send a REST PATCH request to my API to only update some fields not the whole requestbody. Can someone help me to solve this issue
#PatchMapping("/update/{id}")
public ResponseEntity<String> update(#Valid #RequestBody Ordonnance ordonnance, #PathVariable("id") String id){
Optional<Ordonnance> ordonnanceData = ordonnanceRepository.findById(id);
this.ordonnanceRepository.save(ordonnance);
return ResponseEntity.ok("resource updated");
}
You can modify the fields by calling setters on the object which is returned from the repository.
#PatchMapping("/update/{id}")
public ResponseEntity<String> update(#Valid #RequestBody Ordonnance ordonnance, #PathVariable("id") String id){
Optional<Ordonnance> dbOrdonnance = ordonnanceRepository.findById(id);
if (!ordonnanceData.isPresent()) {
// The ID entered could not be found.
return ResponseEntity.notFound("Resource with id " + id + " was not found");
}
// Modify the values of the Ordonnance object retrieved from the database
Ordonnance ordonnanceToEdit = dbOrdonnance.get();
ordonnanceToEdit.setField1(ordonnance.getField1());
ordonnanceToEdit.setField2(ordonnance.getField2());
ordonnanceToEdit.setField3(ordonnance.getField3());
// Save to repository
this.ordonnanceRepository.save(ordonnanceToEdit);
return ResponseEntity.ok("Resource with ID " + id + " was successfully updated.");
}
This code should work, however you should create separate Java DTO Classes which are generally used to transfer data. This way you can only pass in the ID and the fields you would like to update, instead of having to pass in the entire object.
Given a RESTful web service developed using the Spring Boot framework, I wanted a way to suppress the birthDate of all Users in the response. This is what I implemented after looking around for a solution :
#RestController
public class UserResource {
#Autowired
private UserDAOservice userDAOService;
#GetMapping("/users")
public MappingJacksonValue users() {
List<User> users = userDAOService.findAll();
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
.filterOutAllExcept("id", "name");
FilterProvider filters = new SimpleFilterProvider().addFilter(
"UserBirthDateFilter", filter);
MappingJacksonValue mapping = new MappingJacksonValue(users);
mapping.setFilters(filters);
return mapping;
}
}
However, when I hit the rest end point in the browser, I can still see the birth date of the user in the response :
{
"id": 1,
"name": "Adam",
"birthDate": "1980-03-31T16:56:28.926+0000"
}
Question 1 : What API can I use to achieve my objective?
Next, assuming that I want to adhere to HATEOAS in combination with filtering, how can I go about doing this. I am unable to figure out the APIs that can be used for using these two features together :
#GetMapping("/users/{id}")
public EntityModel<User> users(#PathVariable Integer id) {
User user = userDAOService.findById(id);
if (user == null) {
throw new ResourceNotFoundException("id-" + id);
}
EntityModel<User> model = new EntityModel<>(user);
WebMvcLinkBuilder linkTo = linkTo(methodOn(this.getClass()).users());
model.add(linkTo.withRel("all-users"));
//how do I combine EntityModel with filtering?
return model;
}
Question 2 : How do I combine EntityModel with MappingJacksonValue?
Note : I am aware of #JsonIgnore annotation but that would apply the filter for all end points that use the domain; however, I want to restrict the filtering only to the two endpoints above.
Turns out for this to work, I have to add the #JsonFilter annotation above the DTO and provide the same name that was used while creating the SimpleFilterProvider.
#JsonFilter("UserBirthDateFilter")
public class User {
private Integer id;
#Size(min=2, message="user name must be atleast 2 characters")
#ApiModelProperty(notes="user name must be atleast 2 characters")
private String name;
#Past
#ApiModelProperty(notes="birth date cannot be in the past")
private Date birthDate;
//other methods
}
There is an easier way to do this, on your transfer object (the class you are sending back to the client), you can simply use the #JsonIgnore annotation to make sure the field is not serialized, and therefore sent to the client. So simply add #JsonIgnore inside your User class for your birthDay field.
You can also read more here about this approach:
https://www.baeldung.com/jackson-ignore-properties-on-serialization
If you need to return a different object for different endpoints (User without birthDay in your case, only for specific) you should create separate transfer objects and use those for their respective endpoints. You can pass your original entity (User) in the constructor to those classes and copy over all fields needed.
You can use Jackson's #JsonView feature. With this, you can tell a certain request mapping to produce serialized JSON with chosen set of properties.
public class View {
interface UserDetails {}
}
public class User {
#JsonView(View.UserDetails.class)
private Long id;
#JsonView(View.UserDetails.class)
private String name;
private String birthdate;
}
Controller be like
#JsonView(View.UserDetails.class)
#GetMapping("/users")
public MappingJacksonValue users() {
....
}
For question 2, I had the exact same question as you did, and here's what I did. It seems to be working:
#GetMapping(path = "/users/{id}")
public MappingJacksonValue retrieveUser(#PathVariable int id){
User user = service.findOne(id);
if(user==null){
throw new UserNotFoundException("id-"+id);
}
//"all-users", SERVER_PATH + "/users"
EntityModel<User> resource = EntityModel.of(user);
WebMvcLinkBuilder linkTo =
linkTo(methodOn(this.getClass()).retrieveAllUsers());
resource.add(linkTo.withRel("all-users"));
SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("id");
FilterProvider filters = new SimpleFilterProvider().addFilter("UserFilter",filter);
MappingJacksonValue mapping = new MappingJacksonValue(resource);
mapping.setFilters(filters);
return mapping;
}
Response for HTTP GET localhost:8080/users/1
{
"id": 1,
"links": [
{
"rel": "all-users",
"href": "http://localhost:8080/users"
}
]}
I'm using hibernate validations for validating incoming requests in spring #RestController.
Problem: I want to reuse the same DTO object in multiple endpoints. But validate some fields only by condition (eg only on specific endpoints).
#RestController
public class ProductsServlet {
#GetMapping("/avail/product")
public Object avail(#Valid ProductDTO product) {
//should validate id field only
}
#GetMapping("/sell/product")
public Object sell(#Valid(with = GroupFuture.class) ProductDTO product) {
//should validate id + from field
}
}
public class ProductDTO {
#NotNull
#NotBlank
private String id;
#Future(groups = GroupFuture.class)
private Date from;
}
Of course the #Valid(with = GroupFuture.class) statement is invalid. But it shows what I'm trying to achieve.
Is that possible?
Got it. Also having to use the Default.class group to validate any fields not having a group.
#GetMapping("/sell/product")
public Object sell(#Validated({Default.class, GroupFuture.class}) ProductDTO product) {
}