Anotações do curso Spring Boot, JPA e Hibernate — Parte 2
Published: 2020-10-15 23:48:47

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/msht1f9mgookq115rz7k.jpeg)

Continuação do artigo da semana passa sobre Spring Boot, caso não tenha visto a parte 1.

__Métodos HTTP__
Nas classes SERVICE, nós vamos criar os métodos de busca e inserção que vamos utilizar em nosso programa. Como exemplo, vou utilizar a classe AlunoService. Onde tiver // serão os comentários explicando o que cada parte de código está fazendo.

// Vamos começar chamando a classe AlunoRepository como um atributo da classe, que faz a transação com o banco de dados
@Autowired
private AlunoRepository alunoRepository;
// Aqui temos nosso primeiro método, que busca todos os elementos da Classe. Aqui ele chama a classe do repositório e a função findAll(), retornando então a lista com os resultados
public List<Aluno> findAll() {
return this.alunoRepository.findAll();
}

// Este método busca um aluno, pelo id dele. Recebe como parâmetro o ID e retorna a chamada da classe repositório, que chama a função findById(), que busca um elemento pelo id. A função orElse é chamado caso não encontre nenhum aluno pelo id escolhido.
public Aluno getOne(int id) {
return alunoRepository.findById(id).orElse( new Aluno() );
}

// Este método vai salvar um ano no banco. O retorno é a chamada da classe repositório, que chama o método save(), passando como parâmetro o aluno.
public Aluno save(Aluno aluno) {
return this.alunoRepository.save(aluno);
}

// Este método é um pouco mais complexo, ele vai atualizar os dados de um Aluno, através do ID dele. O Optional<> vai encapsular o retorno de métodos e informar se um valor do tipo <T> está presente ou ausente, no nosso programa, ele vai verificar se consegue encontrar o aluno, pelo ID que foi passado. Depois, a variável update recebe um valor null e temos um if, que verifica se o ID foi encontrado. Em caso negativo, ele retorna o update como nulo (ou seja, nada é alterado). Em caso positivo, o update vai chamar o método get(), que pega os dados que estão sendo passados na requisição. Depois, o update chama os métodos setNome() e setRA(), que vão salvar os novos valores dos atributos do aluno que foram alterados. Por fim, o update vai chamar a classe aluno repositor, que por sua vez chama o métodos save(), passando os valores salvos como parâmetro.
public Aluno update(int id, Aluno aluno) {
Optional<Aluno> a = this.alunoRepository.findById(id);
Aluno update = null;
if(a.isPresent()) {
update = a.get();
update.setNome(aluno.getNome());
update.setRa(aluno.getRa());
update = this.alunoRepository.save(update);
}
return update;
}

// Vamos deletar um registro de Aluno, utilizando um método sem retorno e recebendo um ID como parâmetro. Neste método, basta o atributo classe repositório chamar a função deleteById(), passando um id como parâmetro.
public void delete(int id) {
this.alunoRepository.deleteById(id);
}

// Este método realiza a busca de um Aluno, recebendo como parâmetro o nome do mesmo. Ela irá retornar o atributo classe repositório, que chama o método findByNome(), o qual recebe o nome como parâmetro.
public List<Aluno> findByNome(String nome) {
return this.alunoRepository.findByNome(nome);
}

// Este método é similar ao anterior, mas realiza a busca de um Aluno que o parâmetro nome contenha um “pedaço” do nome, ou seja, é uma função de busca que não precisa buscar pelo parâmetro todo, tornando-a mais abrangentes em determinadas situações. Ela irá retornar o atributo classe repositório, que chama o método findByNome(), o qual recebe o nome como parâmetro. O método irá retornar o atributo classe repositório, que chama o método findByNomeContains(), o qual recebe o nome como parâmetro.
public List<Aluno> findByNomeContains(String nome) {
return this.alunoRepository.findByNomeContains(nome);
}

//Método para exibir os resultados em páginas, evitando trazer uma quantidade muito alta de registros de uma vez (para projetos com grande volume de dados). Passamos como parâmetro a quantidade de páginas que queremos exibir e a qtd de linhas por página. Depois, importamos a classe PageRequest que trabalha com isso e criamos uma nova variável, que recebe a quantidade de páginas e linhas. Por fim, realizamos a busca dos dados.
public Page<Produto> paginacao(int pagina, int linhas) {
PageRequest pageRequest = PageRequest.of(pagina, linhas);
return this.produtoRepository.findAll(pageRequest);
}

