Extension Pack for Java Spring Boot Extension Pack
abra o vscode digite ctrl + shift + P digite spring initialize
Project:
Maven
Language:
Java
Spring Boot:
3.2.5
Packaging:
Jar
Java:
17
Dependecies:
Spring web Spring boot devtools Spring Data JPA H2 Database
Abra a pasta raiz do projeto Add git init adcione as alterações e suba o primeiro commit
Classe StudentController.java
Adicione a anotação
@RestController
@RequestMapping("students")
Crie dentro dela um método helloStudent() para testar
public String helloStudent(){
return "Hello from Student COntroller";
}
Remova esse método e sua importação
Crie dentro da pasta students tbm uma pasta entities
Crie os atributos do estudante
private Integer id;
private String name;
private String course;
Va na classe StudentController e crie um método Student getStudent() adicione a anotação
@GetMapping
Construa um objeto dentro dela
Student s = new Student();
adicione cada um dos atributos do objeto:
s.setName("Gustavo Ferreira");
s.setId(1);
s.setCourse("Engenharia da Computação");
altere o método getStudent para getStudents altere o tipo para LIst
Irá ficar assim:
@GetMapping
public List<Student> getStudents() {
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
s1.setId(1);
s1.setName("Gustavo Ferreira");
s1.setCourse("Engenharia da Computação");
s2.setId(2);
s2.setName("Gabriel da Silva");
s2.setCourse("Análise e Desenvolvimento de Sistemas");
s3.setId(1);
s3.setName("Ricardo Toiobo");
s3.setCourse("Gestão de Tecnologia da Informação");
ArrayList<Student> lista = new ArrayList<Student>();
lista.add(s1);
lista.add(s2);
lista.add(s3);
return lista;
}
Integrando o JPA ao projeto
Adicione o mapeamento na classe Student
@Entity @Table(name = "TBL_STUDENT")
Importe o jakarta.persistence
Adicione para no atributo id as anotações:
@Id @GeneratedValue(strategy = GenerationType.AUTO)
Adicione para o atributo name a anotação:
@Column(length = 40)
Ficando exatamente assim:
@Entity
@Table(name = "TBL_STUDENT")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column(length = 40)
private String name;
private String course;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCourse() {
return course;
}
public void setCourse(String course) {
this.course = course;
}
}
Vá para properties:
Insira os dados de login do banco:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=
Salve as alterações e acesse o console do banco de dados: Clique aqui!
Crie um arquivo dentro de resources
data.sql
Insira os dados da tabela de estudantes:
INSERT INTO TBL_STUDENT(NAME, COURSE) VALUES ("Gustavo Ferreira","Engenharia da Computação");
INSERT INTO TBL_STUDENT(NAME, COURSE) VALUES ("Gabriel da Silva","Análise e Desenvolvimento de Sistemas");
INSERT INTO TBL_STUDENT(NAME, COURSE) VALUES ("Ricardo Toiobo","Gestão de Tecnologia da Informação");
Ao depurar irá dar um erro pois sua tabela ainda não existe
Crie uma pasta repositories
Crie um arquivo SudentRepository.java que será uma interface que herda a interface JpaRepository<Student,Integer>
public interface StudentRepository extends JpaRepository<Student,Integer>{
}
Importe os pacotes:
import org.springframework.data.jpa.repository.JpaRepository;
import com.fatec.student.entities.Student;
Altere o Student.java a anotação
@GeneratedValue(strategy = GenerationType.AUTO)
para
@GeneratedValue(strategy = GenerationType.IDENTITY)
Adicione a inicialização do banco de dados e os caracteres pt-br em resorces/aplication.properties:
spring.jpa.defer-datasource-initialization=true
spring.sql.init.encoding=UTF-8
Crie uma pasta services dentro de student Crie um arquivo StudentService.java
Insira a anotação Service e seu pacote
import org.springframework.stereotype.Service;
@Service
public class StudentService {
}
Inserindo a injeção de Dependência e a inversão de controle
@Autowired
private StudentRepository studentRepository;
importe os packages deles
insira a lista de students:
public List<Student>getStudents(){
return studentRepository.findAll();
}
Vá para os serviços e remova os objetos criados, inserindo as anotações que buscam os dados do banco de dados:
@RestController
@RequestMapping("students")
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping
public List<Student> getStudents(){
return studentService.getStudents();
}
}
Para devolver um estudante por ID será necessario buscar no serviço o metodo getStudentById() dentro da class Student Service. Ficará assim:
public Student getStudentById(int id){
}
Dentro dele é necessario que seja retornado um objeto do tipo estudante. Caso não seja encontrado retorne uma excessão
return studentRepository.findById(id).orElseThrow(
()-> new EntityNotFoundException("Aluno não cadastrado")
);
Ficando exatamente assim:
@Service
public class StudentService {
@Autowired
private StudentRepository studentRepository;
public List<Student>getStudents(){
return studentRepository.findAll();
}
public Student getStudentById(int id){
return studentRepository.findById(id).orElseThrow(
()-> new EntityNotFoundException("Aluno não cadastrado")
);
}
}
Volte para o Controlador e crie um mapeamento que recupera o estudante pelo seu ID. Mas precisa ter uma anotação: @GetMapping("{id}").
Crie um metodo com o pathvariable para pegar o valor do serviço irá para a variavel id.
@GetMapping("{id}")
public Student getStudentById(@PathVariable int id){
}
Dentro dela retorna getStudentById(id) através do serviço. Ficando assim:
@GetMapping("{id}")
public Student getStudentById(@PathVariable int id){
return studentService.getStudentById(id);
}
Para remover um estudante também será necessario criar um metodo dento da classe Controladora sem um retorno:
public void deleteStudentByID(int id){
}
Dentro dele é necessario buscar o id, removendo do banco e criar uma excessão caso não seja encontrado. Ficando exatamente assim:
public void deleteStudentByID(int id){
if(this.studentRepository.existsById(id)){
this.studentRepository.deleteById(id);
}
else{
throw new EntityNotFoundException("Aluno não cadastrado")/
}
}
Volte para o Controlador e crie um mapeamento sem retorno que busque o estudante pelo seu ID. Mas precisa ter a anotação de remoção: @DeleteMapping("{id}").
Crie um metodo com o pathvariable para pegar o valor do serviço irá para a variavel id.
@DeleteMapping("{id}")
public void deleteStudentById(@PathVariable int id){
}
Dentro dela crie sem retorno o deleteStudentById(id) através do serviço. Ficando assim:
@DeleteMapping("{id}")
public void deleteStudentById(@PathVariable int id){
this.studentService.deleteStudentById(id);
}
Para inserir um estudante será necessario criar um metodo save dento da classe Controladora:
public Student save(Student student){
}
Dentro dele insere um novo objeto do tipo estudante com o metodo save. Ficando exatamente assim:
public Student save(Student student){
return this.studentRepository.save(student);
}
}
Volte para o controlador e insira uma anotação PostMapping e insira o metodo save do tipo estudante, retornando o próprio estudante:
@PostMapping
public Student save(Student student){
return this.studentService.save(student);
}
O HTTP Tem o Header e Body tanto para o request quanto o response. A anotação RequestBody pega o body do request e coloca dentro do objeto estudante.
@PostMapping
public Student save(@RequestBody Student student){
return this.studentService.save(student);
}
Para atualizar um estudante será necessario criar um metodo nulo dento da classe Controladora que busque pelo id e o objeto do tipo estudante:
public void updateStudentById(int id, Student student){
}
Crie um try catch para tentar executar algo, caso não seja possivel retorna a excessão. Ficando assim:
public void updateStudentById(int id, Student student){
try {
} catch (Exception e) {
// TODO: handle exception
}
}
Agora precisamos da referencia. Vamos criar um objeto auxiliar que irá buscar o nome e curso do estudante. Dentro do try iremos tentar capiturar o dado do estudante, caso não seja possivel retorna uma excessão. Ficando assim:
public void updateStudentById(int id, Student student){
try {
Student aux = studentRepository.getReferenceById(id);
aux.setCourse(student.getCourse());
aux.setName(student.getName());
this.studentRepository.save(aux);
} catch (Exception e) {
throw new EntityNotFoundException("Aluno não encontrado");
}
}
próxima aula iremos inserir o put dentor do controlador para atualizar os dados do aluno.
Volte para o controlador e insira uma anotação PutMapping e insira o metodo update sem retorno do tipo estudante, retornando o updateStudentByID, ficando exatamente assim:
@PutMapping("{id}")
public void update(@PathVariable int id, @RequestBody Student student){
this.studentService.updateStudentById(id, student);
}
Para cada chamada para um end point deverá devolver um status code do HTTP correto. Iremos aplicar para cada dos metodos do controlador extamente esses status.
Para o GetStudents necessita um ResponseEntity em uma lista de objetos Student retornando um ResponseEntity Ok para o metodo getStudents. Atualizaremos o getStudents para o seguinte metodo:
@GetMapping
public ResponseEntity<List<Student>> getStudents(){
return ResponseEntity.ok(studentService.getStudents());
}
Para o GetStudents necessita um ResponseEntity do objeto Student retornando um ResponseEntity Ok para o metodo getStudentById. Atualizaremos o getStudentById para o seguinte metodo:
@GetMapping("{id}")
public ResponseEntity<Student> getStudentById(@PathVariable int id){
return ResponseEntity.ok(studentService.getStudentById(id));
}
Para o GetStudents necessita um ResponseEntity do objeto nulo retornando um ResponseEntity noContent e o build por se tratar de não possuir um retorno do objeto. Atualizaremos o deleteSudentById para o seguinte metodo:
@DeleteMapping("{id}")
public ResponseEntity<Void> deleteStudentById(@PathVariable int id){
this.studentService.deleteStudentById(id);
return ResponseEntity.noContent().build();
}
Para o GetStudents necessita um ResponseEntity do objeto Student retornando um ResponseEntity Ok para o metodo save. Atualizaremos o save para o seguinte metodo:
@PostMapping
public ResponseEntity<Student> save(@RequestBody Student student){
return ResponseEntity.ok(studentService.save(student));
}
Da mesma forma que o metodo deleteStudentById o update não possui retorno do objeto. Nesse caso tambem aplicamos um objeto retornando um ResponseEntity noContent e um build também. Atualizaremos o update para o seguinte método:
@PutMapping("{id}")
public ResponseEntity<Void> update(@PathVariable int id, @RequestBody Student student){
this.studentService.updateStudentById(id, student);
return ResponseEntity.noContent().build();
}
Iremos criar uma classe global para capitar uma das diversas excessões. Crie uma pasta dentro de resources chamada exceptions. Crie uma classe chamada ResourceExceptionHandler. A anotação que iremos usar é @ControllerAdvice, que captiura uma excessões lançadas que serão tratadas. No nosso caso a excessão que iremos tratar é a EntityNotFoundException.
Crie um metodo que trate uma entre diversas excessões e quando acontecer retorne a excessão 404 not found. Ficando assim:
@ControllerAdvice
public class ResourceExceptionHandler {
public ResponseEntity<Void> entityNotFoundException(){
return ResponseEntity.notFound().build();
}
}
Crie uma anotação do exception Handler para indicar qual excessão será tratada, no caso EntityNotFoundException.
@ExceptionHandler(EntityNotFoundException.class)
Para pegar a chamada do request teremos que incluir dentro do metodo um entityNotFoundException e um HttpServletRequest. Ficando assim~no geral:
@ControllerAdvice
public class ResourceExceptionHandler {
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<StandardError> entityNotFoundException(
EntityNotFoundException e,
HttpServletRequest request
){
return ResponseEntity.notFound().build();
}
}
Crie uma classa StandardError quando for o 404. Iremos guardar o instante em que o erro aconteceu, o seu status, o numero do erro, a mensagem e seu endereço 'path'. Ficando assim:
public class StandardError {
private Instant timeStamp;
private Integer status;
private String error;
private String message;
private String path;
}
Crie para esses atributos o Getters e Setters para todos. Detalhe é importar o pacote resources. Ficando a classe completa assim:
package com.fatec.student.resources.exceptions;
import java.time.Instant;
public class StandardError {
private Instant timeStamp;
private Integer status;
private String error;
private String message;
private String path;
public Instant getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(Instant timeStamp) {
this.timeStamp = timeStamp;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
Agora devemos vincular a estrutura da classe dentro de ResourceExceptionHandler para cada campo do erro dentro da classe. Volte para a lcasse ResourceExceptionHandler e adicione os erros para cada atributo da classe:
error.setStatus(HttpStatus.NOT_FOUND.value());
error.setError("Resource Not Found");
error.setMessage(e.getMessage());
error.setTimeStamp(Instant.now());
error.setPath(request.getRequestURI());
Altere o retorno da classe ResorceExceptionHandler com o status da excessão e seu devido erro. Ficando a classe completa exatamente assim:
@ControllerAdvice
public class ResourceExceptionHandler {
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<StandardError> entityNotFoundException(
EntityNotFoundException e,
HttpServletRequest request) {
StandardError error = new StandardError();
error.setStatus(HttpStatus.NOT_FOUND.value());
error.setError("Resource Not Found");
error.setMessage(e.getMessage());
error.setTimeStamp(Instant.now());
error.setPath(request.getRequestURI());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
}
Padrão de transferencia de objetos, a entidade deverá ter domínio entre o serviço e o controlador. As DTO's se aplicam tanto para o request e o response dentro da arquitetura do objeto. Esse mecanismo isola o serviço do controlador fazendo com que o banco de dados não acesse a entidade.
Dentro da pasta student crie uma pasta chamada dto. Iremos criar um record ao invés de uma classe, tornando-os registros. Eles são imutaveis e após sua criação será inalteravel. Crie um StudentResponse.java dentro da pasta. Nela será invés de class será record. Inserimos os parametros id, name e course. Ficando extamente assim assim:
package com.fatec.student.dto;
public record StudentResponse(
int id,
String name,
String course) {
}
Voltamos para camada controller e aplicaremos o DTO para os responses. Na lista que retorna todos os estudantes substituiremos Student por StudentResponse, ficando exatamente assim: Para buscar todos o estudantes:
@GetMapping
public ResponseEntity<List<StudentResponse>> getStudents(){
return ResponseEntity.ok(studentService.getStudents());
}
Para buscar o estudante pelo Id:
@GetMapping("{id}")
public ResponseEntity<StudentResponse> getStudentById(@PathVariable int id){
return ResponseEntity.ok(studentService.getStudentById(id));
}
Para salvar mais um estudante:
@PostMapping
public ResponseEntity<StudentResponse> save(@RequestBody Student student){
StudentResponse newStudent = this.studentService.save(student);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/id")
.buildAndExpand(newStudent.id())
.toUri();
return ResponseEntity.created(location).body(newStudent);
}
Para mapear a lista dos objetos estudantes para a lista do student response. Crie uma pasta mappers dentro da pasta student. Crie uma classe StudantMapper. Dentro dessa pasta cria uma classe StudentMapper. Ele irá mapear a classe student através do metodo get de cada atributo da classe. O nome do metodo estatico chamado toDTO. Serão o getId, getName, getCourse. Não se esqueça de sempre importar o pacote da classe. Ficando exatamente assim:
package com.fatec.student.mappers;
public class StudentMapper{
public static StudentResponse toDTO(Student student){
return new StudentResponse(
student.getId(),
student.getName(),
student.getCourse()
)
}
}
Volte para o StudentService e ao invés de devolver uma lista de estudante, devolva um StudentResponse. Mas para retornar uma lista do tipo StudnetResponse deveremos mudar dentro do metodo. Para que cada objeto de uma lista seja retornavel, ao invés de aplicarmos um for, simplicaremos a estrutura utilizando funções lambda em java. Iremos usar Lambda no retorno da lista de estudantes dentro do metodo studantResponse. O metodo stream() será nossa função lambda. Ela devera mapear a classe StudnetMapper, convertendo a coleção para dentro da lista.
Para buscar todos os estudantes utilizando lambda:
public List<StudentResponse>getStudents(){
List<Student> students = studentRepository.findAll();
return students.stream()
.map( s -> StudentMapper.toDTO(s))
.collect(Collectors.toList());
}
Para buscar o estudante pelo id:
public StudentResponse getStudentById(int id){
Student student = studentRepository.findById(id).orElseThrow(
()-> new EntityNotFoundException("Aluno não cadastrado")
);
return StudentMapper.toDTO(student);
}
Para salvar um novo estudante:
public StudentResponse save(Student student){
return StudentMapper.toDTO(this.studentRepository.save(student));
}
Para mapear a lista dos objetos estudantes para a lista do student request. Crie um record dentro da pasta dto. A estrutura será identica ao do Response passando o paramentro Name e course. Ficando assim:
package com.fatec.student.dto;
public record StudentRequest(
String name,
String course) {
}
Voltamos para camada controller e aplicaremos o DTO para os request. No save que salva o novo estudante substituiremos o parametro do metodo de Student por StudentRequest. No metodo save:
@PostMapping
public ResponseEntity<StudentResponse> save(@RequestBody StudentRequest student){
StudentResponse newStudent = this.studentService.save(student);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/id")
.buildAndExpand(newStudent.id())
.toUri();
return ResponseEntity.created(location).body(newStudent);
}
No metodo update:
@PutMapping("{id}")
public ResponseEntity<Void> update(@PathVariable int id, @RequestBody StudentRequest student){
this.studentService.updateStudentById(id, student);
return ResponseEntity.ok().build();
}
Assim como aplicamos um ModelMapper para os Response, deveremos aplicar para o Request. Aplicamos para o nome e o curso do estudante. Ficando exatamente assim:
public static Student toEntity(StudentRequest request){
Student student = new Student();
student.setName(request.name());
student.setCourse(request.course());
return student;
}
No service substitua o objeto Student por StudentRequest. No metodo save:
public StudentResponse save(StudentRequest request){
Student student = StudentMapper.toEntity(request);
return StudentMapper.toDTO(this.studentRepository.save(student));
}
No metodo updateStudentById:
public void updateStudentById(int id, StudentRequest student){
try {
Student aux = studentRepository.getReferenceById(id);
aux.setCourse(student.course());
aux.setName(student.name());
this.studentRepository.save(aux);
} catch (Exception e) {
throw new EntityNotFoundException("Aluno não encontrado");
}
}
Precisameos desabilitar o JPA de acessar a camada de view Controller. Dentro do application.properties deixamos
spring.jpa.open-in-view=false
Para inserimos as validações no Back-End é necessario adicionar um dependência no documento pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Vá para SudentRequest para inserir uma anotação para ser feita a validação.
Null - Quando não passa um parametro Blank - Quando passa um parametro vazio entre aspas "". Para o atributo Name insira a anotação:
@NotNull(message = "Nome não pode ser nulo")
String name,
Para o atributo Course insira a anotação:
@NotBlank(message = "Curso não pode ser nulo")
String course
Adicione a anotação @Validated para o save e update. Ficando assim:
@PostMapping
public ResponseEntity<StudentResponse> save(@Validated @RequestBody StudentRequest student)
@PutMapping("{id}")
public ResponseEntity<Void> update(@Validated @PathVariable int id, @RequestBody StudentRequest student)
Acesse o entities Student. A constraint que você deverá anotar será dentro da anotação @Column:
@Column(length = 40, nullable = false)
Crie um arquivo dentro de exceptions com o nome da classe ValidationErrors.java Essa classe será herdada de StadardError adicionando mais elementos dentro dele. Detnro dessa classe terá uma lista com todos os itens do erro encontrado. Iremos criar um metodo para adicionar um novo erro de validação e um metodo para buscar o erro. Ficando exatamente assim:
package com.fatec.student.resources.exceptions;
import java.util.ArrayList;
import java.util.List;
public class ValidationErrors extends StandardError{
private List<String> errors = new ArrayList<>();
public void addError(String errors){
this.errors.add(errors);
}
public List<String> getErrors(){
return this.errors;
}
}
Para tratarmos o erro correto devemos criar um novo metodo validationException. Ele terá parametros parecidos com o metodo já criado entityNotFoundException. Fincando exatamente assim:
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationErrors> validationException(
MethodArgumentNotValidException exception,
HttpServletRequest request) {
ValidationErrors error = new ValidationErrors();
error.setStatus(HttpStatus.UNPROCESSABLE_ENTITY.value());
error.setError("Validation Error");
error.setMessage(exception.getMessage());
error.setTimeStamp(Instant.now());
error.setPath(request.getRequestURI());
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(error);
}
Ao copilar deveremos incluir a descrição do erro. Para isso inserimos um metodo exception dentro do metodo validationException:
exception.getBindingResult()
.getFieldErrors()
.forEach(e->error.addError(e.getDefaultMessage()));
Dentro de studentController adicione a anotação @