Skip to content

Spring Boot demo comparing JPA lazy loading (N+1 problem) vs EntityGraph optimization. Shows performance difference between 11 queries vs 1 query for Books-Authors many-to-many relationship. Includes query counting, tests, and metrics.

Notifications You must be signed in to change notification settings

Torkov24/entity-graph

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Entity Graph vs Lazy Loading: N+1 Query Problem Demonstration

Spring Boot Java H2 Database

A comprehensive Spring Boot application demonstrating the N+1 query problem in JPA/Hibernate and how to solve it using EntityGraph optimization. This project provides a practical, hands-on comparison between lazy loading and EntityGraph approaches with real-time query counting and detailed logging.

🎯 Purpose

This project serves as a proof of concept to demonstrate:

  • The N+1 query problem that occurs with lazy loading
  • How EntityGraph can solve this performance issue
  • Real-time SQL query counting and monitoring
  • Best practices for handling many-to-many relationships in JPA

πŸ“‹ Table of Contents

πŸš€ Features

Core Functionality

  • N+1 Query Problem Demonstration: Shows how lazy loading can cause performance issues
  • EntityGraph Optimization: Demonstrates the solution using JPA EntityGraph
  • Real-time Query Counting: Custom StatementInspector tracks every SQL query
  • Automated Testing: Comprehensive test suite validating query performance
  • REST API: Endpoints to test both approaches via HTTP requests

Technical Features

  • Many-to-Many Relationships: Book ↔ Author entities with proper JPA mapping
  • DTO Pattern: Clean data transfer objects for API responses
  • SQL Logging: Detailed SQL query logging with formatting
  • H2 Console: In-memory database with web console for debugging
  • YAML Configuration: Clean, hierarchical configuration structure

πŸ—οΈ Architecture

Entity Relationship Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Author    β”‚         β”‚   book_authors  β”‚         β”‚    Book     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€         β”‚  (join table)   β”‚         β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ id (PK)     │◄───────── author_id (FK)  β”‚         β”‚ id (PK)     β”‚
β”‚ name        β”‚         β”‚ book_id (FK)    │────────►│ title       β”‚
β”‚ email       β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚ isbn        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Application Layers

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    REST Layer                           β”‚
β”‚  BookController - Exposes /lazy and /entity-graph APIs β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  Service Layer                          β”‚
β”‚   BookService - Business logic and query strategies    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                Repository Layer                         β”‚
β”‚  BookRepository - Data access with EntityGraph support β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 Persistence Layer                       β”‚
β”‚     JPA/Hibernate with H2 In-Memory Database           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸš€ Quick Start

Prerequisites

  • Java 21 or higher
  • Maven 3.6+
  • IDE (IntelliJ IDEA, Eclipse, or VS Code)

Installation & Running

  1. Clone the repository

    git clone <repository-url>
    cd entity-graph
  2. Run the application

    mvn spring-boot:run
  3. Access the application

    • Application: http://localhost:8080
    • H2 Console: http://localhost:8080/h2-console
      • JDBC URL: jdbc:h2:mem:testdb
      • Username: sa
      • Password: (empty)

What Happens on Startup

  1. Database Creation: H2 creates tables automatically
  2. Data Population: SQL script inserts 20 authors and 10 books
  3. Demo Execution: N+1 query demonstration runs automatically
  4. Query Logging: All SQL queries are logged with counts

🌐 API Endpoints

Books API

Method Endpoint Description
GET /api/books/lazy Fetch books using lazy loading (N+1 problem)
GET /api/books/entity-graph Fetch books using EntityGraph (optimized)

Example Usage

Test Lazy Loading (N+1 Problem):

curl -X GET http://localhost:8080/api/books/lazy

Test EntityGraph (Optimized):

curl -X GET http://localhost:8080/api/books/entity-graph

Response Format

