[JPA] OSIV(Open-Session-In-View)와 지연로딩에 대해서

저번 포스팅에 이이서 이번에는 지연로딩과 OSIV에 대해서 더 구체적으로 알아보려고 한다.

 

OSIV를 알기 위해 영속성 컨텍스트에 대해 공부한 글이다.

https://seungjjun.tistory.com/232

 

[JPA] 영속성 컨텍스트란? (엔티티 생명주기)

오늘은 이전 프로젝트 진행할 때 실시간 알림 구현중 OSIV 때문에 connection이 반환되지 않아 문제가 발생했던 적이 있었는데 해당 문제가 왜 발생했는지 알아보기 위해 2번의 포스팅(영속성 컨텍

seungjjun.tistory.com

 

OSIV를 켜두면 뷰단에서도 지연로딩을 사용할 수 있다고 배웠는데 지연로딩이 무엇인지 알아보자.

 

지연로딩에 대해 공부하기 이전에 프록시(Proxy)라는 개념을 알아야 한다.

프록시란?

프록시는 실제 클래스를 상속 받아서 만들어진 가짜 객체라고 생각하자. 가짜 객체이긴 하지만 실제 클래스와 겉 모양이 같아서 사용하는 입장에서는 구분하지 않고 사용한다.

 

그리고 프록시 객체는 실제 객체의 참조를 보관한다는 특징을 갖고있다. 그래서 프록시 객체를 통해서 메서드를 호출하면 실제 객체의 메서드를 호출한다.

 

 

JPA에서 객체를 조회하는 두 가지 메서드( em.find(), em.getReference() )가 있는데 그중 em.getReference() 메서드가 프록시 객체를 조회한다.

 

지연로딩이란?

그럼 지연로딩은 무엇이고 프록시와 지연로딩은 어떤 관계가 있는지 살펴보자.

 

그전에 JPA의 Fetch Type을 알아야 하는데 Fetch Type은 데이터를 조회할 때 연관관계에 있는 데이터를 어떻게 가져올지 설정하는 것이다. 지연 로딩(LAZY)과 즉시 로딩(EAGER) 2가지가 방식이 존재한다. 

 

지연로딩

지연로딩은 데이터를 조회할 때 자신과 연관된 데이터를 실제로 사용할 때 조회하는 방식이다.

 

즉시로딩

즉시로딩은 데이터를 조회할 때 연관된 데이터를 한 번에 불러오는 방식이다.

 

지연로딩이 왜 필요한지, 즉시로딩의 문제점을 한번 살펴보자

Team 엔티티와 Member 엔티티가 서로 연관관계가 설정되어 있는 상황이고 member의 이름이 필요한 상황이라고 가정해 보자

member의 이름을 얻기 위해 즉시로딩으로 Member 엔티티를 조회하면 어떻게 될까?

 

멤버의 이름을 얻기 위해서 멤버를 조호했지만 멤버의 이름뿐만 아니라 연관관계가 설정된 team 엔티티도 조회가 되는 상황이 발생할 것이다.

 

만일 위와 같은 상황에서 Member와 연관된 엔티티가 Team 뿐만 아니라 N개가 연관되어 있다면 Member 엔티티 하나만 조회해도 + N개의 엔티티가 더 조회되는 문제가 발생한다. ( JPA N + 1 문제 )

 

그래서 엔티티를 조회할 때 지연로딩 방식을 사용할 것인지 즉시로딩 방식을 사용할것인지 적절하게 fetch 전력을 세우는 것이 중요하다.

 

그러면 우리가 프록시라는 개념이 지연로딩 시 어떻게 사용되는지 알아보자.

이번에는 지연로딩으로 Member 엔티티를 조회한다고 가정해 보자. 

 

즉시로딩 시에는 멤버 엔티티를 조회하면 팀 엔티티까지 조회되는 문제가 있었는데 지연로딩을 사용하면 팀을 바로 조회하지 않고 team 멤버 변수에 프록시 객체를 넣어둔다. 그리고 실제로 팀 데이터가 필요한 순간에 DB에서 팀 데이터를 조회하는 쿼리문을 날리고 프록시 객체를 초기화한다.

 

 

 

그렇다면 다시 근본적인 문제로 돌아와서 OSIV가 켜져 있었을 때, connection이 반납되지 않았던 이유를 알아보자.

일단 OSIV가 켜져 있으면 Service, Repository단에서 트랜잭션이 끝나도 영속 상태를 끝까지 유지한다.

사진을 보면서 무슨 의미인지 자세히 살펴보자.

일단 그림을 보면 클라이언트가 요청을 하면 영속성 컨텍스트가 생성이 되고 DB 커넥션을 가져온다. 그리고 트랜잭션의 범위가 Service와 Repository까지 인 것을 알 수 있다.

그런데 OSIV가 true이면 @Transactional의 범위를 벗어나도 커넥션을 계속 유지한다. 즉 클라이언트가 요청한 API응답이 끝나고 view단인 화면이 렌더링 될 때까지 connection을 반환하지 않고 계속 갖고 있는다.

 

여기서 문제가 발생했는데 실시간으로 알림을 받아오기 위한 SSE 통신은 클라이언트의 요청이 pending 상태이기 때문에 connection을 반환하지 않는 문제가 있었다. 

 

즉, 사용자가 10명이 넘어가면 기본으로 생성된 connection 10개가 고갈이 되어 더 이상 사용할 connection이 없어 데이터를 받아오지 못하는 문제였다.

 

그래서 OSIV를 false로 설정해 주고 (spring.jpa.open-in-view:false) Transaction이 끝나는 순간에 connection을 반환하도록 설정해 주었다.