본문 바로가기

Java/Spring

open-in-view=false 환경에서 LazyInitializationException → 403 에러 전파 사례

개요

오늘은 실제 프로젝트를 프런트 분들과 함께 진행하면서, 생각지도 못했던(?) 어이없는 에러를 공유하려고 한다.

서비스 운영 중 특정 조회 화면에 들어가면 자꾸 로그인이 풀려 튕겨나가는 현상이 발생했다.
퇴근 직전에 AWS CloudWatch 로그를 확인해 보니, 원인은 다름 아닌 LazyInitializationException.
그런데 더 황당했던 건 이 예외가 프런트 쪽에서 “로그아웃”으로 처리되어 버린 것이었다.

원인을 찾아보니, 함께 개발하던 분이 @Transactional 하나를 누락했을 뿐인데… 사용자 입장에선 로그아웃된 것처럼 보였던 거다.
결국 @Transactional 추가만으로 해결했지만, 이 일을 계기로 “open-in-view=false일 때 왜 이런 일이 생길까?”라는 궁금증이 생겼다. 오늘은 그 내용을 정리해 본다.

OSIV(Open-Session-In-View) 란?

JPA에서 EntitiManager를 Hibernate에서 Session이라 부르는데, OSIV의 Session은 이 세션을 의미한다.

JPA에서는 OEIV**(Open EntityManager In View)가 되고, Hibernate에서는 OSIV(Open Session In View)가 되지만 관례상 둘 다 'OSIV**'라고 이야기한다.

true 컨트롤러~뷰까지 영속성 열림 Lazy 로딩 편리 N+1, 성능 예측 불가
false 서비스 계층까지만 열림 구조적, 안정적 Lazy 로딩 시 반드시 @Transactional 필요

 

스프링 부트는 기본적으로 'Open EntityManager In View'를 활성화해 주는데 이때

2025-08-20T13:35:48.169+09:00  WARN 19959 --- [  restartedMain] JpaBaseConfiguration$JpaWebConfiguration 
: 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

open-in-view 옵션을 명시하지 않았을 때 경고 메시지를 남긴다.

실무와 같은 상황 발생을 위해 메서드에 @Transactional을 누락하고 호출해 보았다. 해당 API는 상품에 해당하는 채팅방의 정보를 조회하는 API이다.

@GetMapping("/{roomId}")
    public ResponseEntity<ChatRoomResponse> getChatRoom(
            @PathVariable Long roomId,
            @AuthenticationPrincipal PrincipalDetails principalDetails) {
        return ResponseEntity.ok(chatRoomService.getChatRoom(roomId,principalDetails.getUser()));
    }
public ChatRoomResponse getChatRoom(Long chatRoomId, User user) {

        ChatRoom chatRoom = getChatRoomWithParticipants(chatRoomId);

        chatRoom.addOrActivateMember(user);
        return ChatRoomResponse.from(chatRoom);
    }

403 에러가 난 이유

예외 전파

LazyInitializationException 발생

2025-08-20T13:42:10.587+09:00  WARN 20030 --- [nio-8080-exec-6] .m.m.a.ExceptionHandlerExceptionResolver 
: Resolved [org.hibernate.LazyInitializationException: 
Could not initialize proxy [com.example.bowchat.product.entity.Product#1] - no session]

CommonExceptionHandler 미처리

이런 로그가 뜨면서 당시 발생했던 실제 프로젝트 오류를 exceptionHandling에 대한 처리를 CommonExceptionHandler에서 하지 않았던 상태였었다

Spring Security 쪽으로 전달

처리되지 않은 예외가 Security 필터 체인까지 올라가면서 403 Forbidden으로 변환됨.

프론트에서 해석 = 로그아웃처럼 보임

클라이언트 입장에선 “권한 없음(403)” 응답을 받으니, 자동으로 로그인 세션이 끊겼다고 판단 → 마치 로그아웃된 것처럼 보임.

이번 경험을 통해 얻은 교훈은

- OSIV=false 환경에서는 Lazy 로딩 시 반드시 @Transactional 범위 안에서 처리해야 한다.

- 예외 처리가 허술하면, 전혀 엉뚱한 증상(로그아웃처럼 보이는 문제)으로 이어질 수 있다.

- 운영 환경에서는 OSIV=false가 더 안전하지만, 그만큼 개발자가 책임져야 할 부분(@Transactional, DTO 변환)이 많다.

LazyInitializationException 자체는 단순 JPA 문제지만, 실제 서비스에서는 Security까지 영향을 미쳐 사용자 경험상 “로그아웃 현상”으로 오해될 수 있다. 따라서 open-in-view=false 환경에서는 반드시 필요한 곳에 DTO 변환과 @Transactional을 꼼꼼히 걸어줘야 한다.

 

 

결론 : 야근 하기 싫으면 OSIV=false일때 Lazy로딩이 있다면 @Transactional 잘 붙이자 그냥 잘 붙이자