To wrap the repository with the Web layer, you must use Spring MVC. Thanks to Spring Boot, almost no basic code can be written. Instead, we can focus on the operation:
nonrest/src/main/java/payroll/EmployeeController.java
package payroll;
import java.util.List;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
class EmployeeController {
private final EmployeeRepository repository;
EmployeeController(EmployeeRepository repository) {
this.repository = repository;
}
// Aggregate root
@GetMapping("/employees")
List<Employee> all() {
return repository.findAll();
}
@PostMapping("/employees")
Employee newEmployee(@RequestBody Employee newEmployee) {
return repository.save(newEmployee);
}
// Single item
@GetMapping("/employees/{id}")
Employee one(@PathVariable Long id) {
return repository.findById(id)
.orElseThrow(() -> new EmployeeNotFoundException(id));
}
@PutMapping("/employees/{id}")
Employee replaceEmployee(@RequestBody Employee newEmployee, @PathVariable Long id) {
return repository.findById(id)
.map(employee -> {
employee.setName(newEmployee.getName());
employee.setRole(newEmployee.getRole());
return repository.save(employee);
})
.orElseGet(() -> {
newEmployee.setId(id);
return repository.save(newEmployee);
});
}
@DeleteMapping("/employees/{id}")
void deleteEmployee(@PathVariable Long id) {
repository.deleteById(id);
}
}
@RestController
It means that the data returned by each method will be written directly to the response body instead of rendering the template;EmployeeRepository
Injected into the controller by the constructor;- We provide routing
@GetMapping
(for each operation@PostMapping
, ,@PutMapping
and@DeleteMapping
, corresponding to the HTTPGET
,POST
,PUT
andDELETE
calls). (Note: It is very useful to read each method and understand its role.); EmployeeNotFoundException
An exception is used to indicate when to find an employee but cannot find an employee.
nonrest/src/main/java/payroll/EmployeeNotFoundException.java
package payroll;
class EmployeeNotFoundException extends RuntimeException {
EmployeeNotFoundException(Long id) {
super("Could not find employee " + id);
}
}
When thrown EmployeeNotFoundException
when, Spring MVC configuration of this additional scenes for rendering HTTP 404 :
nonrest/src/main/java/payroll/EmployeeNotFoundAdvice.java
package payroll;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
class EmployeeNotFoundAdvice {
@ResponseBody
@ExceptionHandler(EmployeeNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
String employeeNotFoundHandler(EmployeeNotFoundException ex) {
return ex.getMessage();
}
}
@ResponseBody
Indicates that the suggestion is directly presented in the response body;@ExceptionHandler
The recommended configuration is only thrownEmployeeNotFoundException
response only;@ResponseStatus
Said to sendHttpStatus.NOT_FOUND
, that is HTTP 404 ;- Suggested topics generate content. In this case, it will give an exception message.
To start to use, stand-alone right PayRollApplication
in public static void main
, and select from the IDE in the Run , or:
Spring Initializr uses the Maven wrapper, so type:
$ ./mvnw clean spring-boot:run
Or use the Maven version we installed to type the following command:
$ mvn clean spring-boot:run
After the application starts, we can query it immediately.
$ curl -v localhost:8080/employees
This will produce:
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /employees HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Thu, 09 Aug 2018 17:58:00 GMT
<
* Connection #0 to host localhost left intact
[{"id":1,"name":"Bilbo Baggins","role":"burglar"},{"id":2,"name":"Frodo Baggins","role":"thief"}]
Here, we can view the preloaded data in compressed format.
If we try to query a non-existent user ...
$ curl -v localhost:8080/employees/99
We will get:
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /employees/99 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 404
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 26
< Date: Thu, 09 Aug 2018 18:00:56 GMT
<
* Connection #0 to host localhost left intact
Could not find employee 99
The message nicely shows the HTTP 404 error and the custom message can't find employee 99 .
Showing the currently coded interaction is not difficult ...
$ curl -X POST localhost:8080/employees -H 'Content-type:application/json' -d '{"name": "Samwise Gamgee", "role": "gardener"}'
Create a new Employee
record, and then send content back to us:
{"id":3,"name":"Samwise Gamgee","role":"gardener"}
We can change users:
$ curl -X PUT localhost:8080/employees/3 -H 'Content-type:application/json' -d '{"name": "Samwise Gamgee", "role": "ring bearer"}'
Update user:
{"id":3,"name":"Samwise Gamgee","role":"ring bearer"}
Depending on how we structure the service, it may have a significant impact. In this case, replacement is better than update . For example, if no name is provided, clear it.
We can delete ...
$ curl -X DELETE localhost:8080/employees/3
$ curl localhost:8080/employees/3
Could not find employee 3
All this is fine, but are we served by RESTful? (If we are not prompted, the answer is no.)
What is missing?