- Today
- Total
개발하는 고라니
[JPA] OSIV (Open Session In View) 본문
OSIV (Open Session In VIew)
순수 JPA를 공부할 때는 접해본 적 없는 단어이지만, Spring Boot + JPA를 공부하다보면 종종 언급되는 단어이다.
여기서 Session은 서버 단에서 사용자의 데이터를 저장해주는 Session이 아니다.
흔히 알고있는 JPA의 Entity Manager가 Hibernate에서는 Session이라 불린다.
그래서 Hibernate에서는 Open Session In View라 하고, JPA에서는 Open EntityManager In View라 하는데 관례상 OSIV라 한다.
OSIV를 풀어 말해보면, 'View에서도 Session(EntityManager)가 열려있다' 고 말할 수 있겠다. 그럼 이것이 중요할까?
중요하다. 이로 인해 실시간 트래픽이 많은 어플리케이션에서는 장애로 이어질 수 있다고 한다.
일반적으로 생각해보자.
Q. JPA가 DB Connection을 언제 가져온다고 생각하는지?
- Transaction이 시작할 때 커넥션을 가져온다. (O)
Q. JPA가 DB Connection을 언제 반환한다고 생각하는지?
- Transaction이 끝날 때 커넥션을 반환한다. (△)
OSIV가 ON 상태에서는 트랜잭션이 끝나도 데이터베이스 커넥션을 반환하지 않고, 영속성 컨텍스트를 계속 살려둔다.
클라이언트가 요청한 View 또는 API가 반환되어야 커넥션을 반환하고 영속성 컨텍스트를 끝낸다.
반대로 OFF 상태에서는 트랜잭션이 끝나는 시점에 데이터베이스 커넥션을 반환하고, 영속성 컨텍스트 또한 닫힌다.
OSIV ON
spring.jpa.open-in-view: true #default
Spring Boot는 기본적으로 OSIV를 허용한다.
다음과 같은 관계를 갖는 User와 Team 엔티티가 있다 가정하고 예를 들어보겠다.
User : Team = N : 1 이다. 이며 각 엔티티의 매핑은 다음과 같다. 모든 연관관계는 LAZY 로딩으로 설정하였다.
1) User 목록만을 조회하고, Proxy로 들어온 Team을 Controller에서 강제 초기화 해보자.
@GetMapping("/api/v1/users")
public void getUsers(){
List<User> users = userService.getUsers();
users.forEach(u -> {
Team team = u.getTeam();
//Proxy 강제 초기화
System.out.println("team's name = " + team.getName());
});
}
//Proxy를 Json으로 직렬화하려면 별도의 설정이 필요해 예제에선 void로 함
위 코드를 실행하면
1. User 목록 조회쿼리
2. Team A 조회쿼리 (지연로딩)
3. Team B 조회쿼리 (지연로딩)
총 3번의 쿼리가 나간다.
select
team0_.team_id as team_id1_8_0_,
team0_.name as name2_8_0_
from
team team0_
where
team0_.team_id=?
select
team0_.team_id as team_id1_8_0_,
team0_.name as name2_8_0_
from
team team0_
where
team0_.team_id=?
select
team0_.team_id as team_id1_8_0_,
team0_.name as name2_8_0_
from
team team0_
where
team0_.team_id=?
즉, 지연로딩이 Controller에서 된다. 그 말은 영속성 컨텍스트가 Transaction이 끝나도 살아있다는 뜻이다.
요약)
OSIV 전략은 트랜잭션 시작처럼 최초 DB 커넥션 시작 시점부터 API 응답이 끝날 때 까지 영속성 컨텍스트와 DB 커넥션을 유지한다. 그래서 View Template나 API Controller에서 지연 로딩이 가능하다.
장점 : 지연 로딩은 영속성 컨텍스트가 살아있어야 가능하고, 영속성 컨텍스트는 기본적으로 DB 커넥션을 유지한다.
단점 : 너무 오랜시간 DB 커넥션을 물고있기 때문에 실시간 트래픽이 중요한 어플리케이션에서는 커넥션이 모자랄 수 있다. => 서비스 장애
OSIV OFF
spring.jpa.open-in-view: false
OSIV를 끄고 1) 번을 다시 실행해보자.
LazyInitializationException: could not initialize proxy [com.jpabook.jpashop2.exam.Team#20] - no Session
라는 예외를 만나게 될 것이다. 이제 no Session을 보면, 영속성 컨텍스트가 없다고 이해하면 될 것 같다.
즉, Controller으로 넘어온 시점. 다시 말해 서비스 단에서 살아있던 Transaction이 끝났으므로 Controller에서는 Connection을 반환한 상태이고, 영속성 컨텍스트 또한 죽었다. 그래서 지연 로딩이 되지 않는다.
요약)
OSIV를 끄면 Tx를 종료할 때 영속성 컨텍스트를 닫고, DB Connection도 반환한다.
OSIV를 끄면 모든 지연 로딩을 Tx 안에서 해결해야 한다.
CQS (Command Query Seperation)
이와 관련해서 '커맨드와 쿼리를 분리한다'는 말이 언급된다.
OSIV를 끈 상태에서 복잡성을 관리하는 좋은 방법이라고 한다.
Command : 결과를 반환하지 않고 시스템의 상태를 변화시킨다.
Query : 결과 값을 반환하고, 시스템의 관찰 가능한 상태를 변화시키지 않는다. (free of side effect)
이는 웹 어플리케이션이 이점을 얻는다기 보다, 개발자가 코드를 유지보수 하기 쉬워진다는 장점이 있다. 따라서 개발 전반의 기본 개념으로 깔고가는 것이 좋다고 한다.
이 메서드를 호출했을 때 내부에서 변경이 일어나는 메서드인지, 내부에서 변경이 일어나지 않는 메서드인지 명확히 분리하는 것이다.
이렇게 하면 변경관련 이슈가 발생했을 때 변경이 일어나는 메서드만 찾아 해결하면 되기 때문이다.
김영한님의 권장사항은 다음과 같다.
- INSERT : ID만 반환
- UPDATE : void
- 조회는 내부 변경 로직이 없는 메서드로 설계
결론) 그래서 OSIV를 꺼야할까? 켜야할까? 실시간 트래픽이 많은 어플리케이션에서는 OSIV를 끄고, ADMIN 같이 사용량이 적은 어플리케이션에서는 OSIV를 킨다고 하셨다.
# References
인프런-김영한님의 실전! 스프링 부트와 JPA 활용 2 강의
https://shoark7.github.io/programming/knowledge/command-and-query-method
'Framework > JPA (Hibernate)' 카테고리의 다른 글
[JPA] Querydsl 기본 (0) | 2021.11.20 |
---|---|
[JPA] Spring Data JPA (0) | 2021.10.09 |
[JPA] 벌크 연산 (Bulk Operation) (0) | 2021.08.05 |
[JPA] 페치 조인 (FETCH JOIN) (0) | 2021.08.05 |
[JPA] Cascade / OrphanRemoval (0) | 2021.07.25 |