- Marcos Aurelio
Este é um projeto de exemplo em Java utilizando Spring Boot que demonstra um conjunto básico de funcionalidades REST e persistência com Spring Data JPA.
- Principais modelos:
Usuario,Cargo,Post. - Implementa endpoints CRUD via controllers REST (
UsuarioController,CargoController,PostController). - Utiliza um banco H2 em memória para desenvolvimento (configurado em
src/main/resources/application.properties). - As repositories estendem
JpaRepositorypara operações de persistência (ex.:UsuarioRepository,CargoRepository,PostRepository).
O objetivo é servir como referência para criar uma API REST simples com validações básicas (por exemplo: unicidade de email, name, title) e relacionamentos JPA (ManyToMany, ManyToOne).
- Certifique-se de ter Java instalado e configurado (variável
JAVA_HOME). - Use o Maven wrapper para executar a aplicação:
./mvnw spring-boot:run- Acesse a API em
http://localhost:8080e o console H2 emhttp://localhost:8080/h2-console(verapplication.properties).
Este projeto usa o H2 (in-memory) por padrão para desenvolvimento. A configuração principal está em src/main/resources/application.properties e inclui os seguintes pontos importantes:
spring.datasource.url— string de conexão do H2 (ex.:jdbc:h2:mem:exemplo).spring.h2.console.enabledespring.h2.console.path— ativam e definem o path do console web do H2 (ex.:/h2-console).spring.sql.init.mode— controla se os scripts SQL (schema.sql,data.sql) serão executados automaticamente na inicialização. No projeto está definido comoalways.spring.jpa.hibernate.ddl-auto— controla a criação/atualização do schema via JPA (ex.:update,create,create-drop,none).spring.jpa.defer-datasource-initialization— quandotrue, atrasa a execução dos scripts SQL até que o JPA esteja pronto (útil quando se usa JPA + scripts SQL juntos).
Existem duas formas principais de inicializar o schema e dados do H2 neste projeto:
- Inicializando usando
schema.sqledata.sql(scripts SQL)
-
Coloque arquivos
schema.sqledata.sqlemsrc/main/resources. O Spring Boot executará esses arquivos na inicialização quandospring.sql.init.mode=always. -
Exemplo prático (files já incluídos neste repositório):
src/main/resources/schema.sqlcontém as definições de tabela e chaves estrangeiras (DDL).src/main/resources/data.sqlinsere dados iniciais (DML).
-
Vantagens: total controle sobre DDL/DML; ideal para seeds e exemplos específicos.
-
Cuidado: se usar JPA auto-generation (ex.:
spring.jpa.hibernate.ddl-auto=update), pode haver conflito de criação/alteração de tabelas. Usespring.jpa.defer-datasource-initialization=truepara forçar a ordem correta: JPA prepara-se e os scripts são executados depois.
- Gerar tabelas a partir das entidades (JPA / Hibernate)
-
A propriedade
spring.jpa.hibernate.ddl-autodefine o comportamento. Valores comuns:none— não altera o schemavalidate— checa se o schema é compatível (sem alterações)update— atualiza o schema para refletir entidades JPA (útil em dev)create— cria o schema na inicialização (apaga dados previos)create-drop— cria o schema na inicialização e apaga-o na parada
-
Uso sugerido em desenvolvimento:
updateoucreatepara testar rapidamente. Em produção, prefiravalidateounone+ migrações gerenciadas por ferramentas como Flyway ou Liquibase.
Trocar entre as abordagens
- Se quiser que apenas o JPA gere as tabelas, altere
spring.sql.init.modeparanevere usespring.jpa.hibernate.ddl-auto=update(oucreate). - Se quiser usar apenas os scripts SQL (
schema.sql/data.sql) e não oddl-auto, definaspring.jpa.hibernate.ddl-auto=nonepara evitar alterações pelo Hibernate.
Exemplos rápidos:
- H2 in-memory com scripts SQL (padrão neste projeto):
spring.datasource.url=jdbc:h2:mem:exemplo;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.sql.init.mode=always
spring.jpa.defer-datasource-initialization=true
spring.jpa.hibernate.ddl-auto=none
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console- H2 file-based persistente e JPA gerando schema:
spring.datasource.url=jdbc:h2:./data/exemplo;AUTO_SERVER=TRUE
spring.sql.init.mode=never
spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true
spring.h2.console.path=/h2-consoleComo acessar o H2 Console
- Acesse
http://localhost:8080/h2-consolee informe a JDBC URL usada (ex.:jdbc:h2:mem:exemplo). Se estiver usando memória, o nome do DB deve ser o mesmo configurado emspring.datasource.url.
Observações e boas práticas
spring.jpa.hibernate.ddl-auto=updatepode ser conveniente para desenvolvimento, mas pode causar mudanças inesperadas no schema em ambientes maiores; evite em produção.- Prefira usar scripts SQL e/ou ferramentas de migração (Flyway/Liquibase) para garantir controle total do schema em produção.
Para criar um novo modelo (entidade JPA) neste projeto, siga estes passos e convenções:
-
Localização: Crie a classe em
src/main/java/com/example/exemplo/Model/. Exemplo:MeuModelo.java. -
Estrutura básica:
- Use
@Entitypara marcar a classe como entidade JPA. - Use
@Table(name = "nome_da_tabela")para definir o nome da tabela no banco (opcional, mas recomendado). - Defina um campo
idcomo chave primária:@Id@GeneratedValue(strategy = GenerationType.IDENTITY)para auto-incremento.
- Campos simples: use tipos primitivos ou wrappers (ex.:
String,Long,LocalDateTime). - Use
@Columnpara customizar colunas (ex.:nullable = false,unique = true,columnDefinition).
- Use
-
Relacionamentos:
@ManyToOne: para relacionamentos muitos-para-um (ex.: um Post pertence a um Usuario).@ManyToMany: para relacionamentos muitos-para-muitos (ex.: Usuario tem muitos Cargos).- Use
@JoinColumnpara especificar a coluna de junção. - Para ManyToMany bidirecional, use
mappedByno lado inverso.
JPA suporta vários tipos de relacionamentos entre entidades. Aqui estão os principais, com exemplos baseados nos modelos existentes (Usuario, Cargo, Post):
-
Many-to-One (Muitos-para-Um):
- Um lado "muitos" aponta para um lado "um".
- Exemplo: Muitos
Posts pertencem a umUsuario. - Anotações:
@ManyToOneno lado "muitos",@JoinColumnpara a chave estrangeira. - Exemplo em
Post.java:@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "usuario_id") private Usuario author;
- No lado "um" (
Usuario), não precisa de anotação específica se for unidirecional.
-
One-to-Many (Um-para-Muitos):
- O inverso de Many-to-One: um lado "um" tem uma coleção do lado "muitos".
- Exemplo: Um
Usuariotem muitosPosts. - Anotações:
@OneToManycommappedByno lado "um". - Exemplo em
Usuario.java(se adicionado):@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true) private List<Post> posts = new ArrayList<>();
mappedByrefere-se ao campo no lado oposto (authoremPost).
-
Many-to-Many (Muitos-para-Muitos):
- Ambos os lados podem ter coleções do outro.
- Exemplo:
Usuarios têm muitosCargos, eCargos têm muitosUsuarios. - Anotações:
@ManyToMany,@JoinTableno lado proprietário (semmappedBy). - Exemplo em
Usuario.java:@ManyToMany @JoinTable( name = "usuario_cargos", joinColumns = @JoinColumn(name = "usuario_id"), inverseJoinColumns = @JoinColumn(name = "cargo_id") ) private Set<Cargo> cargos = new HashSet<>();
- No lado inverso (
Cargo.java):@ManyToMany(mappedBy = "cargos") private Set<Usuario> usuarios = new HashSet<>();
Dicas para relacionamentos:
- Bidirecional vs Unidirecional: Bidirecional permite navegação de ambos os lados (use
mappedBy). Unidirecional é mais simples, mas limita acesso. - Fetch: Use
FetchType.LAZYpara carregamento preguiçoso (padrão para @ManyToOne e @ManyToMany);EAGERcarrega imediatamente. - Cascade: Define operações em cascata (ex.:
CascadeType.ALLpara propagar saves/deletes). - Orphan Removal: Para @OneToMany, remove filhos órfãos automaticamente.
- Sempre teste relacionamentos no H2 Console para verificar tabelas de junção e chaves estrangeiras.
Para criar um novo Controller REST neste projeto, siga estes passos para implementar endpoints CRUD básicos:
-
Localização: Crie a classe em
src/main/java/com/example/exemplo/Controller/. Exemplo:MeuController.java. -
Estrutura básica:
- Use
@RestControllerpara marcar a classe como controller REST (combina@Controllere@ResponseBody). - Use
@RequestMapping("/recurso")para definir o caminho base (ex.:/usuarios). - Injete o Repository correspondente com
@Autowired.
- Use
-
Mapeamento de rotas:
@GetMappingpara leitura (listar ou buscar).@PostMappingpara criação.@PutMapping("/{id}")para atualização.@DeleteMapping("/{id}")para exclusão.- Use
@PathVariablepara capturar IDs na URL. - Use
@RequestBodypara receber dados no corpo da requisição.
-
Operações básicas:
- Listar todos: Retorne
List<Entidade>ouResponseEntity<List<Entidade>>. - Buscar por ID: Use
repository.findById(id)e retorneResponseEntity.ok()ounotFound(). - Criar: Salve com
repository.save(entidade)e retorneResponseEntity.created(location).body(saved). - Atualizar: Busque, atualize campos, salve e retorne
ResponseEntity.ok(saved). - Deletar: Verifique existência, delete e retorne
ResponseEntity.noContent().
- Listar todos: Retorne
Exemplo completo (ProdutoController.java, assumindo um modelo Produto e ProdutoRepository):
package com.example.exemplo.Controller;
import com.example.exemplo.Model.Produto;
import com.example.exemplo.Model.Repository.ProdutoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.net.URI;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/produtos")
public class ProdutoController {
@Autowired
private ProdutoRepository produtoRepository;
@GetMapping
public List<Produto> list() {
return produtoRepository.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<Produto> read(@PathVariable Long id) {
Optional<Produto> produto = produtoRepository.findById(id);
return produto.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Produto> create(@RequestBody Produto produto) {
Produto saved = produtoRepository.save(produto);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(saved.getId())
.toUri();
return ResponseEntity.created(location).body(saved);
}
@PutMapping("/{id}")
public ResponseEntity<Produto> update(@PathVariable Long id, @RequestBody Produto details) {
Optional<Produto> opt = produtoRepository.findById(id);
if (opt.isEmpty()) {
return ResponseEntity.notFound().build();
}
Produto produto = opt.get();
// Atualize campos conforme necessário
if (details.getNome() != null) {
produto.setNome(details.getNome());
}
if (details.getPreco() != null) {
produto.setPreco(details.getPreco());
}
Produto saved = produtoRepository.save(produto);
return ResponseEntity.ok(saved);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
Optional<Produto> opt = produtoRepository.findById(id);
if (opt.isEmpty()) {
return ResponseEntity.notFound().build();
}
produtoRepository.deleteById(id);
return ResponseEntity.noContent().build();
}
}Dicas:
- Use
ResponseEntitypara controle fino de status HTTP (ex.: 404 para não encontrado, 201 para criado). - Adicione validações (ex.:
@Validno@RequestBody) e tratamento de erros com@ExceptionHandler. - Para relacionamentos, valide referências (ex.: verificar se ID de entidade relacionada existe).
- Teste endpoints com ferramentas como Postman ou curl após implementar.
-
Timestamps:
- Para campos de criação e atualização automática, use:
@CreationTimestampparacreatedAt.@UpdateTimestampparaupdatedAt.
- Importe de
org.hibernate.annotations.
- Para campos de criação e atualização automática, use:
-
Construtores e métodos:
- Crie um construtor vazio (obrigatório para JPA).
- Crie construtores sobrecarregados para facilitar criação.
- Gere getters e setters para todos os campos (use IDE ou Lombok para automatizar).
Exemplo de modelo simples (Produto.java):
package com.example.exemplo.Model;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
@Entity
@Table(name = "produtos")
public class Produto {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "nome", nullable = false)
private String nome;
@Column(name = "preco", nullable = false)
private Double preco;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "categoria_id")
private Categoria categoria; // Assumindo uma entidade Categoria
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
public Produto() {}
public Produto(String nome, Double preco, Categoria categoria) {
this.nome = nome;
this.preco = preco;
this.categoria = categoria;
}
// Getters e setters...
}Após criar o modelo, crie uma interface Repository correspondente em src/main/java/com/example/exemplo/Model/Repository/ (ex.: ProdutoRepository extends JpaRepository<Produto, Long>), e opcionalmente um Controller em src/main/java/com/example/exemplo/Controller/ seguindo o padrão dos existentes.
Dicas:
- Sempre teste a entidade executando a aplicação e verificando se as tabelas são criadas corretamente no H2 Console.
- Use validações adicionais com Bean Validation (ex.:
@NotBlank,@Email) se necessário. - Para relacionamentos complexos, consulte a documentação JPA/Hibernate.
Aqui estão links para as documentações oficiais das tecnologias e técnicas utilizadas neste projeto:
- Spring Boot: Framework para criação de aplicações Java standalone. Documentação oficial.
- Spring Data JPA: Abstração para acesso a dados com JPA. Documentação oficial.
- Spring Web (REST Controllers): Para construção de APIs REST. Documentação oficial.
- Hibernate ORM: Implementação JPA usada pelo Spring Data. Documentação oficial.
- H2 Database: Banco de dados em memória para desenvolvimento. Documentação oficial.
- Maven: Ferramenta de build e gerenciamento de dependências. Documentação oficial.
- Java Persistence API (JPA): Especificação para mapeamento objeto-relacional. Documentação oficial.
Para tutoriais e guias adicionais: