āļ§āļīāļ˜āļĩāđāļāđ‰āļ›āļąāļāļŦāļē N+1 āđƒāļ™ Spring Data JPA āļ›āļĩ 2026: Fetch Join āđāļĨāļ° EntityGraph

āļ„āļđāđˆāļĄāļ·āļ­āļ‰āļšāļąāļšāļŠāļĄāļšāļđāļĢāļ“āđŒāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ•āļĢāļ§āļˆāļˆāļąāļšāđāļĨāļ°āđāļāđ‰āđ„āļ‚āļ›āļąāļāļŦāļē N+1 āđƒāļ™ Spring Data JPA āļ„āļĢāļ­āļšāļ„āļĨāļļāļĄ Fetch join, @EntityGraph, batch fetching āđāļĨāļ°āļāļĨāļĒāļļāļ—āļ˜āđŒāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļāļēāļĢāļ„āļīāļ§āļĢāļĩ

āļāļēāļĢāđāļāđ‰āļ›āļąāļāļŦāļē N+1 āļ”āđ‰āļ§āļĒ Spring Data JPA āđ‚āļ”āļĒāđƒāļŠāđ‰ fetch join āđāļĨāļ° EntityGraph

āļ›āļąāļāļŦāļē N+1 āļ–āļ·āļ­āđ€āļ›āđ‡āļ™āļŦāļ™āļķāđˆāļ‡āđƒāļ™āļāļąāļšāļ”āļąāļāļ”āđ‰āļēāļ™āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļ—āļĩāđˆāļžāļšāļšāđˆāļ­āļĒāļ—āļĩāđˆāļŠāļļāļ”āđƒāļ™ JPA āļ„āļīāļ§āļĢāļĩāļ˜āļĢāļĢāļĄāļ”āļēāļŠāļģāļŦāļĢāļąāļšāļ”āļķāļ‡āļ„āļģāļŠāļąāđˆāļ‡āļ‹āļ·āđ‰āļ­ 100 āļĢāļēāļĒāļāļēāļĢāļ­āļēāļˆāļāļĢāļ°āļ•āļļāđ‰āļ™āđƒāļŦāđ‰āđ€āļāļīāļ”āļ„āļīāļ§āļĢāļĩ SQL āļ–āļķāļ‡ 101 āļ„āļĢāļąāđ‰āļ‡: āļŦāļ™āļķāđˆāļ‡āļ„āļĢāļąāđ‰āļ‡āļŠāļģāļŦāļĢāļąāļšāļ„āļģāļŠāļąāđˆāļ‡āļ‹āļ·āđ‰āļ­ āđāļĨāļ°āļ­āļĩāļāļŦāļ™āļķāđˆāļ‡āļ„āļĢāļąāđ‰āļ‡āļŠāļģāļŦāļĢāļąāļšāļĨāļđāļāļ„āđ‰āļēāļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡āđāļ•āđˆāļĨāļ°āļĢāļēāļĒ āļāļēāļĢāđ€āļžāļīāđˆāļĄāļˆāļģāļ™āļ§āļ™āļ„āļīāļ§āļĢāļĩāļ­āļĒāđˆāļēāļ‡āđ€āļ‡āļĩāļĒāļšāđ† āļ™āļĩāđ‰āļ—āļģāđƒāļŦāđ‰āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļĨāļ”āļĨāļ‡āđāļĨāļ°āļŠāļĢāđ‰āļēāļ‡āļ āļēāļĢāļ°āđƒāļŦāđ‰āļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨ

āļœāļĨāļāļĢāļ°āļ—āļšāļˆāļĢāļīāļ‡āļ‚āļ­āļ‡ N+1

endpoint āļ—āļĩāđˆāļ„āļ·āļ™āļ„āđˆāļēāļšāļ—āļ„āļ§āļēāļĄ 50 āļĢāļēāļĒāļāļēāļĢāļžāļĢāđ‰āļ­āļĄāļœāļđāđ‰āđ€āļ‚āļĩāļĒāļ™āļ­āļēāļˆāļāļĢāļ°āđ‚āļ”āļ”āļˆāļēāļ 10ms āđ€āļ›āđ‡āļ™ 500ms āđ€āļ™āļ·āđˆāļ­āļ‡āļˆāļēāļ N+1 āļāļēāļĢāļ•āļĢāļ§āļˆāļˆāļąāļšāđāļ•āđˆāđ€āļ™āļīāđˆāļ™āđ† āļŠāđˆāļ§āļĒāļ›āđ‰āļ­āļ‡āļāļąāļ™āļ›āļąāļāļŦāļēāļ§āļīāļāļĪāļ•āđƒāļ™āļĢāļ°āļšāļšāđ‚āļ›āļĢāļ”āļąāļāļŠāļąāļ™

āļ—āļģāļ„āļ§āļēāļĄāđ€āļ‚āđ‰āļēāđƒāļˆāļ›āļąāļāļŦāļē N+1 āđƒāļ™ JPA

āļ›āļąāļāļŦāļē N+1 āđ€āļāļīāļ”āļ‚āļķāđ‰āļ™āđ€āļĄāļ·āđˆāļ­ JPA āđ‚āļŦāļĨāļ”āļ„āļ­āļĨāđ€āļĨāļāļŠāļąāļ™āļ‚āļ­āļ‡āđ€āļ­āļ™āļ—āļīāļ•āļĩ āļˆāļēāļāļ™āļąāđ‰āļ™āđ€āļĢāļĩāļĒāļāđƒāļŠāđ‰āļ„āļīāļ§āļĢāļĩāđ€āļžāļīāđˆāļĄāđ€āļ•āļīāļĄāļŠāļģāļŦāļĢāļąāļšāđāļ•āđˆāļĨāļ°āđ€āļ­āļ™āļ—āļīāļ•āļĩāđ€āļžāļ·āđˆāļ­āđ‚āļŦāļĨāļ”āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒāļ‚āļ­āļ‡āļĄāļąāļ™ āļžāļĪāļ•āļīāļāļĢāļĢāļĄāļ™āļĩāđ‰āļĄāļēāļˆāļēāļāļāļēāļĢ lazy loading āļ—āļĩāđˆāđ€āļ›āđ‡āļ™āļ„āđˆāļēāđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™āļ‚āļ­āļ‡āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒ @OneToMany āđāļĨāļ° @ManyToMany

