Real World Example

Production Incident Review

A ShopEase sale day incident, written the way teams at Flipkart or PhonePe write post-mortems. Each root cause maps to a pitfall from this module:

SymptomRoot causeFix shipped
HikariCP "Connection is not available, request timed out after 30000ms"OSIV held connections during slow JSON writes to mobile clientsopen-in-view=false, DTOs built in the service
GET /api/users/{id} p99 = 9 s for resellersuser.getOrders() loaded 300k orders to show a countremoved the collection; countByUserId query
Cart items vanished randomly from SetLombok @Data hashCode used generated idproxy-safe equals/hashCode
StackOverflowError in error logslogging log.info("order {}", order) with generated toStringhand-written toString

The Fixed Endpoint

public record UserSummary(Long id, String name, String email, long orderCount, BigDecimal lifetimeValue) {}

public interface UserRepository extends JpaRepository<User, Long> {
    @Query("""
        select new com.shopease.user.UserSummary(
                 u.id, u.name, u.email, count(o.id), coalesce(sum(o.total), 0))
          from User u left join Order o on o.user = u
         where u.id = :id
         group by u.id, u.name, u.email
        """)
    Optional<UserSummary> findSummary(@Param("id") Long id);
}

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
class UserController {
    private final UserService users;

    @GetMapping("/{id}")
    UserSummary get(@PathVariable Long id) {
        return users.summary(id);        // DTO only; no entity leaves the service
    }
}

Request

curl localhost:8080/api/users/42

Example output

select u1_0.id,u1_0.name,u1_0.email,count(o1_0.id),coalesce(sum(o1_0.total),0)
  from users u1_0 left join orders o1_0 on o1_0.user_id=u1_0.id
 where u1_0.id=? group by u1_0.id,u1_0.name,u1_0.email

HTTP/1.1 200 OK
{"id":42,"name":"Kiran Traders","email":"[email protected]","orderCount":312904,"lifetimeValue":48211930.50}

Exercise: After turning OSIV off, GET /api/orders/7 throws LazyInitializationException: could not initialize proxy [Product#3] - no Session. The controller returns the Order entity. Give two correct fixes.

Show answer
1. Return a DTO built inside the @Transactional(readOnly = true) service method,
   loading what it needs in one query:
     select o from Order o join fetch o.items i join fetch i.product where o.id = :id
   then map to OrderDto(id, status, items[name, qty, price]).
2. Use a DTO projection query directly (select new ...OrderLine(...)) and group the lines.
(Wrong fix: turning OSIV back on or making associations EAGER.)