OSIV(Open Session In View) 란?
OSIV란 영속성 컨텍스트를 View 영역까지 열어두는 기능이다.
즉, View 레이어에서도 지연로딩과 같은 영속성 컨텍스트의 특징을 사용할 수 있다.
Spring boot에서 OSIV는 default가 true기 때문에 따로 설정을 바꿔주지 않는 한 항상 실행된다.
데이터베이스 커넥션
JPA는 언제 데이터베이스 커넥션을 가져오고 언제 커넥션을 반환할까?
JPA에서 영속성 컨텍스트를 사용하기 위해선 당연히 데이터베이스 커넥션을 사용해야한다.
기본적으로 데이터베이스 트랜잭션을 시작할 때 영속성 컨텍스트가 데이터베이스 커넥션을 가져온다.
다음과 같이 보통 Service 단에서 트랜잭션이 시작하니 위의 트랜잭션 코드가 실행될 때 데이터베이스 커넥션이 만들어지는 것이다.
그렇다면 데이터베이스 커넥션은 언제 DB에 반환될까?
OSIV가 true로 설정돼있으면 이 데이터베이스 커넥션은 한 트랜잭션이 모두 끝나고 컨트롤러에서 코드를 돌 때까지 반환이 되지 않는다.
그 이유는 api를 살펴봤을 때 만약 객체가 레이지 로딩이라면 객체에 접근 할 때마다
영속성 컨텍스트에서 프록시 객체를 초기화 시키고 데이터를 가져와야하니 데이터베이스 커넥션을 반환하지 않는 것이다.
api가 호출 됐다면 api가 반활될 때까지, 화면인 경우엔 뷰 템플릿이 렌더링 될 때까지 데이터베이스 커넥션이 반환되지 않는다.
하지만 이 전략엔 치명적인 단점이 있다.
OSIV 단점
너무 오랜 시간 데이터베이스 커넥션을 물고있기 때문에, 실시간 트래픽이 중요한 서비스에선
데이터베이스 커넥션을 할당받지 못해 장애로 이어지는 것이다.
극단적인 예로 api에서 또 다른 외부 api를 호출할 때 외부 api가 3초가 걸린다고 하면
데이터베이스 커넥션은 그 3초가 넘는 시간 동안 해당 호출에 묶여있는 것이다.
OSIV를 OFF 하면?
OSIV를 off로 설정하면 딱 트랜잭션의 주기 동안만 데이터베이스 커넥션이 유지가 되고,
영속성 컨텍스트도 트랜잭션 동안만 살아있는다.
데이터베이스 커넥션이 짧은 시간 동안만 유지된다는 장점으로도 이어질 수 있겠지만,
컨트롤러 단에서 지연로딩을 사용하지 못 하니, 한 트랜잭션안에 모든 지연로딩 로직을 다
밀어 넣어야 한다는 단점이 있다.
OSIV를 false로 바꾸고 api에서 지연로딩을 실행하면 다음과 같이 no session 즉, 연결이 없다는 에러를 볼 수 있다.
해결 방법은 트랜잭션 안에서 미리 lazy 로딩 대상을 다 초기화를 시켜 두거나,
fetch join을 사용해서 미리 다 로딩을 해놓거나 두 가지 방법이 있다.
해결 방법
@GetMapping("/api/v3")
public List<OrderDto> ordersV3(){
List<Order> orders = orderRepository.findAllWithItem();
List<OrderDto> result = orders.stream().map(o -> new OrderDto(o)).collect(toList());
return result;
}
예를 들어 위와 같은 api가 있다고 해보자.
여기서 문제가 되는 것은 orderRepository에서 order를 가져온 후
result를 채워 넣을 때 lazy 로딩이 발생한다는 것이다.
이를 해결하기 위해
List<Order> orders = orderRepository.findAllWithItem();
List<OrderDto> result = orders.stream().map(o -> new OrderDto(o)).collect(toList());
return result;
해당 코드를 다른 Query용 Service를 만들어 해당 서비스 안에 전부 넣어버리는 것이다.
Service단에 트랜잭션을 걸고 위의 코드를 전부 집어넣은 메소드를 하나 만든 후
이렇게 api를 수정해주면 간단하게 문제를 해결할 수 있다.
커맨드와 쿼리를 분리하자
보통 비즈니스 로직은 특정 엔티티 몇 개를 등록하거나 수정하는 것이므로 성능이 크게 문제가 되지 않는다.
그런데 복잡한 화면을 출력하기 위한 조회 쿼리는 성능을 최적화 하는 것이 중요하다.
하지만 복잡성에 비해 핵심 비즈니스에 큰 영향을 주지는 않기 때문에,
이 둘의 관심사를 분리하는 선택은 유지보수 관점에서 충분히 의미있다.
단순하게 설명해서 다음처럼 분리하는 것이다.
- OrderService
- OrderService: 핵심 비즈니스 로직
- OrderQueryService: 화면이나 API에 맞춘 서비스 (주로 읽기 전용 트랜잭션)
- 애플리케이션이 작다면 OrderSerivce 하나만 유지하고, 크다면 분리하는 것이 좋다.
결론
고객 서비스의 실시간 API는 OSIV를 끄고, ADMIN 처럼 커넥션을 많이 사용하지 않는 곳에서는
OSIV를 켠다.
참고: 김영한 실전! 스프링 부트와 JPA 활용2
'spring > JPA' 카테고리의 다른 글
[JPA] API 개발과 성능 최적화 3 (페이징과 한계 돌파) (0) | 2024.08.11 |
---|---|
[JPA] API 개발과 성능 최적화 2 (컬렉션 조회 시 성능 최적화) (0) | 2024.08.10 |
[JPA] API 개발과 성능 최적화 1 (지연 로딩과 조회 성능 최적화) (0) | 2024.08.08 |
[JPA] 객체 지향 쿼리 언어 JPQL 7 (엔티티 직접 사용, 네임드 쿼리, 벌크 연산) (0) | 2024.08.07 |
[JPA] 객체 지향 쿼리 언어 JPQL 6 (fetch join의 한계, 다형성 쿼리) (0) | 2024.08.06 |