āļĨāļ­āļ‡āļžāļīāļˆāļēāļĢāļ“āļēāđ‚āļĄāđ€āļ”āļĨāļ„āļĨāļēāļŠāļŠāļīāļāļ—āļĩāđˆāļĄāļĩāļ„āļģāļŠāļąāđˆāļ‡āļ‹āļ·āđ‰āļ­āđāļĨāļ°āļĨāļđāļāļ„āđ‰āļē āđāļ•āđˆāļĨāļ°āļ„āļģāļŠāļąāđˆāļ‡āļ‹āļ·āđ‰āļ­āđ€āļ›āđ‡āļ™āļ‚āļ­āļ‡āļĨāļđāļāļ„āđ‰āļēāļŦāļ™āļķāđˆāļ‡āļĢāļēāļĒ āđāļĨāļ°āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒāļ™āļĩāđ‰āđƒāļŠāđ‰ lazy loading āđ€āļ›āđ‡āļ™āļ„āđˆāļēāđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™

Order.javajava
@Entity
@Table(name = "orders")
public class Order {

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

    private String orderNumber;

    private LocalDateTime createdAt;

    // ManyToOne relationship is lazy by default since JPA 2.0
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private Customer customer;

    // OneToMany relationship lazy by default
    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
    private List<OrderItem> items = new ArrayList<>();

    // Getters and setters omitted
}
Customer.javajava
@Entity
@Table(name = "customers")
public class Customer {

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

    private String name;

    private String email;

    // Getters and setters omitted
}

āđ€āļĄāļ·āđˆāļ­āļ„āļīāļ§āļĢāļĩāļ”āļķāļ‡āļ„āļģāļŠāļąāđˆāļ‡āļ‹āļ·āđ‰āļ­āđāļĨāđ‰āļ§āđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļŠāļ·āđˆāļ­āļĨāļđāļāļ„āđ‰āļē Hibernate āļˆāļ°āđ€āļĢāļĩāļĒāļāđƒāļŠāđ‰āļ„āļīāļ§āļĢāļĩāđ€āļžāļīāđˆāļĄāđ€āļ•āļīāļĄāļŠāļģāļŦāļĢāļąāļšāđāļ•āđˆāļĨāļ°āļ„āļģāļŠāļąāđˆāļ‡āļ‹āļ·āđ‰āļ­

OrderService.java - Problematic codejava
@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;

    public List<OrderDto> getAllOrders() {
        // 1 query: SELECT * FROM orders
        List<Order> orders = orderRepository.findAll();

        // For each order, accessing customer triggers a query
        return orders.stream()
            .map(order -> new OrderDto(
                order.getId(),
                order.getOrderNumber(),
                // N queries: SELECT * FROM customers WHERE id = ?
                order.getCustomer().getName()
            ))
            .toList();
    }
}

āļŠāļģāļŦāļĢāļąāļšāļ„āļģāļŠāļąāđˆāļ‡āļ‹āļ·āđ‰āļ­ 100 āļĢāļēāļĒāļāļēāļĢ āđ‚āļ„āđ‰āļ”āļ™āļĩāđ‰āđ€āļĢāļĩāļĒāļāđƒāļŠāđ‰āļ„āļīāļ§āļĢāļĩ SQL 101 āļ„āļĢāļąāđ‰āļ‡ āļšāļąāļ™āļ—āļķāļāļ‚āļ­āļ‡ Hibernate āđ€āļœāļĒāđƒāļŦāđ‰āđ€āļŦāđ‡āļ™āļĢāļđāļ›āđāļšāļšāļ—āļĩāđˆāļ—āļģāļĨāļēāļĒāļĨāđ‰āļēāļ‡āļ™āļĩāđ‰

sql
-- Query 1: fetch orders
SELECT o.id, o.order_number, o.created_at, o.customer_id FROM orders o

-- Queries 2-101: fetch each customer
SELECT c.id, c.name, c.email FROM customers c WHERE c.id = 1
SELECT c.id, c.name, c.email FROM customers c WHERE c.id = 2
SELECT c.id, c.name, c.email FROM customers c WHERE c.id = 3
-- ... 97 more identical queries

āļāļēāļĢāļ•āļĢāļ§āļˆāļˆāļąāļšāļ›āļąāļāļŦāļē N+1 āļ”āđ‰āļ§āļĒāļšāļąāļ™āļ—āļķāļāļ‚āļ­āļ‡ Hibernate

āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āđāļĢāļāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡āļāļąāļšāļāļēāļĢāđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™āļšāļąāļ™āļ—āļķāļ SQL āđ€āļžāļ·āđˆāļ­āļĢāļ°āļšāļļāļ„āļīāļ§āļĢāļĩāļ—āļĩāđˆāļĄāļĩāļ›āļąāļāļŦāļē āļāļēāļĢāļāļģāļŦāļ™āļ”āļ„āđˆāļēāļ•āđˆāļ­āđ„āļ›āļ™āļĩāđ‰āđāļŠāļ”āļ‡āļ—āļļāļāļ„āļīāļ§āļĢāļĩāļ—āļĩāđˆ Hibernate āđ€āļĢāļĩāļĒāļāđƒāļŠāđ‰

yaml
# application.yml
spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        # Format SQL for better readability
        format_sql: true
        # Display session statistics (queries, time)
        generate_statistics: true

logging:
  level:
    # Detailed SQL query logging
    org.hibernate.SQL: DEBUG
    # Display prepared statement parameters
    org.hibernate.orm.jdbc.bind: TRACE

āļŠāļ–āļīāļ•āļīāļ‚āļ­āļ‡ Hibernate āđƒāļŦāđ‰āļŠāļĢāļļāļ›āļ—āļĩāđˆāļĄāļĩāļ„āđˆāļēāđ€āļĄāļ·āđˆāļ­āļŠāļīāđ‰āļ™āļŠāļļāļ”āđāļ•āđˆāļĨāļ°āļ˜āļļāļĢāļāļĢāļĢāļĄ

text
Session Metrics {
    23421 nanoseconds spent acquiring 1 JDBC connection;
    0 nanoseconds spent releasing 0 JDBC connections;
    1254789 nanoseconds spent preparing 101 JDBC statements;
    15478963 nanoseconds spent executing 101 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
}

āļˆāļģāļ™āļ§āļ™ 101 āļ„āļģāļŠāļąāđˆāļ‡ JDBC āļŠāļģāļŦāļĢāļąāļšāļĢāļēāļĒāļāļēāļĢāļ„āļģāļŠāļąāđˆāļ‡āļ‹āļ·āđ‰āļ­āļ˜āļĢāļĢāļĄāļ”āļēāđ€āļ›āđ‡āļ™āļŠāļąāļāļāļēāļ“āļ—āļĩāđˆāļŠāļąāļ”āđ€āļˆāļ™āļ‚āļ­āļ‡āļ›āļąāļāļŦāļē N+1

āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™āđƒāļ™āļĢāļ°āļšāļšāđ‚āļ›āļĢāļ”āļąāļāļŠāļąāļ™

āļšāļąāļ™āļ—āļķāļ SQL āđāļĨāļ°āļŠāļ–āļīāļ•āļīāļŠāđˆāļ‡āļœāļĨāļāļĢāļ°āļ—āļšāļ•āđˆāļ­āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž āļ•āļąāļ§āđ€āļĨāļ·āļ­āļāđ€āļŦāļĨāđˆāļēāļ™āļĩāđ‰āļ„āļ§āļĢāļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™āđƒāļ™āļĢāļ°āļšāļšāđ‚āļ›āļĢāļ”āļąāļāļŠāļąāļ™āđāļĨāļ°āļŠāļ‡āļ§āļ™āđ„āļ§āđ‰āļŠāļģāļŦāļĢāļąāļšāļŠāļ āļēāļžāđāļ§āļ”āļĨāđ‰āļ­āļĄāļāļēāļĢāļžāļąāļ’āļ™āļēāđāļĨāļ°āļāļēāļĢāļ—āļ”āļŠāļ­āļš

āļ§āļīāļ˜āļĩāđāļāđ‰āļ—āļĩāđˆ 1: Fetch Join āļ”āđ‰āļ§āļĒ JPQL

Fetch join āđ‚āļŦāļĨāļ”āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒāđƒāļ™āļ„āļīāļ§āļĢāļĩ SQL āđ€āļ”āļĩāļĒāļ§āļœāđˆāļēāļ™ join āđāļ™āļ§āļ—āļēāļ‡āļ—āļĩāđˆāļŠāļąāļ”āđ€āļˆāļ™āļ™āļĩāđ‰āđāļāđ‰āļ›āļąāļāļŦāļē N+1 āđ‚āļ”āļĒāļ”āļķāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāļˆāļģāđ€āļ›āđ‡āļ™āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđƒāļ™āļ„āļĢāļąāđ‰āļ‡āđ€āļ”āļĩāļĒāļ§

OrderRepository.javajava
public interface OrderRepository extends JpaRepository<Order, Long> {

    // Explicit fetch join to load customers
    @Query("SELECT o FROM Order o JOIN FETCH o.customer")
    List<Order> findAllWithCustomer();

    // Multiple fetch join for several associations
    @Query("SELECT o FROM Order o " +
           "JOIN FETCH o.customer c " +
           "JOIN FETCH o.items i")
    List<Order> findAllWithCustomerAndItems();

    // Fetch join with WHERE condition
    @Query("SELECT o FROM Order o " +
           "JOIN FETCH o.customer c " +
           "WHERE o.createdAt > :since")
    List<Order> findRecentOrdersWithCustomer(
        @Param("since") LocalDateTime since
    );
}

Fetch join āđ€āļ›āļĨāļĩāđˆāļĒāļ™āļ„āļīāļ§āļĢāļĩ N+1 āđƒāļŦāđ‰āđ€āļ›āđ‡āļ™āļ„āļīāļ§āļĢāļĩāļ—āļĩāđˆāļ›āļĢāļąāļšāđƒāļŦāđ‰āđ€āļŦāļĄāļēāļ°āļŠāļĄāđ€āļžāļĩāļĒāļ‡āļ„āļĢāļąāđ‰āļ‡āđ€āļ”āļĩāļĒāļ§

sql
-- Single query with join
SELECT o.id, o.order_number, o.created_at, o.customer_id,
       c.id, c.name, c.email
FROM orders o
JOIN customers c ON o.customer_id = c.id

āļ•āļ­āļ™āļ™āļĩāđ‰āļšāļĢāļīāļāļēāļĢāđƒāļŠāđ‰āđ€āļĄāļ˜āļ­āļ”āļ—āļĩāđˆāļ›āļĢāļąāļšāđƒāļŦāđ‰āđ€āļŦāļĄāļēāļ°āļŠāļĄāđ‚āļ”āļĒāđ„āļĄāđˆāļ•āđ‰āļ­āļ‡āđāļāđ‰āđ„āļ‚āđ‚āļ„āđ‰āļ”āļ—āļēāļ‡āļ˜āļļāļĢāļāļīāļˆ

OrderService.java - Optimized codejava
@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;

    public List<OrderDto> getAllOrders() {
        // Single query with join
        List<Order> orders = orderRepository.findAllWithCustomer();

        // No additional queries
        return orders.stream()
            .map(order -> new OrderDto(
                order.getId(),
                order.getOrderNumber(),
                order.getCustomer().getName() // Already loaded
            ))
            .toList();
    }
}

āļžāļĢāđ‰āļ­āļĄāļ—āļĩāđˆāļˆāļ°āļžāļīāļŠāļīāļ•āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ Spring Boot āđāļĨāđ‰āļ§āļŦāļĢāļ·āļ­āļĒāļąāļ‡āļ„āļĢāļąāļš?

āļāļķāļāļāļ™āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āđāļšāļšāđ‚āļ•āđ‰āļ•āļ­āļš, flashcards āđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

āļ§āļīāļ˜āļĩāđāļāđ‰āļ—āļĩāđˆ 2: @EntityGraph āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ„āļ§āļšāļ„āļļāļĄāđāļšāļšāļ›āļĢāļ°āļāļēāļĻ

