In this tutorial, you’ll learn how to build a Spring Boot CRUD (Create, Read, Update, Delete) application using the H2 in-memory database. We will use Java record
for the DTO (Data Transfer Object) and follow best practices by keeping the conversion logic in the service layer.
What You’ll Learn:
- Setting up a Spring Boot project with H2.
- Configuring Spring Boot to use H2 as an in-memory database.
- Implementing CRUD operations with Spring Data JPA.
- Using Java
record
as DTOs for transferring data. - Testing RESTful APIs using Postman.
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 the 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: To interact with the H2 database using JPA (Java Persistence API).
- H2 Database: An in-memory database to store data.
- Spring Boot DevTools: For hot reloading during development.
- Click Generate to download the project, extract the zip file, and open it in your IDE.
Explanation:
Spring Initializr helps generate a pre-configured Spring Boot project with all the necessary dependencies, so you don’t have to manually configure each one. We will use H2, which is an in-memory database, to simplify our setup and avoid the need for an external database server.
Step 2: Configuring H2 Database
2.1 Configure application.properties
In the src/main/resources/application.properties
file, configure Spring Boot to use H2 as the database:
# 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
# JPA configurations
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
Explanation:
- spring.datasource.url: Specifies the JDBC URL to use an in-memory H2 database named
testdb
. - spring.h2.console.enabled=true: Enables the H2 web console, which allows you to visually access and manage the database at
http://localhost:8080/h2-console
. - spring.jpa.hibernate.ddl-auto=update: Updates the database schema automatically based on the entity mappings.
Step 3: Creating the Employee Entity
3.1 Create the Employee Entity
In the model
package, create a Java class named Employee
to represent the employee entity in the database.
package com.example.springbooth2crud.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String email;
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
Explanation:
- @Entity: Marks this class as a JPA entity, meaning it will be mapped to a table in the H2 database.
- @Id and @GeneratedValue: The
id
field is the primary key, and its value is auto-generated.
Step 4: Creating the Repository
4.1 Create EmployeeRepository
In the repository
package, create an interface EmployeeRepository
that extends JpaRepository
:
package com.example.springbooth2crud.repository;
import com.example.springbooth2crud.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
Explanation:
- JpaRepository: Provides all the necessary CRUD methods like
save()
,findAll()
,findById()
, anddeleteById()
without needing to write custom SQL. - @Repository: Marks this interface as a Spring repository, which allows Spring to find and use it.
Step 5: Using Java record for DTO
5.1 Create the EmployeeDTO Class
In the model
package, create a Java record
called EmployeeDTO
:
package com.example.springbooth2crud.model;
public record EmployeeDTO(Long id, String firstName, String lastName, String email) {}
Explanation:
- Java
record
: A feature introduced in Java 14 (finalized in Java 16) that reduces boilerplate code. It automatically generates the constructor, getters,toString()
,equals()
, andhashCode()
methods.
Step 6: Creating the Service Layer
6.1 Create EmployeeService Interface
In the service
package, define an interface EmployeeService
:
package com.example.springbooth2crud.service;
import com.example.springbooth2crud.model.EmployeeDTO;
import java.util.List;
import java.util.Optional;
public interface EmployeeService {
List<EmployeeDTO> getAllEmployees();
Optional<EmployeeDTO> getEmployeeById(Long id);
EmployeeDTO saveEmployee(EmployeeDTO employeeDTO);
EmployeeDTO updateEmployee(Long id, EmployeeDTO employeeDTO);
void deleteEmployee(Long id);
}
6.2 Implement EmployeeService in EmployeeServiceImpl
In the service implementation, add the conversion logic between Employee (entity) and EmployeeDTO (DTO):
package com.example.springbooth2crud.service;
import com.example.springbooth2crud.model.Employee;
import com.example.springbooth2crud.model.EmployeeDTO;
import com.example.springbooth2crud.repository.EmployeeRepository;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class EmployeeServiceImpl implements EmployeeService {
private final EmployeeRepository employeeRepository;
public EmployeeServiceImpl(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
@Override
public List<EmployeeDTO> getAllEmployees() {
return employeeRepository.findAll().stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
@Override
public Optional<EmployeeDTO> getEmployeeById(Long id) {
return employeeRepository.findById(id).map(this::convertToDTO);
}
@Override
public EmployeeDTO saveEmployee(EmployeeDTO employeeDTO) {
Employee employee = convertToEntity(employeeDTO);
Employee savedEmployee = employeeRepository.save(employee);
return convertToDTO(savedEmployee);
}
@Override
public EmployeeDTO updateEmployee(Long id, EmployeeDTO employeeDTO) {
Employee employee = employeeRepository.findById(id).orElseThrow();
employee.setFirstName(employeeDTO.firstName());
employee.setLastName(employeeDTO.lastName());
employee.setEmail(employeeDTO.email());
Employee updatedEmployee = employeeRepository.save(employee);
return convertToDTO(updatedEmployee);
}
@Override
public void deleteEmployee(Long id) {
employeeRepository.deleteById(id);
}
// Convert Employee Entity to EmployeeDTO
private EmployeeDTO convertToDTO(Employee employee) {
return new EmployeeDTO(employee.getId(), employee.getFirstName(), employee.getLastName(), employee.getEmail());
}
// Convert EmployeeDTO to Employee Entity
private Employee convertToEntity(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
employee.setFirstName(employeeDTO.firstName());
employee.setLastName(employeeDTO.lastName());
employee.setEmail(employeeDTO.email());
return employee;
}
}
Explanation:
- Conversion logic between DTO and Entity is kept in the service layer to ensure the controller only handles HTTP requests and responses, following best practices.
- convertToDTO: Converts an
Employee
entity into anEmployeeDTO
. - convertToEntity:
Converts an EmployeeDTO
into an Employee
entity.
Step 7: Creating the REST Controller
In the controller
package, create the EmployeeController
to expose REST APIs:
package com.example.springbooth2crud.controller;
import com.example.springbooth2crud.model.EmployeeDTO;
import com.example.springbooth2crud.service.EmployeeService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
private final EmployeeService employeeService;
public EmployeeController(EmployeeService employeeService) {
this.employeeService = employeeService;
}
@GetMapping
public List<EmployeeDTO> getAllEmployees() {
return employeeService.getAllEmployees();
}
@GetMapping("/{id}")
public ResponseEntity<EmployeeDTO> getEmployeeById(@PathVariable Long id) {
Optional<EmployeeDTO> employee = employeeService.getEmployeeById(id);
return employee.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping
public EmployeeDTO createEmployee(@RequestBody EmployeeDTO employeeDTO) {
return employeeService.saveEmployee(employeeDTO);
}
@PutMapping("/{id}")
public ResponseEntity<EmployeeDTO> updateEmployee(@PathVariable Long id, @RequestBody EmployeeDTO employeeDTO) {
try {
EmployeeDTO updatedEmployee = employeeService.updateEmployee(id, employeeDTO);
return ResponseEntity.ok(updatedEmployee);
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteEmployee(@PathVariable Long id) {
employeeService.deleteEmployee(id);
return ResponseEntity.noContent().build();
}
}
Explanation:
- This controller provides the REST API endpoints for CRUD operations.
- The controller communicates with the service layer, ensuring a clean separation of concerns.
Step 8: Running and Testing the Application
8.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
8.2 Testing with Postman
You can test the REST APIs using Postman:
- GET all employees:
- URL:
http://localhost:8080/api/employees
- Response: A list of employee DTOs.
- URL:
- GET employee by ID:
- URL:
http://localhost:8080/api/employees/{id}
- Response: The employee data for the given ID.
- URL:
- POST create a new employee:
- URL:
http://localhost:8080/api/employees
- Body:
{ "firstName": "John", "lastName": "Doe", "email": "john.doe@example.com" }
- URL:
- PUT update an existing employee:
- URL:
http://localhost:8080/api/employees/{id}
- Body:
{ "firstName": "John", "lastName": "Doe", "email": "john.doe@newemail.com" }
- URL:
- DELETE an employee:
- URL:
http://localhost:8080/api/employees/{id}
- Response: No content (HTTP 204).
- URL:
Conclusion
In this tutorial, we built a Spring Boot CRUD REST API with an H2 in-memory database. We followed best practices by using Java record
as the DTO and keeping the conversion logic between the entity and DTO in the service layer. By using H2, we simplified our database setup and made the project easy to run without requiring external dependencies.
This guide provides a comprehensive approach to building a Spring Boot CRUD application with H2 and is suitable for anyone looking for a lightweight, easy-to-run setup.