[
  {
    "id": 1,
    "title": "The Enchanted Forest",
    "isbn": "ISBN-978-0-100000-X",
    "authors": [
      {
        "id": 1,
        "name": "J.K. Rowling",
        "email": "jkrowling@literature.com"
      }
    ]
  }
]

πŸ—„οΈ Database Schema

Sample Data

The application automatically populates the database with:

Authors (20 total)

  • J.K. Rowling, Stephen King, George R.R. Martin
  • Agatha Christie, J.R.R. Tolkien, Isaac Asimov
  • And 14 more famous authors...

Books (10 total)

Book Title Authors Count Sample Authors
The Enchanted Forest 1 J.K. Rowling
Mystery of the Lost Key 2 Stephen King, George R.R. Martin
Dragons and Legends 3 Agatha Christie, J.R.R. Tolkien, Isaac Asimov
The Secret Garden Chronicles 4 Arthur Conan Doyle, Jane Austen, Mark Twain, Charles Dickens

⚑ Query Comparison

Lazy Loading (N+1 Problem)

-- Initial query to fetch all books
SELECT b.id, b.title, b.isbn FROM books b

-- Then N additional queries (one per book to fetch authors)
SELECT a.id, a.name, a.email FROM authors a 
JOIN book_authors ba ON a.id = ba.author_id 
WHERE ba.book_id = 1

SELECT a.id, a.name, a.email FROM authors a 
JOIN book_authors ba ON a.id = ba.author_id 
WHERE ba.book_id = 2

-- ... 8 more similar queries

Total Queries: 11 (1 + 10)

EntityGraph (Optimized)

-- Single query with JOIN to fetch everything
SELECT b.id, ba.book_id, a.id, a.email, a.name, b.isbn, b.title 
FROM books b 
LEFT JOIN book_authors ba ON b.id = ba.book_id 
LEFT JOIN authors a ON a.id = ba.author_id

Total Queries: 1

Performance Impact

  • Query Reduction: 90% fewer queries (11 β†’ 1)
  • Network Round-trips: Significant reduction
  • Database Load: Dramatically improved
  • Response Time: Faster for larger datasets

πŸ› οΈ Technologies Used

Core Framework

  • Spring Boot 3.5.6: Main application framework
  • Spring Data JPA: Data persistence and repository layer
  • Hibernate 6.6+: ORM implementation

Database

  • H2 Database: In-memory database for development and testing
  • Flyway/SQL Scripts: Database initialization

Development Tools

  • Lombok: Reduces boilerplate code
  • Maven: Build and dependency management
  • SLF4J + Logback: Logging framework

Testing

  • JUnit 5: Unit and integration testing
  • Spring Boot Test: Testing Spring components

πŸ“ Project Structure

src/
β”œβ”€β”€ main/
β”‚   β”œβ”€β”€ java/com/example/entitygraph/
β”‚   β”‚   β”œβ”€β”€ controller/
β”‚   β”‚   β”‚   └── BookController.java              # REST endpoints
β”‚   β”‚   β”œβ”€β”€ dto/
β”‚   β”‚   β”‚   β”œβ”€β”€ AuthorDto.java                   # Author data transfer object
β”‚   β”‚   β”‚   └── BookDto.java                     # Book data transfer object
β”‚   β”‚   β”œβ”€β”€ entity/
β”‚   β”‚   β”‚   β”œβ”€β”€ Author.java                      # Author JPA entity
β”‚   β”‚   β”‚   └── Book.java                        # Book JPA entity with EntityGraph
β”‚   β”‚   β”œβ”€β”€ repository/
β”‚   β”‚   β”‚   β”œβ”€β”€ AuthorRepository.java            # Author data access
β”‚   β”‚   β”‚   └── BookRepository.java              # Book data access with EntityGraph
β”‚   β”‚   β”œβ”€β”€ service/
β”‚   β”‚   β”‚   └── BookService.java                 # Business logic interface
β”‚   β”‚   β”œβ”€β”€ util/
β”‚   β”‚   β”‚   β”œβ”€β”€ QueryCounter.java                # SQL query counter
β”‚   β”‚   β”‚   └── CountingStatementInspector.java  # Hibernate query interceptor
β”‚   β”‚   β”œβ”€β”€ demo/
β”‚   β”‚   β”‚   └── N1QueryDemoRunner.java           # Automated demonstration
β”‚   β”‚   └── EntityGraphApplication.java          # Main application class
β”‚   └── resources/
β”‚       β”œβ”€β”€ application.yaml                     # Application configuration
β”‚       └── data.sql                             # Database initialization script
└── test/
    └── java/com/example/entitygraph/
        └── service/
            └── BookServiceTest.java             # Service layer tests