Nas classes CONTROLLER, nós vamos utilizar os métodos de busca (que criamos nos SERVICE) para se comunicar com o banco de dados. Como exemplo, vou utilizar a classe AlunoController.
// Vamos começar chamando a classe AlunoService como sendo um atributo da nossa classe.
@Autowired
private AlunoService alunoService;
// Aqui vamos trazer a lista de todos os alunos. Vamos começar usando a anotação @GetMapping, passando como value(url) o caminho alunos. O método tem como retorno a chamada de alunoService, que chama a função findAll().
@GetMapping(value = “alunos”)
public List<Aluno> getAll() {
return this.alunoService.findAll();
}

// Vamos usar novamente a anotação @GetMapping, passando como value(url) o caminho alunos especificando o ID. O argumento terá a anotação @PathVariable que recebe um id. O método tem como retorno a chamada de alunoService, que chama a função getOne(), passando o id.
@GetMapping(value=”alunos/{id}”)
public Aluno getOne(@PathVariable int id){
return this.alunoService.getOne(id);
}

// Este método criará novos registros em nosso banco de dados. Utilizaremos a anotação @PostMapping (passando o caminho em value). O método terá a anotação @RequestBody, que pedirá o corpo da requisição (onde será passado os atributos e valores dos alunos). O retorno é o atributo alunoService, que chama nosso método save(), passando os dados do aluno que inserimos
@PostMapping(value=”alunos”)
public Aluno create(@RequestBody Aluno aluno) {
return this.alunoService.save(aluno);
}

// Este método atualizará os registros de um aluno em nosso banco de dados. Utilizaremos a anotação @PatchtMapping (passando o caminho em value, com o ID). O método terá a anotação @RequestBody, que pedirá o corpo da requisição (onde será passado os atributos e valores dos alunos) e o @PathVariable. O retorno é o atributo alunoService, que chama nosso método update(), passando o ID e os dados do aluno.
@PatchMapping(value=”alunos/{id}”)
public Aluno update(@RequestBody Aluno aluno, @PathVariable int id) {
return this.alunoService.update(id, aluno);
}

// Este método vai apagar os registros de um aluno em nosso banco de dados. Utilizaremos a anotação @DeletetMapping (passando o caminho em value, com o ID). O método terá a anotação @PathVariable para o ID. O retorno é o atributo alunoService, que chama nosso método delete(), passando o ID do aluno.
@DeleteMapping(value=”alunos/{id}”)
public void delete(@PathVariable int id) {
this.alunoService.delete(id);
}

// Este método vai buscar um aluno pelo nome. Utilizaremos a anotação @GetMapping (passando o caminho em value, com o parâmetro nome). O método terá a anotação @PathVariable, que recebe o nome. O retorno é o atributo alunoService, que chama nosso método findByNome(), passando o nome.
@GetMapping (value = “alunos/search/{nome}”)
public List<Aluno> search(@PathVariable String nome) {
return this.alunoService.findByNome(nome);
}

// Este método vai buscar um aluno pelo nome. Utilizaremos a anotação @GetMapping (passando o caminho em value, com o parâmetro nome). O método terá a anotação @PathVariable, que recebe o nome. O retorno é o atributo alunoService, que chama nosso método findByNome(), passando o nome.
@GetMapping (value = “alunos/searchnew/{nome}”)
public List<Aluno> searchBy(@PathVariable String nome) {
return this.alunoService.findByNomeContains(nome);
}

// Este método vai exibir todos os resultados, dividios em páginas. Utilizaremos a anotação @GetMapping (passando o caminho em value, com o parâmetro nome). O método terá a anotação @RequestParam, que recebe o caminho da pagina e da linha e o valor inicial deles. O retorno será a chama da classe produtoServico, que vai chamar o método de paginacao, recebendo os argumentos de pagina e linhas.
@GetMapping(value = “produtos/paginador”)
public Page<Produto> paginacao(@RequestParam (value = “pagina”, defaultValue = “0”) int pagina, @RequestParam (value = “linhas”, defaultValue = “5”) int linhas){
return this.produtoService.paginacao(pagina, linhas);
}

