The full picture
- Mechanics: OpenEntityManagerInViewInterceptor binds an EntityManager to the request thread before the controller and closes it after response rendering; transactions still begin/end at @Transactional boundaries, but the context (and eventually a connection when lazy loads fire) outlives them.
- The connection-holding trap: request → service TX ends → controller calls a 3s downstream API → serializer triggers lazy load → connection re-acquired/held while… multiply by concurrent requests = pool starvation with 'idle' CPUs. Classic prod incident shape.
- With OSIV off: LazyInitializationException outside boundaries is a feature — a compile-time-ish signal that a use-case's fetch plan is undeclared. Answer it with fetch joins/entity graphs/DTO projections at the repository (previous question's toolbox).
- Legit OSIV niche: small server-rendered CRUD apps (Thymeleaf) where developer velocity beats these concerns — defensible if chosen knowingly.
# application.yml
spring:
jpa:
open-in-view: false # decision made explicit
// Now this blows up in tests instead of N+1-ing in prod:
@GetMapping("/orders/{id}")
OrderDto get(@PathVariable long id) {
Order o = service.load(id); // TX ended inside service
return OrderDto.from(o); // touches o.getLines() ->
} // LazyInitializationException ✅ (told you!)
// Fix where it belongs: service returns a DTO built from a fetch-joined query.