In this tutorial, we will build a Spring Boot CRUD (Create, Read, Update, Delete) application using H2 in-memory database. We will handle exceptions at the service layer and use @RestControllerAdvice
for global exception handling.
What You’ll Learn:
- Setting up a Spring Boot project.
- Configuring Spring Boot to use H2 as an in-memory database.
- Implementing CRUD operations for the Todo entity.
- Handling exceptions in the service layer.
- Creating a global exception handler using
@RestControllerAdvice
. - Testing exception handling in REST APIs using Postman.
Quick Overview of @RestControllerAdvice annotation
Prerequisites
Before starting, ensure you have:
- Java Development Kit (JDK) 17 or later
- Apache Maven (for project management)
- IDE (e.g., IntelliJ IDEA, Eclipse, or VS Code)
- Postman (to test APIs)
Step 1: Setting Up the Project
1.1 Create a Spring Boot Project
Open Spring Initializr.
Configure the project metadata:
- Project: Maven
- Language: Java
- Spring Boot Version: Latest (3.x)
- Group:
com.example
- Artifact:
spring-boot-h2-crud
- Java Version: 17 or later
Add the following dependencies:
- Spring Web: For building RESTful web services.
- Spring Data JPA: For interacting with the H2 database.
- H2 Database: In-memory database.
Click Generate to download the project, extract the zip file, and open it in your IDE.
Step 2: Configuring H2 Database
2.1 Configure application.properties
In the src/main/resources/application.properties
file, configure Spring Boot to use H2:
# H2 Database Configuration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update
Explanation:
- spring.datasource.url: Specifies the JDBC URL to use an in-memory H2 database.
- spring.h2.console.enabled=true: Enables the H2 web console for viewing the database at
http://localhost:8080/h2-console
.
Step 3: Creating the Todo Entity
3.1 Create the Todo Entity
In the model
package, create a class named Todo
to represent the entity in the H2 database:
package com.example.springbooth2crud.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.time.LocalDate;
@Entity
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String description;
private String status;
private LocalDate createDate;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public LocalDate getCreateDate() { return createDate; }
public void setCreateDate(LocalDate createDate) { this.createDate = createDate; }
}
Explanation:
- @Entity: Marks this class as a JPA entity that will map to the database table.
- @Id: Specifies the primary key (
id
). - Fields:
title
,description
,status
, andcreateDate
represent the attributes of the Todo entity.
Step 4: Creating the Repository
4.1 Create TodoRepository
In the repository
package, create an interface TodoRepository
that extends JpaRepository
:
package com.example.springbooth2crud.repository;
import com.example.springbooth2crud.model.Todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface TodoRepository extends JpaRepository<Todo, Long> {
}
Explanation:
- JpaRepository: Provides CRUD operations without writing SQL queries.
- @Repository: Marks this interface as a repository to perform database operations.
Step 5: Creating the Service Layer
5.1 Create TodoService Interface
In the service
package, define the TodoService
interface:
package com.example.springbooth2crud.service;
import com.example.springbooth2crud.model.Todo;
import java.util.List;
import java.util.Optional;
public interface TodoService {
List<Todo> getAllTodos();
Optional<Todo> getTodoById(Long id);
Todo saveTodo(Todo todo);
Todo updateTodo(Long id, Todo todo);
void deleteTodoById(Long id);
}
5.2 Implement TodoService in TodoServiceImpl
In the service implementation, we'll also throw exceptions from the service layer:
package com.example.springbooth2crud.service;
import com.example.springbooth2crud.exception.ResourceNotFoundException;
import com.example.springbooth2crud.model.Todo;
import com.example.springbooth2crud.repository.TodoRepository;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class TodoServiceImpl implements TodoService {
private final TodoRepository todoRepository;
public TodoServiceImpl(TodoRepository todoRepository) {
this.todoRepository = todoRepository;
}
@Override
public List<Todo> getAllTodos() {
return todoRepository.findAll();
}
@Override
public Optional<Todo> getTodoById(Long id) {
return todoRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Todo not found with id: " + id));
}
@Override
public Todo saveTodo(Todo todo) {
return todoRepository.save(todo);
}
@Override
public Todo updateTodo(Long id, Todo todoDetails) {
Todo todo = todoRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Todo not found with id: " + id));
todo.setTitle(todoDetails.getTitle());
todo.setDescription(todoDetails.getDescription());
todo.setStatus(todoDetails.getStatus());
todo.setCreateDate(todoDetails.getCreateDate());
return todoRepository.save(todo);
}
@Override
public void deleteTodoById(Long id) {
Todo todo = todoRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Todo not found with id: " + id));
todoRepository.delete(todo);
}
}
Explanation:
- The service layer contains business logic, including CRUD operations and exception handling.
- ResourceNotFoundException is thrown if the requested Todo item does not exist.
Step 6: Defining Custom Exceptions
6.1 Create ResourceNotFoundException
In the exception
package, create a custom exception class:
package com.example.springbooth2crud.exception;
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
Explanation:
- This custom exception is thrown when a resource (like a Todo) is not found in the database.
Step 7: Global Exception Handling with @RestControllerAdvice
7.1 Create GlobalExceptionHandler
In the exception
package, create a global exception handler using @RestControllerAdvice
:
package com.example.springbooth2crud.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Map<String, Object>> handleResourceNotFoundException(ResourceNotFoundException ex) {
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("timestamp", LocalDateTime.now());
errorDetails.put("message", ex.getMessage());
errorDetails.put("status", HttpStatus.NOT_FOUND.value());
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleGlobalException(Exception ex) {
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("timestamp", LocalDateTime.now());
errorDetails.put("message", "An unexpected error occurred");
errorDetails.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Explanation:
@RestControllerAdvice
handles exceptions globally and sends a structured error response withtimestamp
,message
, andstatus
.
Step 8: Creating the REST Controller
8.1 Create TodoController
In the controller
package, create the TodoController
to handle REST API requests:
package com.example.springbooth2crud.controller;
import com.example.springbooth2crud.model.Todo;
import com.example.springbooth2crud.service.TodoService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/todos")
public class TodoController {
private final TodoService todoService;
public TodoController(TodoService todoService) {
this.todoService = todoService;
}
@GetMapping
public List<Todo> getAllTodos() {
return todoService.getAllTodos();
}
@GetMapping("/{id}")
public ResponseEntity<Todo> getTodoById(@PathVariable Long id) {
return ResponseEntity.ok(todoService.getTodoById(id).get());
}
@PostMapping
public ResponseEntity<Todo> createTodo(@RequestBody Todo todo) {
Todo savedTodo = todoService.saveTodo(todo);
return new ResponseEntity<>(savedTodo, HttpStatus.CREATED);
}
@PutMapping("/{id}")
public ResponseEntity<Todo> updateTodo(@PathVariable Long id, @RequestBody Todo todoDetails) {
Todo updatedTodo = todoService.updateTodo(id, todoDetails);
return ResponseEntity.ok(updatedTodo);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTodoById(@PathVariable Long id) {
todoService.deleteTodoById(id);
return ResponseEntity.noContent().build();
}
}
Step 9: Running and Testing the Application
9.1 Running the Application
To run the application, open SpringBootH2CrudApplication.java
and click the Run button in your IDE, or run the following command in the terminal:
./mvnw spring-boot:run
9.2 Testing with Postman
Test the REST APIs using Postman:
GET all todos:
- URL:
http://localhost:8080/api/todos
- Response: A list of todos.
- URL:
GET todo by ID:
- URL:
http://localhost:8080/api/todos/{id}
- Response: Todo details or
404 Not Found
.
- URL:
POST create a new todo:
- URL:
http://localhost:8080/api/todos
- Body:
{ "title": "Learn Spring Boot", "description": "Study Spring Boot CRUD application with H2.", "status": "IN_PROGRESS", "createDate": "2024-09-22" }
- URL:
PUT update a todo:
- URL:
http://localhost:8080/api/todos/{id}
- Body:
{ "title": "Complete Spring Boot Project", "description": "Update the CRUD project with H2 and exception handling.", "status": "COMPLETED", "createDate": "2024-09-22" }
- URL:
DELETE a todo:
- URL:
http://localhost:8080/api/todos/{id}
- Response:
204 No Content
.
- URL:
Conclusion
In this tutorial, we built a Spring Boot CRUD REST API with H2 Database, handled exceptions globally using @RestControllerAdvice
, and tested the endpoints using Postman. We followed best practices by throwing exceptions in the service layer and returning meaningful error messages.
This guide provides a clean and maintainable way to implement a CRUD application with proper exception handling in Spring Boot.
Comments
Post a Comment
Leave Comment