āļ„āļģāļ­āļ˜āļīāļšāļēāļĒāļ›āļĢāļ°āļāļ­āļš @EntityGraph āļ™āļģāđ€āļŠāļ™āļ­āļ—āļēāļ‡āđ€āļĨāļ·āļ­āļāđāļšāļšāļ›āļĢāļ°āļāļēāļĻāđāļ—āļ™ fetch join āđ‚āļ”āļĒāļāļģāļŦāļ™āļ”āļ§āđˆāļēāļˆāļ°āđ‚āļŦāļĨāļ”āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒāđƒāļ”āđ‚āļ”āļĒāđ„āļĄāđˆāļ•āđ‰āļ­āļ‡āđ€āļ‚āļĩāļĒāļ™ JPQL āđāļšāļšāļāļģāļŦāļ™āļ”āđ€āļ­āļ‡

OrderRepository.javajava
public interface OrderRepository extends JpaRepository<Order, Long> {

    // Inline EntityGraph with attributePaths
    @EntityGraph(attributePaths = {"customer"})
    List<Order> findAll();

    // EntityGraph with multiple attributes
    @EntityGraph(attributePaths = {"customer", "items"})
    List<Order> findByCreatedAtAfter(LocalDateTime since);

    // Named EntityGraph referencing entity definition
    @EntityGraph(value = "Order.withCustomerAndItems")
    List<Order> findByCustomerId(Long customerId);

    // Combination with custom query
    @EntityGraph(attributePaths = {"customer"})
    @Query("SELECT o FROM Order o WHERE o.orderNumber LIKE :prefix%")
    List<Order> findByOrderNumberPrefix(@Param("prefix") String prefix);
}

EntityGraph āļ—āļĩāđˆāļĄāļĩāļŠāļ·āđˆāļ­āļ–āļđāļāļāļģāļŦāļ™āļ”āđ‚āļ”āļĒāļ•āļĢāļ‡āļšāļ™āđ€āļ­āļ™āļ—āļīāļ•āļĩāđ€āļžāļ·āđˆāļ­āļ™āļģāļāļĨāļąāļšāļĄāļēāđƒāļŠāđ‰āđƒāļ™āļŦāļĨāļēāļĒ repository

Order.javajava
@Entity
@Table(name = "orders")
@NamedEntityGraph(
    name = "Order.withCustomer",
    attributeNodes = @NamedAttributeNode("customer")
)
@NamedEntityGraph(
    name = "Order.withCustomerAndItems",
    attributeNodes = {
        @NamedAttributeNode("customer"),
        @NamedAttributeNode("items")
    }
)
@NamedEntityGraph(
    name = "Order.full",
    attributeNodes = {
        @NamedAttributeNode("customer"),
        @NamedAttributeNode(value = "items", subgraph = "items-product")
    },
    subgraphs = @NamedSubgraph(
        name = "items-product",
        attributeNodes = @NamedAttributeNode("product")
    )
)
public class Order {
    // Fields unchanged
}

Subgraph āļ­āļ™āļļāļāļēāļ•āđƒāļŦāđ‰āđ‚āļŦāļĨāļ”āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒāļ—āļĩāđˆāļ‹āđ‰āļ­āļ™āļāļąāļ™āđ„āļ”āđ‰ āļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡āļ‚āđ‰āļēāļ‡āļ•āđ‰āļ™āđ‚āļŦāļĨāļ”āļ„āļģāļŠāļąāđˆāļ‡āļ‹āļ·āđ‰āļ­ āļĢāļēāļĒāļāļēāļĢāļ‚āļ­āļ‡āļžāļ§āļāđ€āļ‚āļē āđāļĨāļ°āļœāļĨāļīāļ•āļ āļąāļ“āļ‘āđŒāļ‚āļ­āļ‡āđāļ•āđˆāļĨāļ°āļĢāļēāļĒāļāļēāļĢāđƒāļ™āļ„āļīāļ§āļĢāļĩāđ€āļ”āļĩāļĒāļ§

āļ§āļīāļ˜āļĩāđāļāđ‰āļ—āļĩāđˆ 3: Batch Fetching āļŠāļģāļŦāļĢāļąāļšāļ„āļ­āļĨāđ€āļĨāļāļŠāļąāļ™

Batch fetching āļ™āļģāđ€āļŠāļ™āļ­āļ—āļēāļ‡āđ€āļĨāļ·āļ­āļāđāļ—āļ™ fetch join āļŠāļģāļŦāļĢāļąāļšāļ„āļ­āļĨāđ€āļĨāļāļŠāļąāļ™ @OneToMany āđāļ—āļ™āļ—āļĩāđˆāļˆāļ°āđ‚āļŦāļĨāļ”āđāļ•āđˆāļĨāļ°āļ„āļ­āļĨāđ€āļĨāļāļŠāļąāļ™āļ—āļĩāļĨāļ°āļĢāļēāļĒāļāļēāļĢ Hibernate āļˆāļ°āļˆāļąāļ”āļāļĨāļļāđˆāļĄāļ„āļīāļ§āļĢāļĩāđ€āļ›āđ‡āļ™āļŠāļļāļ”

Order.javajava
@Entity
@Table(name = "orders")
public class Order {

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

    private String orderNumber;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private Customer customer;

    // Batch fetching on the collection
    @OneToMany(mappedBy = "order")
    @BatchSize(size = 25)
    private List<OrderItem> items = new ArrayList<>();
}

āļ”āđ‰āļ§āļĒ @BatchSize(size = 25) Hibernate āļˆāļ°āđ‚āļŦāļĨāļ”āļĢāļēāļĒāļāļēāļĢāļŠāļģāļŦāļĢāļąāļšāļ„āļģāļŠāļąāđˆāļ‡āļ‹āļ·āđ‰āļ­ 25 āļĢāļēāļĒāļāļēāļĢāđƒāļ™āļ„āļĢāļēāļ§āđ€āļ”āļĩāļĒāļ§āđāļ—āļ™āļ—āļĩāđˆāļˆāļ°āđ€āļ›āđ‡āļ™ 1 āļĢāļēāļĒāļāļēāļĢ āļŠāļģāļŦāļĢāļąāļš 100 āļ„āļģāļŠāļąāđˆāļ‡āļ‹āļ·āđ‰āļ­ āļˆāļģāļ™āļ§āļ™āļ„āļīāļ§āļĢāļĩāļĨāļ”āļĨāļ‡āļˆāļēāļ 101 āđ€āļ›āđ‡āļ™ 5

sql
-- Without batch fetching: 100 queries
SELECT * FROM order_items WHERE order_id = 1
SELECT * FROM order_items WHERE order_id = 2
-- ... 98 more queries

-- With @BatchSize(size = 25): 4 queries
SELECT * FROM order_items WHERE order_id IN (1, 2, 3, ..., 25)
SELECT * FROM order_items WHERE order_id IN (26, 27, 28, ..., 50)
SELECT * FROM order_items WHERE order_id IN (51, 52, 53, ..., 75)
SELECT * FROM order_items WHERE order_id IN (76, 77, 78, ..., 100)

āļāļēāļĢāļāļģāļŦāļ™āļ”āļ„āđˆāļēāļ‚āļ™āļēāļ”āđāļšāļ—āļŠāđŒāļ—āļąāđˆāļ§āđ‚āļĨāļāđƒāļŠāđ‰āļāļąāļšāļ„āļ­āļĨāđ€āļĨāļāļŠāļąāļ™āļ—āļąāđ‰āļ‡āļŦāļĄāļ”āđƒāļ™āđāļ­āļ›āļžāļĨāļīāđ€āļ„āļŠāļąāļ™

yaml
# application.yml
spring:
  jpa:
    properties:
      hibernate:
        # Global default batch size
        default_batch_fetch_size: 25
Batch āļāļąāļš Fetch Join

Batch fetching āđ€āļŦāļĄāļēāļ°āļŠāļģāļŦāļĢāļąāļšāļāļĢāļ“āļĩāļ—āļĩāđˆ fetch join āļŠāļĢāđ‰āļēāļ‡āļœāļĨāļ„āļđāļ“āļ„āļēāļĢāđŒāļ—āļĩāđ€āļ‹āļĩāļĒāļ™āļ—āļĩāđˆāđƒāļŦāļāđˆāđ€āļāļīāļ™āđ„āļ› āļŠāļģāļŦāļĢāļąāļšāļ„āļģāļŠāļąāđˆāļ‡āļ‹āļ·āđ‰āļ­āļ—āļĩāđˆāļĄāļĩ 10 āļĢāļēāļĒāļāļēāļĢāđāļĨāļ° 5 āļāļēāļĢāļŠāļģāļĢāļ°āđ€āļ‡āļīāļ™ fetch join āļ„āļ·āļ™āļ„āđˆāļē 50 āđāļ–āļ§ Batch fetching āđ€āļĢāļĩāļĒāļāđƒāļŠāđ‰āļ„āļīāļ§āļĢāļĩāđāļĒāļ 2 āļĢāļēāļĒāļāļēāļĢāļ—āļĩāđˆāļĄāļĩāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļĄāļēāļāļāļ§āđˆāļē

āđ€āļ›āļĢāļĩāļĒāļšāđ€āļ—āļĩāļĒāļšāļāļĨāļĒāļļāļ—āļ˜āđŒāļāļēāļĢāđ‚āļŦāļĨāļ”

āđāļ•āđˆāļĨāļ°āļāļĨāļĒāļļāļ—āļ˜āđŒāļĄāļĩāļ‚āđ‰āļ­āđ„āļ”āđ‰āđ€āļ›āļĢāļĩāļĒāļšāļ‚āļķāđ‰āļ™āļ­āļĒāļđāđˆāļāļąāļšāļšāļĢāļīāļšāļ—āļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™ āļ•āļēāļĢāļēāļ‡āļ•āđˆāļ­āđ„āļ›āļ™āļĩāđ‰āļŠāļĢāļļāļ›āļāļĢāļ“āļĩāļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™āļ—āļĩāđˆāđ€āļŦāļĄāļēāļ°āļŠāļĄāļ—āļĩāđˆāļŠāļļāļ”

| āļāļĨāļĒāļļāļ—āļ˜āđŒ | āļāļĢāļ“āļĩāļāļēāļĢāđƒāļŠāđ‰āļ‡āļēāļ™ | āļ‚āđ‰āļ­āļ”āļĩ | āļ‚āđ‰āļ­āđ€āļŠāļĩāļĒ | |----------|----------|------------|---------------| | Fetch Join | āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒ @ManyToOne | āļ„āļīāļ§āļĢāļĩ SQL āđ€āļ”āļĩāļĒāļ§ | āļœāļĨāļ„āļđāļ“āļ„āļēāļĢāđŒāļ—āļĩāđ€āļ‹āļĩāļĒāļ™āļāļąāļšāļ„āļ­āļĨāđ€āļĨāļāļŠāļąāļ™ | | @EntityGraph | āļāļēāļĢāđ‚āļŦāļĨāļ”āđāļšāļšāļ›āļĢāļ°āļāļēāļĻ | āđƒāļŠāđ‰āļ‹āđ‰āļģāđ„āļ”āđ‰ āļ­āđˆāļēāļ™āļ‡āđˆāļēāļĒ | āļĒāļ·āļ”āļŦāļĒāļļāđˆāļ™āļ™āđ‰āļ­āļĒāļāļ§āđˆāļē JPQL | | Batch Fetching | āļ„āļ­āļĨāđ€āļĨāļāļŠāļąāļ™ @OneToMany | āļŦāļĨāļĩāļāđ€āļĨāļĩāđˆāļĒāļ‡āļœāļĨāļ„āļđāļ“āļ„āļēāļĢāđŒāļ—āļĩāđ€āļ‹āļĩāļĒāļ™ | āļ„āļīāļ§āļĢāļĩāļŦāļĨāļēāļĒāļĢāļēāļĒāļāļēāļĢ | | Subselect | āļ„āļ­āļĨāđ€āļĨāļāļŠāļąāļ™āļ—āļĩāđˆāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āđ„āļĄāđˆāļšāđˆāļ­āļĒ | āđ‚āļŦāļĨāļ”āđ€āļ‰āļžāļēāļ°āđ€āļĄāļ·āđˆāļ­āļˆāļģāđ€āļ›āđ‡āļ™ | āļ„āļīāļ§āļĢāļĩāļĒāđˆāļ­āļĒāļ—āļĩāđˆāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒāļāļąāļ™ |

āļāļĨāļĒāļļāļ—āļ˜āđŒ subselect āđ‚āļŦāļĨāļ”āļ—āļąāđ‰āļ‡āļ„āļ­āļĨāđ€āļĨāļāļŠāļąāļ™āđ€āļĄāļ·āđˆāļ­āđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļ­āļ‡āļ„āđŒāļ›āļĢāļ°āļāļ­āļšāđƒāļ”āđ† āđ€āļ›āđ‡āļ™āļ„āļĢāļąāđ‰āļ‡āđāļĢāļ

Order.javajava
@OneToMany(mappedBy = "order")
@Fetch(FetchMode.SUBSELECT)
private List<OrderItem> items = new ArrayList<>();
sql
-- Generated subselect query
SELECT * FROM order_items
WHERE order_id IN (SELECT id FROM orders WHERE created_at > ?)

āļŦāļĨāļĩāļāđ€āļĨāļĩāđˆāļĒāļ‡ N+1 āļ”āđ‰āļ§āļĒ DTO Projections

DTO projection āļ™āļģāđ€āļŠāļ™āļ­āđāļ™āļ§āļ—āļēāļ‡āļ—āļĩāđˆāļĢāļļāļ™āđāļĢāļ‡āđāļ•āđˆāļĄāļĩāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž āđ‚āļ”āļĒāļāļēāļĢāđ€āļĨāļ·āļ­āļāđ€āļ‰āļžāļēāļ°āļ„āļ­āļĨāļąāļĄāļ™āđŒāļ—āļĩāđˆāļˆāļģāđ€āļ›āđ‡āļ™ projection āļŦāļĨāļĩāļāđ€āļĨāļĩāđˆāļĒāļ‡āļāļēāļĢāđ‚āļŦāļĨāļ”āđ€āļ­āļ™āļ—āļīāļ•āļĩāđāļĨāļ°āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒāļ‚āļ­āļ‡āļžāļ§āļāđ€āļ‚āļēāđ‚āļ”āļĒāļŠāļīāđ‰āļ™āđ€āļŠāļīāļ‡

OrderSummaryDto.javajava
public record OrderSummaryDto(
    Long orderId,
    String orderNumber,
    String customerName,
    String customerEmail
) {}
OrderRepository.javajava
public interface OrderRepository extends JpaRepository<Order, Long> {

    // DTO projection with constructor
    @Query("SELECT new com.example.dto.OrderSummaryDto(" +
           "o.id, o.orderNumber, c.name, c.email) " +
           "FROM Order o JOIN o.customer c")
    List<OrderSummaryDto> findAllOrderSummaries();

    // DTO projection with condition
    @Query("SELECT new com.example.dto.OrderSummaryDto(" +
           "o.id, o.orderNumber, c.name, c.email) " +
           "FROM Order o JOIN o.customer c " +
           "WHERE o.createdAt > :since")
    List<OrderSummaryDto> findRecentOrderSummaries(
        @Param("since") LocalDateTime since
    );
}

āđāļ™āļ§āļ—āļēāļ‡āļ™āļĩāđ‰āļŠāļĢāđ‰āļēāļ‡āļ„āļīāļ§āļĢāļĩ SQL āļ—āļĩāđˆāđ€āļŦāļĄāļēāļ°āļŠāļĄāļ—āļĩāđˆāļŠāļļāļ”āđ‚āļ”āļĒāđ„āļĄāđˆāļĄāļĩāļ„āđˆāļēāđƒāļŠāđ‰āļˆāđˆāļēāļĒāļ‚āļ­āļ‡āļāļēāļĢāđāļĄāļ›āđ€āļ­āļ™āļ—āļīāļ•āļĩ

sql
SELECT o.id, o.order_number, c.name, c.email
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.created_at > ?

āļāļēāļĢāļāļģāļŦāļ™āļ”āļ„āđˆāļēāļ‚āļąāđ‰āļ™āļŠāļđāļ‡āļ”āđ‰āļ§āļĒ Spring Data JPA

Spring Data JPA 3.x āđāļ™āļ°āļ™āļģāļāļēāļĢāļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļˆāļąāļ”āļāļēāļĢ EntityGraph āđāļĨāļ°āļ„āļīāļ§āļĢāļĩāļ—āļĩāđˆāļ›āļĢāļąāļšāđƒāļŦāđ‰āđ€āļŦāļĄāļēāļ°āļŠāļĄ

OrderRepository.javajava
public interface OrderRepository extends JpaRepository<Order, Long> {

    // Dynamic EntityGraph with Specification
    @EntityGraph(attributePaths = {"customer"})
    List<Order> findAll(Specification<Order> spec);

    // Pagination with EntityGraph
    @EntityGraph(attributePaths = {"customer"})
    Page<Order> findByCustomerNameContaining(
        String name,
        Pageable pageable
    );

    // Slice for efficient pagination
    @EntityGraph(attributePaths = {"customer"})
    Slice<Order> findByCreatedAtBefore(
        LocalDateTime date,
        Pageable pageable
    );
}

āļāļēāļĢāđ‚āļŦāļĨāļ”āđāļšāļšāļĄāļĩāđ€āļ‡āļ·āđˆāļ­āļ™āđ„āļ‚āļ­āļ™āļļāļāļēāļ•āđƒāļŦāđ‰āđƒāļŠāđ‰āļāļĨāļĒāļļāļ—āļ˜āđŒāļ—āļĩāđˆāđāļ•āļāļ•āđˆāļēāļ‡āļāļąāļ™āļ•āļēāļĄāļšāļĢāļīāļšāļ—