// OFF-TOPIC: Vou mostrar um exemplo de como seria esta busca pelo navegador. Vamos ter a url completa que chama nosso método (http://localhost:8081/produtos/paginador) e passaremos dois parâmetros em nossa busca. Em “pagina=1” estamos dizendo para exibir a pagina 1 e em “linhas=1” para exibir a busca a partir da primeira linha.
http://localhost:8081/produtos/paginador?pagina=1&linhas=1
ComamandLineRunner e populando dados direto no banco
O Spring Boot fornece duas interfaces (CommandLineRunner e ApplicationRunner) para rodar partes específicas de códigos quando a aplicação for totalmente inicializada. Estas interface são chamadas um pouco antes do run(), uma vez que a SpringApplication é concluído. Aqui vamos utilizar um exemplo para popularmos (inserir) dados no banco de dados sempre que a aplicação iniciar, não mostrarei todas as Classes utilizadas para não ficar muito extenso. Vou utilizar // dentro do código para explicar alguns trechos. Vamos começar declarando os atributos das classes que vamos utilizar (ProdutoRepository, CategoriaRepository e ImagemRepository) com a anotação @Autowired por cima delas (Injeção de Dependências).

Embaixo, nós temos nosso método principal que está rodando o Spring Boot, não vamos mexer nada nele. E, em seguida, teremos nosso método que implementos da interface CommandLineRunner e continuarei a explicação agora dentro do próprio código:

@SpringBootApplication
public class ApicursoApplication implements CommandLineRunner {
@Autowired
private ProdutoRepository produtorepository;
@Autowired
private CategoriaRepository categoriarepository;
@Autowired
private ImagemRepository imagemRepository;
public static void main(String[] args) {
SpringApplication.run(ApicursoApplication.class, args);
}
@Override
public void run(String… args) throws Exception {
// Aqui vamos cadastrar um novo item na tabela de Categorias, instanciando um objeto chamado de cat1. A classe irá chamar o builder() (logo explicarei mais sobre ele) e chamará os métodos com os campos que serão preenchidos. Vamos passar o nome da categoria, sua descrição e chamar o método build() para construir nossa instância do objeto. Depois, chamaremos pelo this o atributo categoriarepositoy, que chama o método save() e passaremos o cat1 como argumento. Faremos a mesma coisa para criar dois outros produtos e um imagem (do produto), o Produto e Imagem são duas outras tabelas do nosso banco e a imagem receberá apenas uma url como parâmetro.
Categoria cat1 = Categoria.builder().nome(“Eletrodomésticos”).descricao(“Eletrodoméstico”).build();
this.categoriarepository.save(cat1);
Produto prod1 = Produto.builder().categoria(cat1).nome(“Geladeira”).descricao(“Geladeira”).preco(2500).build();
this.produtorepository.save(prod1);
Produto prod2 = Produto.builder().categoria(cat1).nome(“Fogão”).descricao(“Fogão”).preco(1000).build();
Imagem img =Imagem.builder().url(“http://localhost:8080").build();
// Este pedaço de código irá unir o que criamos das tabelas produtos e imagem, e, depois, salvá-las em suas próprias tabelas (fará um JOIN entre as tabelas)
img.setProdutos(Arrays.asList(prod2));
prod2.setImagens(Arrays.asList(img));
this.imagemRepository.save(img);
this.produtorepository.save(prod2);
}
}

__Manipulando Tabelas__
A anotação @Builder permite produzir automaticamente o código necessário para instanciar uma classe de maneira mais fácil, como neste exemplo: Person.builder().name(“Adam Savage”) .city(“San Francisco”).build();

Já comentei sobre as anotações @ManyToOne, @ManyToMany, @JoinTable, @JoinColumn anteriormente, aqui estamos referenciando a ligação das tabelas, utilizando as chaves primárias e estrangeiras das mesmas. Utilizando o fetch=FetchType.EAGER, ele irá carregar todos os produtos que estão relacionado a uma determinada categoria (chamado de Eager Loading, do Hibernate).
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name = “categoria_id”)
private Categoria categoria;
@ManyToMany
@JoinTable( name = “produto_imagem”,
joinColumns = @JoinColumn(name=”produto_id”),
inverseJoinColumns = @JoinColumn(name=”imagem_id”)
)
private List<Imagem> imagens;

O @JsonIgnore é usado no nível de campos, para marcar uma propriedade (ou lista de propriedades) que serão ignoradas, ou seja, não vão ser exibidas.
@OneToMany(mappedBy = “categoria”)
@JsonIgnore
private List<Produto> produtos;
// Passando a anotação @Query, nós podemos criar uma busca de dados personalizada. Colocando como parâmetro o nativeQuery como true, é possível passar comandos de busca em SQL puro. O nome do método de busca que estamos criando é o procuraPorNome.
@Query(value = “SELECT * FROM Aluno WHERE nome = :nome”, nativeQuery = true)
public List<Aluno> procuraPorNome(String nome);