Este projeto é um exemplo completo de API RESTful desenvolvida com Spring Boot, JPA/Hibernate, PostgreSQL e Swagger (OpenAPI 3), seguindo uma arquitetura limpa e organizada em camadas:
Controller → Service → Repository → Model.
- 📂 Estrutura do Projeto
- ⚙️ Tecnologias Utilizadas
- 🐘 Configurando o Banco de Dados com Docker
- 🧩 Dependências Principais
- ⚙️ Configuração do Spring Boot
- 🧠 Arquitetura e Código-Fonte
- 🧾 Endpoints da API
- 🧭 Acessando o Swagger UI
- 🐞 Tratamento de Concorrência com @Version
- 💡 Melhorias Futuras
- 📜 Licença
com.test.crud_auto ├─ controller │ └─ UserController.java ├─ dto │ └─ UserDTO.java ├─ model │ └─ UserEntity.java ├─ repository │ └─ UserRepository.java ├─ service │ └─ UserService.java └─ CrudAutoApplication.java
| Tecnologia | Função |
|---|---|
| Java 21+ | Linguagem principal |
| Spring Boot 3 | Framework principal |
| Spring Data JPA | Mapeamento ORM |
| PostgreSQL 16 | Banco de dados |
| Docker | Contêinerização do banco |
| Swagger / Springdoc OpenAPI | Documentação automática da API |
| Hibernate @Version | Controle de concorrência otimista |
Execute os comandos abaixo no terminal para preparar o ambiente de banco:
# Passo 1: criar uma rede Docker isolada docker network create backend-net # Passo 2: subir Postgres + pgAdmin na mesma rede docker run -d \ --name postgress-test \ --network backend-net \ -p 5432:5432 \ -e POSTGRES_USER=admin \ -e POSTGRES_PASSWORD=admin123 \ -e POSTGRES_DB=mydb \ postgres:16 docker run -d \ --name pgadmin \ --network backend-net \ -p 5050:80 \ -e PGADMIN_DEFAULT_EMAIL=admin@admin.com \ -e PGADMIN_DEFAULT_PASSWORD=admin123 \ dpage/pgadmin4
Após subir os containers, o pgAdmin estará acessível em: http://localhost:5050 Login:
admin@admin.comSenha:admin123
<dependencies> <!-- Web + JPA + PostgreSQL --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency> <!-- Swagger / OpenAPI --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.8.13</version> </dependency> </dependencies>
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb spring.datasource.username=admin spring.datasource.password=admin123 spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=update spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect # Swagger springdoc.api-docs.path=/api-docs springdoc.swagger-ui.path=/swagger-ui.html
@Entity @Table(name = "users") public class UserEntity { @Id @GeneratedValue private UUID id; @Column(nullable = false) private String name; @Column(nullable = false) private int age; @Version @Column(nullable = false) private Long version = 0L; public UserEntity() {} public UserEntity(String name, int age) { this.name = name; this.age = age; } public UserDTO toDTO() { return new UserDTO(id, name, age); } }
public record UserDTO(UUID id, String name, int age) {}
public interface UserRepository extends JpaRepository<UserEntity, UUID> {}
@Service public class UserService { private final UserRepository repo; public UserService(UserRepository repo) { this.repo = repo; } @Transactional public UserDTO create(String name, int age) { return repo.save(new UserEntity(name, age)).toDTO(); } @Transactional(readOnly = true) public List<UserDTO> list(int page, int size) { Pageable pageable = PageRequest.of(page, size, Sort.by("id").descending()); return repo.findAll(pageable).map(UserEntity::toDTO).toList(); } @Transactional public UserDTO update(UUID id, String name, int age) { UserEntity user = repo.findById(id) .orElseThrow(() -> new EntityNotFoundException("Usuário não encontrado")); user.setName(name); user.setAge(age); return repo.save(user).toDTO(); } @Transactional public void delete(UUID id) { if (!repo.existsById(id)) throw new EntityNotFoundException("Usuário não encontrado"); repo.deleteById(id); } }
@RestController @RequestMapping("/api/users") @Tag(name = "Usuários", description = "Gerenciamento de usuários") public class UserController { private final UserService service; public UserController(UserService service) { this.service = service; } @Operation(summary = "Cria um novo usuário") @PostMapping public ResponseEntity<UserDTO> create(@RequestBody UserDTO req) { return ResponseEntity.status(HttpStatus.CREATED) .body(service.create(req.name(), req.age())); } @Operation(summary = "Lista usuários paginados") @GetMapping public ResponseEntity<List<UserDTO>> list( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "50") int size) { return ResponseEntity.ok(service.list(page, size)); } @Operation(summary = "Atualiza um usuário existente") @PutMapping("/{id}") public ResponseEntity<UserDTO> update(@PathVariable UUID id, @RequestBody UserDTO req) { return ResponseEntity.ok(service.update(id, req.name(), req.age())); } @Operation(summary = "Remove um usuário pelo ID") @DeleteMapping("/{id}") public ResponseEntity<Void> delete(@PathVariable UUID id) { service.delete(id); return ResponseEntity.noContent().build(); } }
| Método | Endpoint | Descrição |
|---|---|---|
POST |
/api/users |
Cria um novo usuário |
GET |
/api/users |
Lista usuários paginados |
PUT |
/api/users/{id} |
Atualiza usuário existente |
DELETE |
/api/users/{id} |
Remove um usuário |
Após iniciar a aplicação, acesse:
http://localhost:8080/swagger-ui.html
A documentação interativa será exibida automaticamente com todos os endpoints da API.
O campo @Version na entidade protege contra updates simultâneos.
Se dois usuários tentarem salvar a mesma entidade ao mesmo tempo, o segundo update causará uma exceção:
org.hibernate.StaleObjectStateException
Essa abordagem evita perda de dados e inconsistências silenciosas.