Spring Boot + JPA + Hibernate: Database Made Easy

boot.cloud
Boot to Cloud
Published on Mar, 30 2026 3 min read 0 comments
image

In Week 3, we built REST APIs with controllers, DTOs, validation, and CRUD operations.
Now, in Week 4, it’s time to connect your APIs to a real database using Spring Data JPA and Hibernate, and understand entity relationships, queries, and performance best practices.

What is JPA and Hibernate?

JPA (Java Persistence API) is a standard interface for working with relational databases in Java.
Hibernate is the most popular JPA implementation, providing extra features like caching, lazy loading, and advanced queries.

Think of JPA as the contract and Hibernate as the implementation.

Why Use Spring Data JPA?

Spring Data JPA simplifies database access by:

  • Auto-generating queries
  • Handling boilerplate CRUD code
  • Supporting pagination and sorting
  • Integrating seamlessly with Spring Boot

Step 1: Add Dependencies

In your pom.xml:

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

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- Optional: MySQL/PostgreSQL -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

Start with H2 (in-memory DB) for development. Switch to MySQL/PostgreSQL for production.

Step 2: Configure Database

application.yml example:

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

ddl-auto: update automatically creates tables based on your entities. Use validate in production.

Step 3: Create Entities & Relationships

User Entity

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String email;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<Order> orders = new ArrayList<>();

    // Getters & Setters
}

Order Entity

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String product;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    // Getters & Setters
}

Relationships Explained

| Type       | Use Case                      |
| ---------- | ----------------------------- |
| OneToOne   | User profile, unique relation |
| OneToMany  | User → Orders                 |
| ManyToMany | Student → Course              |

Step 4: Repository Layer

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

Why this is powerful:

  • Spring auto-generates queries
  • Supports custom query methods based on naming conventions

Example:

List<User> findByNameContaining(String name);

Step 5: CRUD with Service Layer

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public User saveUser(User user) {
        return userRepository.save(user);
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    public Optional<User> getUserById(Long id) {
        return userRepository.findById(id);
    }

    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

Following Service → Repository → Controller keeps your architecture clean and scalable.

Step 6: Pagination & Sorting

@GetMapping
public Page<User> getUsers(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size
) {
    Pageable pageable = PageRequest.of(page, size, Sort.by("name").ascending());
    return userRepository.findAll(pageable);
}

Benefits:

  • Handles large datasets efficiently
  • Professional APIs always implement pagination

Step 7: JPQL vs Native Queries

JPQL (Object-Oriented Query)

@Query("SELECT u FROM User u WHERE u.name LIKE %:name%")
List<User> searchByName(@Param("name") String name);

Native SQL

@Query(value = "SELECT * FROM users WHERE name LIKE %:name%", nativeQuery = true)
List<User> searchByNameNative(@Param("name") String name);

Use JPQL for portability; Native SQL for complex or DB-specific queries.

Step 8: Performance Tips

  • Use fetch = FetchType.LAZY for large relations
  • Add indexes for frequently searched columns
  • Avoid N+1 queries (use JOIN FETCH)
  • Enable query caching if needed

Example:

@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
@Cacheable
private List<Order> orders;

Week 4 Recap

✅ JPA + Hibernate basics
✅ Entity relationships
✅ Repository layer
✅ CRUD services
✅ Pagination & sorting
✅ JPQL vs Native queries
✅ Performance best practices

What’s Next? (Week 5)

👉 Spring Boot Security: JWT Authentication Step-by-Step

We’ll cover:

  • Spring Security architecture
  • JWT login flow
  • Role-based access
  • Password hashing
  • Protecting REST APIs

📌 Action Items

✔ Create User + Order entities
✔ Implement OneToMany relationship
✔ Test CRUD operations in Postman
✔ Push code to GitHub

0 Comments