βš™οΈ Configuration

Application Configuration (application.yaml)

spring:
  application:
    name: entity-graph
  
  # Database Configuration
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password:
  
  # JPA Configuration
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        use_sql_comments: true
        session_factory:
          statement_inspector: com.example.entitygraph.util.CountingStatementInspector
  
  # SQL Initialization
  sql:
    init:
      mode: always

# Logging Configuration
logging:
  level:
    org.hibernate.SQL: DEBUG
    com.example.entitygraph: DEBUG

Key Configuration Features

  • Query Logging: All SQL queries are logged with formatting
  • Statement Inspector: Custom interceptor counts every query
  • Auto-initialization: Database populated on startup
  • H2 Console: Enabled for development debugging

πŸ§ͺ Testing

Running Tests

# Run all tests
mvn test

# Run specific test class
mvn test -Dtest=BookServiceTest

# Run with detailed logging
mvn test -Dspring.profiles.active=test

Test Coverage

  • N+1 Problem Validation: Verifies lazy loading causes multiple queries
  • EntityGraph Optimization: Confirms single query execution
  • Query Count Assertions: Validates exact number of SQL queries
  • Data Integrity: Ensures correct data mapping and relationships

Sample Test Output

========================================
TESTING LAZY LOADING (N+1 PROBLEM)
========================================
Books fetched: 10
Total queries executed: 11
Expected: 11 queries (1 for books + 10 for authors)

========================================
TESTING ENTITY GRAPH (OPTIMIZED)
========================================
Books fetched: 10
Total queries executed: 1
Expected: 1 query (books with authors in single join)

πŸ“Š Performance Analysis

Query Execution Patterns

Scenario Books Count Queries (Lazy) Queries (EntityGraph) Improvement
10 books 10 11 1 90.9%
50 books 50 51 1 98.0%
100 books 100 101 1 99.0%

When to Use Each Approach

Use Lazy Loading When:

  • You rarely need related entities
  • Working with single entities
  • Memory constraints are critical

Use EntityGraph When:

  • You always need related entities
  • Working with collections
  • Performance is critical
  • Reducing database round-trips

πŸ” Monitoring & Debugging

Query Monitoring

The application includes a custom CountingStatementInspector that:

  • Intercepts every SQL query
  • Assigns sequential numbers to queries
  • Logs queries with detailed formatting
  • Provides real-time query counting

H2 Console

Access the H2 database console at http://localhost:8080/h2-console:

  • Browse tables and data
  • Execute custom SQL queries
  • Verify data relationships
  • Debug query execution

Log Analysis

Monitor application logs to see:

SQL Query #1: SELECT b.id, b.title, b.isbn FROM books b
SQL Query #2: SELECT a.id, a.name FROM authors a WHERE ...
SQL Query #3: SELECT a.id, a.name FROM authors a WHERE ...

πŸ“š Additional Resources

About

Spring Boot demo comparing JPA lazy loading (N+1 problem) vs EntityGraph optimization. Shows performance difference between 11 queries vs 1 query for Books-Authors many-to-many relationship. Includes query counting, tests, and metrics.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages