session per request란 영속성 세션과 요청 라이프사이클을 같이 묶기 위한 트랜잭션 패턴이다. 스프링은 이 패턴의 자체적으로 구현했는데, 이것이 바로 OpenSessionInViewInterceptor
이다. 이 패턴을 통해 lazy 연관관계를 사용하는 것을 용이하게해 개발 생산성을 높여준다.
Service
레이어의 @Transactional
을 벗어나 컨트롤러 또는 뷰에서도 lazy 로딩이 가능하다 OSIV를 사용한 어플리케이션에서 요청의 흐름은 다음과 같다
스프링 부트 어플리케이션은 기본적으로 OSIV가 활성화 되어 있다. 하지만 스프링 부트 2.0부터 명시적으로 설정하지 않을 시 경고가 표시된다
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
@Entity
public class User {
@Id
private Long id;
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private List<Post> posts = new ArrayList<>();
}
@Service
@AllArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional(readOnly = true)
public Optional<User> findUserById(Long userId) {
return userRepository.findByUserId(userId);
}
}
위와 같은 코드를 OSIV 패턴 없이 사용한다면, 흐름은 다음과 같다
findUserById
의 종료와 함께 트랜잭션이 커밋된다면, 클라이언트 코드에서 User
객체의 posts
는 로딩되지 않았고 이제는 준영속 상태이기 때문에 사용할 수 없다. posts
에 접근하면, LazyInitializationException
이 발생한다
하지만 OSIV를 활성화한다면 세션의 종료 시점은 요청의 종료시점이기 때문에 트랜잭션 프록시는 새로운 세션을 생성하는 것이 아니라 기존의 세션을 사용한다
@Transactional(readOnly = true)
public Optional<User> findUserById(Long userId) {
Optional<User> user = userRepository.findByUserId(userId);
if (user.isPresnet()) {
// 외부 무엇인가 호출
}
return user;
}
만약 findUserById
를 사용하는 요청이 무수히 많이 들어온다면, 결국 커넥션 풀은 커넥션이 바닥날 것이다. 심지어 문제의 원인(느린 외부 호출)이 DB과 연관이 없으므로, 디버깅도 힘들다
@Transactional
을 제거해 위 상황을 피할 수 있다. 하지만 OSIV가 활성화 되어 있다면 이와 상관없이 요청이 끝날때까지 커넥션이 유지될 것이다
OSIV를 사용하면 커넥션 풀을 소진하는 것 이외에도, 객체 그래프 탐색으로 인한 원치않는 쿼리가 발생할 수도 있다. 심지어 트랜잭션을 관리하지 않으면 쿼리들이 auto-commit mode
로 동작해 쿼리 하나하나가 커밋된다.
간단한 서비스라면 OSIV의 생산성 증대가 도움이 될 수도 있다
하지만 조금이라도 서비스가 복잡해지면 OSIV로 인한 문제들이 발생할 수 있다. 특히 외부 호출이 잦거나 트랜잭션 밖에서 로직이 많은 경우 OSIV를 비활성화하는 것이 매우 추천된다
A Guide to Spring’s Open Session In View : https://www.baeldung.com/spring-open-session-in-view
[JPA]open-session-in-view 를 알아보자 : https://gracelove91.tistory.com/100