Java 아키텍처·Spring 용어 사전
JPAOpen Session In View · LazyInitializationException

OSIV

영속성 컨텍스트를 뷰 렌더링까지 열어 두는 설정. 편하지만 커넥션을 오래 붙잡는다.

Open Session In View. 영속성 컨텍스트(와 DB 커넥션)를 컨트롤러·뷰 렌더링까지 열어 두는 설정. Spring Boot에서 기본이 켜져 있다.

무엇을 해결하나

@Transactional
Order find(Long id) { return repo.findById(id).orElseThrow(); }

// 컨트롤러에서
Order o = service.find(1L);
o.getMember().getName();      // 트랜잭션은 끝났다

OSIV 꺼짐 → LazyInitializationException: could not initialize proxy - no Session OSIV 켜짐 → 컨텍스트가 아직 살아 있어 지연 로딩이 동작한다

대가 — 커넥션을 오래 쥔다

[OSIV ON]
요청 시작 ──── 커넥션 확보 ──── 서비스 ──── 컨트롤러 ──── 뷰 렌더링 ──── 커넥션 반납
              └──────────────── 이 구간 내내 커넥션 점유 ────────────────┘

[OSIV OFF]
요청 시작 ─── 서비스(트랜잭션) ─── 반납 ─── 컨트롤러 ─── 뷰 렌더링
             └─ 이 구간만 점유 ─┘

외부 API 호출이나 느린 렌더링이 컨트롤러에 있으면, 그동안 커넥션이 묶인다. 트래픽이 몰리면 커넥션 풀이 마르고 전체 서비스가 멈춘다.

끄는 법과 그 대가

spring:
  jpa:
    open-in-view: false      # 끄면 기동 시 WARN 로그가 사라진다

끄면 지연 로딩을 트랜잭션 안에서 끝내야 한다.

// ① 페치 조인으로 필요한 걸 다 가져온다
@Query("select o from Order o join fetch o.member where o.id = :id")
Order findWithMember(@Param("id") Long id);

// ② 트랜잭션 안에서 DTO 로 변환해 내보낸다 (권장)
@Transactional(readOnly = true)
OrderDto find(Long id) {
    Order o = repo.findById(id).orElseThrow();
    return new OrderDto(o.getId(), o.getMember().getName());   // 여기서 초기화된다
}

②가 정석이다 — 엔티티를 계층 밖으로 내보내지 않으면 OSIV도 LazyInitializationException도 문제가 되지 않는다.

어떻게 판단하나

  • 트래픽이 적은 사내 시스템 · 빠른 개발이 우선 — → 켜 둬도 된다
  • 외부 API 호출이 섞인다 · 트래픽이 많다 — → 끄고 DTO 변환으로 간다

Spring Boot가 기동할 때 이 설정에 대해 WARN을 찍는 이유가 "기본값이지만 의식적으로 정하라"는 신호다.

면접 함정

  • "OSIV를 끄면 성능이 좋아진다" → 커넥션 점유 시간이 줄 뿐, 쿼리 자체가 빨라지지 않는다.
  • "OSIV가 켜져 있으면 트랜잭션도 열려 있다" → 컨텍스트만 열려 있다. 트랜잭션은 서비스에서 끝나므로 컨트롤러의 변경은 반영되지 않는다.

켜져 있는지 확인하는 법

기동 로그에 이 경고가 있으면 켜져 있는 것이다

spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning

"명시적으로 정하라" 는 뜻이다. 끄든 켜든 설정 파일에 적어 두는 것이 의도를 남기는 방법이다.

끄면 어디서 터지나

// 컨트롤러에서 엔티티를 그대로 반환하면 직렬화 중에 터진다
@GetMapping("/orders/{id}")
public Order get(@PathVariable Long id) { return service.find(id); }
// LazyInitializationException: could not initialize proxy - no Session
//   ← Jackson 이 getMember() 를 부르는 순간

엔티티를 API 응답으로 내보내지 않는 것이 근본 해법이다. DTO로 바꾸면 OSIV 설정과 무관해지고, 스키마 변경이 API에 새는 것도 막힌다.

// Jackson 이 프록시를 만나 터지는 것 자체를 막는 모듈도 있다
// Hibernate5Module — 초기화 안 된 프록시를 null 로 직렬화한다 (임시방편)

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — JPA·영속성 — 영속성 컨텍스트·N+1