OSIV는 언제 쓸까?

이상민·2021년 11월 1일
1

JPA

목록 보기
3/8
post-thumbnail

1. Session per Request

session per request란 영속성 세션과 요청 라이프사이클을 같이 묶기 위한 트랜잭션 패턴이다. 스프링은 이 패턴의 자체적으로 구현했는데, 이것이 바로 OpenSessionInViewInterceptor이다. 이 패턴을 통해 lazy 연관관계를 사용하는 것을 용이하게해 개발 생산성을 높여준다.

  • OSIV를 사용한다면 요청의 시작과 끝동안 영속성 컨텍스트가 유지되기 때문에, Service레이어의 @Transactional을 벗어나 컨트롤러 또는 뷰에서도 lazy 로딩이 가능하다

2. 스프링 부트와 OSIV

  • OSIV를 사용한 어플리케이션에서 요청의 흐름은 다음과 같다

    1. 스프링이 요청의 시작에 새로운 하이버네이트 세션을 생성한다
    2. 어플리케이션이 세션이 필요할때마다 이미 존재하는 세션을 재사용한다
    3. 요청 종료때, 동일한 인터셉터가 세션을 닫는다
  • 스프링 부트 어플리케이션은 기본적으로 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

3. OSIV 옹호와 반대

3-1. 옹호 : 생산성

  • 위에서 언급한 것처럼, OSIV 패턴을 사용하면 lazy 연관의 사용이 더 편하다
@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 패턴 없이 사용한다면, 흐름은 다음과 같다

    1. 스프링 프록시가 호출을 인터셉트하고 현재 트랜잭션을 갖거나, 없다면 생성한다
    2. 프록시가 메소드 호출을 구현에 위임한다
    3. 프록시가 트랜잭션을 커밋하고, 세션을 닫는다
      *세션 : 엔티티 매니저와 이로 관리되는 영속성 컨택스트
  • findUserById의 종료와 함께 트랜잭션이 커밋된다면, 클라이언트 코드에서 User 객체의 posts는 로딩되지 않았고 이제는 준영속 상태이기 때문에 사용할 수 없다. posts에 접근하면, LazyInitializationException이 발생한다

  • 하지만 OSIV를 활성화한다면 세션의 종료 시점은 요청의 종료시점이기 때문에 트랜잭션 프록시는 새로운 세션을 생성하는 것이 아니라 기존의 세션을 사용한다

3-2. 반대 : 성능

  • 서비스 메소드를 다음처럼 외부 호출을 사용했다고 하자
    @Transactional(readOnly = true)
    public Optional<User> findUserById(Long userId) {
        Optional<User> user = userRepository.findByUserId(userId);
        
        if (user.isPresnet()) {
          // 외부 무엇인가 호출
        }
        
        return user;
    }
  1. 스프링 프록시가 현재 세션을 갖거나 새로 생성한다, 아직 새션은 DB 커넥션을 얻지 않았다
  2. 유저를 찾는 쿼리를 수행하기 전 세션은 커넥션 풀에서 커넥션을 빌려와 연결한다
  3. 외부 호출을 했을때 모종의 이유로 시간이 오래걸린다면, 세션을 커넥션을 유지한채로 기다린다
  • 만약 findUserById를 사용하는 요청이 무수히 많이 들어온다면, 결국 커넥션 풀은 커넥션이 바닥날 것이다. 심지어 문제의 원인(느린 외부 호출)이 DB과 연관이 없으므로, 디버깅도 힘들다

  • @Transactional을 제거해 위 상황을 피할 수 있다. 하지만 OSIV가 활성화 되어 있다면 이와 상관없이 요청이 끝날때까지 커넥션이 유지될 것이다

  • OSIV를 사용하면 커넥션 풀을 소진하는 것 이외에도, 객체 그래프 탐색으로 인한 원치않는 쿼리가 발생할 수도 있다. 심지어 트랜잭션을 관리하지 않으면 쿼리들이 auto-commit mode로 동작해 쿼리 하나하나가 커밋된다.


4. OSIV를 사용할까 말까?

  • 간단한 서비스라면 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

profile
편하게 읽기 좋은 단위의 포스트를 추구하는 개발자입니다

0개의 댓글