Tutorial Series Part 3: Persistence Layer: JPA & Hibernate Integration

Date: January 21, 2025


Welcome back! So far, you’ve set up your Spring Boot project and designed your REST APIs with DTOs and controllers. Now, it’s time to connect your application to a real database using JPA and Hibernate for persistence.


What You’ll Learn in This Part

  • Configuring Spring Boot with JPA and Hibernate
  • Creating entity classes and repositories
  • Mapping between DTOs and entities
  • Performing CRUD operations backed by the database
  • Simple configuration of an in-memory database (H2) for testing

Step 1: Add JPA and Database Dependencies

Update your pom.xml or build.gradle to include:

<!-- Maven example -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- H2 in-memory database -->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

Step 2: Configure the Database

In src/main/resources/application.properties, add:

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true

This sets up an in-memory H2 database with an accessible web console at /h2-console.


Step 3: Define the Entity

Replace the previous simple User model with a JPA entity that maps to a table.

package com.example.demoapp.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String email;

    // Constructors, getters and setters
}

Step 4: Create the Repository Interface

Spring Data JPA will generate implementation at runtime.

package com.example.demoapp.repository;

import com.example.demoapp.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    // Optional: Custom queries can go here
}

Step 5: Service Layer to Handle Business Logic

Create a service to decouple controller from repository:

package com.example.demoapp.service;

import com.example.demoapp.dto.UserDTO;
import com.example.demoapp.model.User;
import com.example.demoapp.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    // Convert entity to DTO
    private UserDTO convertToDTO(User user) {
        UserDTO dto = new UserDTO();
        dto.setId(user.getId());
        dto.setName(user.getName());
        dto.setEmail(user.getEmail());
        return dto;
    }

    // Convert DTO to entity
    private User convertToEntity(UserDTO dto) {
        User user = new User();
        user.setId(dto.getId());
        user.setName(dto.getName());
        user.setEmail(dto.getEmail());
        return user;
    }

    public UserDTO createUser(UserDTO dto) {
        User user = convertToEntity(dto);
        User saved = userRepository.save(user);
        return convertToDTO(saved);
    }

    public UserDTO getUserById(Long id) {
        return userRepository.findById(id)
                .map(this::convertToDTO)
                .orElse(null);
    }

    public List<UserDTO> getAllUsers() {
        return userRepository.findAll().stream()
                .map(this::convertToDTO)
                .collect(Collectors.toList());
    }

    public UserDTO updateUser(Long id, UserDTO dto) {
        return userRepository.findById(id).map(user -> {
            user.setName(dto.getName());
            user.setEmail(dto.getEmail());
            User updated = userRepository.save(user);
            return convertToDTO(updated);
        }).orElse(null);
    }

    public boolean deleteUser(Long id) {
        if (!userRepository.existsById(id)) {
            return false;
        }
        userRepository.deleteById(id);
        return true;
    }
}

Step 6: Update the Controller to Use the Service

package com.example.demoapp.controller;

import com.example.demoapp.dto.UserDTO;
import com.example.demoapp.service.UserService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public ResponseEntity<UserDTO> createUser(@Valid @RequestBody UserDTO userDto) {
        UserDTO created = userService.createUser(userDto);
        return new ResponseEntity<>(created, HttpStatus.CREATED);
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        UserDTO user = userService.getUserById(id);
        if (user == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        return ResponseEntity.ok(user);
    }

    @GetMapping
    public List<UserDTO> getAllUsers() {
        return userService.getAllUsers();
    }

    @PutMapping("/{id}")
    public ResponseEntity<UserDTO> updateUser(@PathVariable Long id, @Valid @RequestBody UserDTO userDto) {
        UserDTO updated = userService.updateUser(id, userDto);
        if (updated == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        return ResponseEntity.ok(updated);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        boolean deleted = userService.deleteUser(id);
        if (!deleted) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        return ResponseEntity.noContent().build();
    }
}

Step 7: Run and Test

The data will persist in-memory during runtime, and Hibernate will automatically create and update tables.


Summary

In this part, we have:

  • Configured Spring Boot for JPA with Hibernate
  • Created a database-backed User entity
  • Built a repository and service layer for clean separation of concerns
  • Updated controller to use persistence services

Next Up

In Part 4, we will secure the app using Spring Security to protect your APIs and manage authentication/authorization.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *