Real World Example

Amazon-style "Your Orders" Page

The "Your Orders" page of a large store shows, per order: date, status, total, and the first product names with thumbnails. A real team found this page taking 1.8 s for heavy users. The log showed 1 + 40 + 120 queries: orders, then items per order, then the product per item. Here is how ShopEase fixes it with a paged order query plus batch fetching (fetch joins cannot be combined safely with paging a collection; see Pagination and Sorting).

public interface OrderRepository extends JpaRepository<Order, Long> {
    Page<Order> findByUserId(Long userId, Pageable pageable);   // 1 query + 1 count
}

@Entity @Table(name = "orders")
public class Order {
    @OneToMany(mappedBy = "order")
    @BatchSize(size = 50)
    private List<OrderItem> items = new ArrayList<>();
}

@Entity @Table(name = "order_items")
public class OrderItem {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id")
    private Product product;                                    // batch-loaded via default_batch_fetch_size
}

Before and After

Example

hibernate.generate_statistics=true prints session metrics at the end of the request:

GET /api/me/orders?page=0&size=40

Example output

-- before (all lazy, no batching)
StatisticalLoggingSessionEventListener : Session Metrics {
    161 JDBC statements executed;  1734 ms total
}

-- after (default_batch_fetch_size=50 + @BatchSize on items)
DEBUG org.hibernate.SQL : select o1_0.id,o1_0.status,o1_0.total,o1_0.user_id from orders o1_0 where o1_0.user_id=? order by o1_0.created_at desc limit ?,?
DEBUG org.hibernate.SQL : select count(o1_0.id) from orders o1_0 where o1_0.user_id=?
DEBUG org.hibernate.SQL : select i1_0.order_id,i1_0.id,i1_0.price,i1_0.product_id,i1_0.quantity from order_items i1_0 where i1_0.order_id in (?,?,?,...)
DEBUG org.hibernate.SQL : select p1_0.id,p1_0.category_id,p1_0.description,p1_0.name,p1_0.price,p1_0.stock from products p1_0 where p1_0.id in (?,?,?,...)
StatisticalLoggingSessionEventListener : Session Metrics {
    5 JDBC statements executed;  38 ms total
}

Team Checklist

  • All associations LAZY; spring.jpa.open-in-view=false.
  • hibernate.default_batch_fetch_size set (16–100) as a global safety net.
  • Each screen has one repository method that fetches exactly its data (fetch join, entity graph or DTO).
  • SQL logging on in dev; query-count assertions in integration tests for the busiest endpoints.