1. 계기
SpringBoot Application을 실행 후 아래와 같은 로그를 보게 되었다.
그와 동시에 로그 레벨이 warning이라 내용을 살펴보았고, 그 중 spring.jpa.open-in-view 와 관련된 정리를 해보려고 한다.
PostgreSQLDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
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
아래 위주로 내용을 정리해 보려고 한다.
- spring.jpa.open-in-view is enabled by default 경고의 의미는 무엇이며
- 왜 Spring Boot에서 이 경고를 보여주는지
- 그리고 실제 운영에서 겪을 수 있는 문제들은 어떤 것인지
2. OSIV
우선 OSIV가 어떤 것인지 알아볼 필요가 있는데, 기본적으로는 개념과 동작방식 그리고 장단점 정도를 알고 있어야할 것이다.
그와 별개로 JPA에서는 OEIV라고 부르고, Hibernate에서는 OSIV로 부른다고 한다. 하지만 관례상 모두 OSIV로 불리고 있다고 하니 참고하면 좋을 것 같다.
본격적인 OSIV의 개념 설명에 앞서 개념을 더 잘 이해하기 위한 몇가지를 알아둘 필요가 있다.
영속성 컨텍스트, 지연 로딩 에 대해 간략히 설명해 보려고 한다.
영속성 컨텍스트 (Persistence Context)
엔티티를 관리하는 메모리 공간
즉, JPA가 엔티티의 상태를 추적하고 관리하는 1차 캐시
// 1. 동일성 보장
val user1 = entityManager.find(User::class.java, 1L)
val user2 = entityManager.find(User::class.java, 1L)
// user1 === user2 → true (같은 인스턴스)
// 2. 변경 감지 (Dirty Checking)
@Transactional
fun updateUser() {
val user = userRepository.findById(1L).get()
user.name = "변경된 이름" // save() 호출 없이도 자동 업데이트
}
생명주기
- 트랜잭션 시작 : 영속성 컨텍스트 생성
- 트랜잭션 종료 : 영속성 컨텍스트 종료
- OSIV 활성화 시 : HTTP 요청 전체 동안 유지
지연 로딩 (Lazy Loading)
필요할 때만 데이터를 가져오는 전략
즉, 연관된 엔티티를 실제 사용 시점에 DB에서 조회
기본적으로 아래와 같이 동작한다.
@Entity
class User {
@OneToMany(fetch = FetchType.LAZY) // 기본값
var orders: List<Order> = mutableListOf()
}
// 사용 예시
val user = userRepository.findById(1L).get() // User만 조회
println(user.name) // OK
println(user.orders.size) // 이 시점에 Order 조회 쿼리 실행
여러 블로그에도 나와 있듯 주로 발생되는 Exception은 LazyInitializationException 인데, 아래와 같이 영속성 컨텍스트가 종료된 이후 발생된다.
// LazyInitializationException 발생 케이스
@Transactional
fun getUser(): User {
return userRepository.findById(1L).get()
} // 여기서 영속성 컨텍스트 종료
// Controller
fun controllerMethod() {
val user = userService.getUser()
// 영속성 컨텍스트가 없어서 지연 로딩 실패
println(user.orders.size) // Exception 발생
}
OSIV 활성화를 통해 위와 같은 이슈를 해결할 수 있다.
// OSIV 활성화 시
@Controller
class UserController {
fun getUser(): String {
val user = userService.getUser()
// 컨트롤러에서도 지연 로딩 가능
user.orders.forEach { println(it.product) }
return "user-page"
}
}
// OSIV 비활성화 시
@Controller
class UserController {
fun getUser(): String {
val user = userService.getUser()
// 지연 로딩 불가능
// user.orders 접근 시 LazyInitializationException
return "user-page"
}
}
OSIV를 사용하지 않는 경우에는 EntityManager와 Transaction은 같은 라이프 사이클을 가지게 된다.
Transaction이 종료되면서 영속성 컨텍스트도 같이 종료하기 때문이다.
OSIV를 사용하는 경우에는 Transaction은 @Transactional을 선언한 객체/메소드까지만 라이프 사이클이 유지되고 영속성 컨텍스트는 View 영역까지 살아있다.
위 두가지 개념을 통해 OSIV가 필요한 배경을 다시 정리하면,
- 영속성 컨텍스트는 엔티티 관리의 핵심이고, 그 생명주기가 중요하다.
- 지연 로딩은 편리하지만 영속성 컨텍스트가 살아있을 때만 동작한다.
- OSIV는 위 두 개념을 연결하는 다리 역할을 한다.
3. 장단점
장점
- 편리한 Lazy Loading (View에서 엔티티 접근 가능)
- 코드 작성의 단순함
- N+1 문제 회피 가능
단점
- 데이터베이스 커넥션 점유 시간 증가
- 트랜잭션 범위 밖에서의 예기치 못한 쿼리 실행
- 성능 저하 및 커넥션 풀 고갈 위험
-> 실제로 운영에서 open-in-view 셋팅으로 인해 특정 API가 자주 다운되는 이슈에 대한 내용
https://medium.com/frientrip/spring-boot%EC%9D%98-open-in-view-%EA%B7%B8-%EC%9C%84%ED%97%98%EC%84%B1%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC-83483a03e5dc - 디버깅의 어려움
4. 결론
팀 상황에 맞는 선택
- 트래픽이 많은 서비스: OSIV 비활성화 권장
- 프로토타입이나 소규모 서비스: 활성화 고려 가능
점진적 마이그레이션 전략
'Computer Science > 개념 | 이론' 카테고리의 다른 글
Hikari CP (3) | 2025.06.08 |
---|---|
Forward Proxy VS Reverse Proxy (0) | 2025.02.26 |