OrderService.javajava
@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final EntityManager entityManager;

    public List<Order> getOrdersWithGraph(String graphName) {
        // Dynamic EntityGraph retrieval
        EntityGraph<?> graph = entityManager
            .getEntityGraph(graphName);

        return entityManager
            .createQuery("SELECT o FROM Order o", Order.class)
            .setHint("jakarta.persistence.loadgraph", graph)
            .getResultList();
    }

    public List<Order> getOrdersForListing() {
        // Minimal graph for listing
        return getOrdersWithGraph("Order.withCustomer");
    }

    public List<Order> getOrdersForDetail() {
        // Full graph for detail view
        return getOrdersWithGraph("Order.full");
    }
}

āđ€āļĢāļīāđˆāļĄāļāļķāļāļ‹āđ‰āļ­āļĄāđ€āļĨāļĒ!

āļ—āļ”āļŠāļ­āļšāļ„āļ§āļēāļĄāļĢāļđāđ‰āļ‚āļ­āļ‡āļ„āļļāļ“āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āļŠāļąāļĄāļ āļēāļĐāļ“āđŒāđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

āļāļēāļĢāļ—āļ”āļŠāļ­āļšāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāđ€āļžāļ·āđˆāļ­āļ•āļĢāļ§āļˆāļˆāļąāļš N+1

āļāļēāļĢāļ—āļ”āļŠāļ­āļšāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļīāļĒāļ·āļ™āļĒāļąāļ™āļ§āđˆāļēāđ„āļĄāđˆāļĄāļĩāļ›āļąāļāļŦāļē N+1 āđ‚āļ”āļĒāļāļēāļĢāļ™āļąāļšāļ„āļīāļ§āļĢāļĩ SQL āļ—āļĩāđˆāđ€āļĢāļĩāļĒāļāđƒāļŠāđ‰

OrderRepositoryPerformanceTest.javajava
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class OrderRepositoryPerformanceTest {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private EntityManager entityManager;

    @PersistenceContext
    private EntityManager em;

    private Statistics statistics;

    @BeforeEach
    void setUp() {
        // Enable Hibernate statistics
        Session session = entityManager.unwrap(Session.class);
        SessionFactory factory = session.getSessionFactory();
        statistics = factory.getStatistics();
        statistics.setStatisticsEnabled(true);
        statistics.clear();
    }

    @Test
    void findAllWithCustomer_shouldExecuteSingleQuery() {
        // Given: 50 orders in database
        createTestOrders(50);
        entityManager.clear();
        statistics.clear();

        // When: fetch with fetch join
        List<Order> orders = orderRepository.findAllWithCustomer();

        // Then: single query executed
        assertThat(orders).hasSize(50);
        assertThat(statistics.getQueryExecutionCount())
            .as("Should execute only 1 query with fetch join")
            .isEqualTo(1);
    }

    @Test
    void findAll_withoutOptimization_triggersNPlus1() {
        // Given: 50 orders in database
        createTestOrders(50);
        entityManager.clear();
        statistics.clear();

        // When: fetch without optimization
        List<Order> orders = orderRepository.findAll();

        // Access customers to trigger lazy loading
        orders.forEach(o -> o.getCustomer().getName());

        // Then: N+1 queries (51 instead of 1)
        assertThat(statistics.getQueryExecutionCount())
            .as("Should detect N+1 problem")
            .isGreaterThan(1);
    }

    private void createTestOrders(int count) {
        for (int i = 0; i < count; i++) {
            Customer customer = new Customer();
            customer.setName("Customer " + i);
            customer.setEmail("customer" + i + "@test.com");
            entityManager.persist(customer);

            Order order = new Order();
            order.setOrderNumber("ORD-" + i);
            order.setCustomer(customer);
            order.setCreatedAt(LocalDateTime.now());
            entityManager.persist(order);
        }
        entityManager.flush();
    }
}

āļāļēāļĢāļĒāļ·āļ™āļĒāļąāļ™āļšāļ™ getQueryExecutionCount() āļĢāļąāļšāļ›āļĢāļ°āļāļąāļ™āļ§āđˆāļēāļāļēāļĢāđ€āļžāļīāđˆāļĄāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļĒāļąāļ‡āļ„āļ‡āļ­āļĒāļđāđˆāļĢāļ°āļŦāļ§āđˆāļēāļ‡āļ§āļīāļ§āļąāļ’āļ™āļēāļāļēāļĢāļ‚āļ­āļ‡āđ‚āļ„āđ‰āļ”

āļšāļ—āļŠāļĢāļļāļ›

āļ›āļąāļāļŦāļē N+1 āđ€āļ›āđ‡āļ™āļ„āļ§āļēāļĄāļ—āđ‰āļēāļ—āļēāļĒāļ”āđ‰āļēāļ™āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļ—āļĩāđˆāļŠāļģāļ„āļąāļāđƒāļ™ Spring Data JPA āđāļ•āđˆāļĄāļĩāļ§āļīāļ˜āļĩāđāļāđ‰āļ—āļĩāđˆāļĄāļĩāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļŦāļĨāļēāļĒāļ§āļīāļ˜āļĩ Fetch join āđāļĨāļ° @EntityGraph āđāļāđ‰āļ›āļąāļāļŦāļēāļŠāđˆāļ§āļ™āđƒāļŦāļāđˆāđ‚āļ”āļĒāļāļēāļĢāđ‚āļŦāļĨāļ”āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒāđƒāļ™āļ„āļīāļ§āļĢāļĩāđ€āļ”āļĩāļĒāļ§ Batch fetching āđ€āļ›āđ‡āļ™āļ—āļēāļ‡āđ€āļĨāļ·āļ­āļāļŠāļģāļŦāļĢāļąāļšāļ„āļ­āļĨāđ€āļĨāļāļŠāļąāļ™āļ‚āļ™āļēāļ”āđƒāļŦāļāđˆāļ—āļĩāđˆ fetch join āļŠāļĢāđ‰āļēāļ‡āļœāļĨāļ„āļđāļ“āļ„āļēāļĢāđŒāļ—āļĩāđ€āļ‹āļĩāļĒāļ™

āļĢāļēāļĒāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļāļēāļĢāļ›āđ‰āļ­āļ‡āļāļąāļ™ N+1:

  • ✅ āđ€āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™āļšāļąāļ™āļ—āļķāļ SQL āđƒāļ™āļāļēāļĢāļžāļąāļ’āļ™āļēāđ€āļžāļ·āđˆāļ­āļ•āļĢāļ§āļˆāļˆāļąāļšāļ„āļīāļ§āļĢāļĩāļŦāļĨāļēāļĒāļĢāļēāļĒāļāļēāļĢ
  • ✅ āđƒāļŠāđ‰ JOIN FETCH āļŠāļģāļŦāļĢāļąāļšāļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒ @ManyToOne āļ—āļĩāđˆāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļšāđˆāļ­āļĒ
  • ✅ āļ›āļĢāļ°āļĒāļļāļāļ•āđŒāđƒāļŠāđ‰ @EntityGraph āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāđ‚āļŦāļĨāļ”āđāļšāļšāļ›āļĢāļ°āļāļēāļĻāļ—āļĩāđˆāđƒāļŠāđ‰āļ‹āđ‰āļģāđ„āļ”āđ‰
  • ✅ āļāļģāļŦāļ™āļ”āļ„āđˆāļē @BatchSize āļŠāļģāļŦāļĢāļąāļšāļ„āļ­āļĨāđ€āļĨāļāļŠāļąāļ™ @OneToMany āļ‚āļ™āļēāļ”āđƒāļŦāļāđˆ
  • ✅ āđƒāļŠāđ‰ DTO projection āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāđāļšāļšāļ­āđˆāļēāļ™āļ­āļĒāđˆāļēāļ‡āđ€āļ”āļĩāļĒāļ§
  • ✅ āđ€āļ‚āļĩāļĒāļ™āļāļēāļĢāļ—āļ”āļŠāļ­āļšāđ€āļžāļ·āđˆāļ­āļ•āļĢāļ§āļˆāļŠāļ­āļšāļˆāļģāļ™āļ§āļ™āļ„āļīāļ§āļĢāļĩāļ—āļĩāđˆāđ€āļĢāļĩāļĒāļāđƒāļŠāđ‰
  • ✅ āļ›āļīāļ”āđƒāļŠāđ‰āļ‡āļēāļ™āļšāļąāļ™āļ—āļķāļ SQL āđāļĨāļ°āļŠāļ–āļīāļ•āļīāđƒāļ™āļĢāļ°āļšāļšāđ‚āļ›āļĢāļ”āļąāļāļŠāļąāļ™
  • ✅ āļ—āļģāđ‚āļ›āļĢāđ„āļŸāļĨāđŒ endpoint āļŠāļģāļ„āļąāļāđ€āļ›āđ‡āļ™āļ›āļĢāļ°āļˆāļģāļ”āđ‰āļ§āļĒāđ€āļĄāļ•āļĢāļīāļāļ‚āļ­āļ‡ Hibernate

āđāļ—āđ‡āļ

#spring data jpa
#n+1 problem
#fetch join
#entitygraph
#performance

āđāļŠāļĢāđŒ

āļšāļ—āļ„āļ§āļēāļĄāļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡

āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļ—āļēāļ‡āđ€āļ—āļ„āļ™āļīāļ„ Spring GraphQL āļžāļĢāđ‰āļ­āļĄ resolver āđāļĨāļ° DataLoader

āļŠāļąāļĄāļ āļēāļĐāļ“āđŒ Spring GraphQL: Resolver, DataLoader āđāļĨāļ°āļ§āļīāļ˜āļĩāđāļāđ‰āļ›āļąāļāļŦāļē N+1

āđ€āļ•āļĢāļĩāļĒāļĄāļ•āļąāļ§āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ Spring GraphQL āļ”āđ‰āļ§āļĒāļ„āļđāđˆāļĄāļ·āļ­āļ—āļĩāđˆāļ„āļĢāļšāļ–āđ‰āļ§āļ™āļ™āļĩāđ‰ Resolver, DataLoader, āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ›āļąāļāļŦāļē N+1, mutation āđāļĨāļ°āđāļ™āļ§āļ›āļāļīāļšāļąāļ•āļīāļ—āļĩāđˆāļ”āļĩāļ—āļĩāđˆāļŠāļļāļ”āļŠāļģāļŦāļĢāļąāļšāļ„āļģāļ–āļēāļĄāļ—āļēāļ‡āđ€āļ—āļ„āļ™āļīāļ„

Spring Boot 3.4 Virtual Threads: āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāđāļĨāļ° Benchmark āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž

Spring Boot 3.4 Virtual Threads: āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāđāļĨāļ° Benchmark āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž

āđ€āļŠāļĩāđˆāļĒāļ§āļŠāļēāļ Java 21 Virtual Threads āļ”āđ‰āļ§āļĒ Spring Boot 3.4: 15 āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ Benchmark āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž āđāļĨāļ°āļĢāļđāļ›āđāļšāļšāļāļēāļĢāļĒāđ‰āļēāļĒāļĢāļ°āļšāļšāļŠāļģāļŦāļĢāļąāļšāļŠāļ­āļšāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāđ€āļ—āļ„āļ™āļīāļ„

Structured logging āđƒāļ™ Spring Boot āļ”āđ‰āļ§āļĒ Logback āđāļĨāļ° JSON

Spring Boot Logging āđƒāļ™āļ›āļĩ 2026: āļĨāđ‡āļ­āļāđāļšāļšāļĄāļĩāđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡āļŠāļģāļŦāļĢāļąāļšāđ‚āļ›āļĢāļ”āļąāļāļŠāļąāļ™āļ”āđ‰āļ§āļĒ Logback āđāļĨāļ° JSON

āļ„āļđāđˆāļĄāļ·āļ­āļ‰āļšāļąāļšāļŠāļĄāļšāļđāļĢāļ“āđŒāļŠāļģāļŦāļĢāļąāļš structured logging āđƒāļ™ Spring Boot āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļē Logback JSON, MDC āļŠāļģāļŦāļĢāļąāļš tracing āđāļ™āļ§āļ›āļāļīāļšāļąāļ•āļīāļ—āļĩāđˆāļ”āļĩāļ—āļĩāđˆāļŠāļļāļ”āđƒāļ™āđ‚āļ›āļĢāļ”āļąāļāļŠāļąāļ™ āđāļĨāļ°āļāļēāļĢāļĢāļ§āļĄāļāļąāļš ELK Stack