본문 바로가기
카테고리 없음

Open EntityManager In View / Open Session In View

by 찐세 2021. 6. 7.

이것은 JPA에 대해서 자세하게 학습하지 않았고, 스스로 이해한 내용을 바탕으로 작성하였다.

앞으로 계속 학습하면서 잘못된 부분이 있었다면, 수정을 할 것이다.

 

 

JPA를 사용하면서 이러한 경우를 한번쯤은 만나게 될 것이다.

 

로직을 수행하면서 객체의 상태를 변경했는데, 왜 뷰에 출력된 내용에는 변경사항이 반영이 안되어있지..?!

 

 

뷰에 출력하는 내용은 보통 DB에서 읽어온 내용인데, 이것이 변경되지 않았다는 것은 객체의 변경사항이 DB에 반영이 되지 않았다고 할 수 있을 것이다!!

 

 

이런 상황에 대해서 한번 정리를 해보고자 한다.

 

그러기 위해서는 먼저 영속성 컨텍스트에 대해서 간단하게 알아볼 필요가 있다.

 

 

영속성 컨텍스트는 엔티티를 영구 저장하는 환경이라는 뜻이다.

뭔 소린지 모르겠다...@u@ 영속성 컨텍스트를 직접 사용하는 관점에서 보면 이렇게 정의할 수 있을 것 같다.

애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스 같은 역할. 엔티티 매니저를 통해 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.

애플리케이션과 데이터베이스 사이에서 ORM의 기능을 수행하면서 사용되는 환경이라고 이해하면 될 지 모르겠다.

출처 : https://aishwaryavaishno.files.wordpress.com/2013/06/jpa.png

요런식으로..??

 

 

가상의 데이터베이스라고 생각을 해본다면....

예를 들어, 애플리케이션에서 DB에서 데이터를 참조하여 엔티티를 불러오고자 한다면 DB에서 직접 가져오는 것이 아닌 영속성 컨텍스트에서 가져오는 것이고, 반대로 애플리케이션의 엔티티를 생성, 수정 후에 저장하려고 할 때 먼저 영속성 컨텍스트에 저장하고 영속성 컨텍스트에서 일괄적으로 DB에 저장을 한다고 볼 수 있을 것같다.

정확하게 이렇게 이해를 하는 것이 맞는지는 모르겠지만, 가상의 데이터베이스라 한다면 애플리케이션과 DB를 매개하는 중간 DB역할을 한다고 이해를 해봤다.

 

 

 

그럼 다시 본론으로 돌아와서 영속성 컨텍스트를 요청 처리의 관점에서 살펴보자.

우선 스프링 부트 애플리케이션에는 Open EntityManager In View filter 또는 Open Session In View filter가 기본적으로 등록이 되어있고, 활성화 되어있다.

이 필터가 하는 역할은  JPA Entity Manager, Hibernate의 session 즉, 영속성 컨텍스트를 요청을 처리하는 전체 프로세스에 바인딩해주는 역할을 한다.

영속성 컨텍스트가 존재하는 한 DB에서 읽어온 객체는 영속성 컨텍스트에서 관리하는 persist 상태의 객체가 된다. 이는 별도로 해제하지 않는 이상 영속성 컨텍스트가 끝날때까지 유지된다.

 

 

그렇다면 영속성 컨텍스트가 DB에서 읽어온 객체를 관리해준다면서... 왜 변경사항을 DB에 반영해주지 않는 건데..?? 라는 질문을 할 수 있을 것이다.

persist 상태의 객체 일지라도, 변경 사항을 감지하기 위해서는 트랜잭션 내부에서 객체가 변경되어야 한다. 

즉, 트랜잭션 내부에서 변경된 persist 객체에 대해서만 영속성 컨텍스트가 변경사항을 감지하고 있다가, 트랜잭션이 종료될 때 update 쿼리를 발생시켜 변경사항을 DB에 반영하는 것이다.

그렇기 때문에 "객체를 변경했는데... 변경사항이 적용이 안됐네.." 같은 상황은 객체의 변경이 트랜잭션 밖에서 일어났을 가능성이 높다.

 

 

Repository (JpaRepository)는 그 구현체인 SimpleJpaRepository를 보면 @Transactional(readOnly = true)로 트랜잭션이 적용되어 있는 것을 알 수 있다.  DB를 통해 객체를 읽어오는 트랜잭션이다.

 

 

하지만 Service클래스는 기본적으로 트랜잭션이 적용되어있지 않기 때문에 별도로 트랜잭션을 지정해줘야한다. @Transactional을 통해서 특정 메서드 단위로 지정하거나 클래스 전체에 대해서 지정을 할 수있다.

그렇다면 Service 내부 로직은 트랜잭션 내부에서 수행이 될 것이고, 객체의 변경사항이 감지되고 트랜잭션이 끝날 때 변경 사항이 DB에 반영이 될 것이다.

 

 

 

추가로 Open EntityManager in View / Open Session In Vew 에서 View 라는 단어는 왜 사용되는 것인지 한번 알아보려고 한다.

그대로 직역하면 영속성 컨텍스트를 view에서도 열 것인가? 이다.

그렇기 때문에 영속성 컨텍스트가 종료되는 시점은 뷰 랜더링이 끝나고 나서인데, 그렇다는 말은 뷰 랜더링 과정에서도 영속성 컨텍스를 사용할 수 있다는 말이다.

그래서 필요한 데이터를 랜더링 하는 시점에 추가로 읽어오는 것도 가능하다.(lazy loading)

 

 

이것이 불가능하다면 뷰 랜더링을 위해서 뷰에 필요한 모든 정보를 종합해서 model 을 통해서 넘겨줘야 하는데, 이러지 않아도 된다는 것이다. 

뷰에서 도메인 기반으로 객체를 네비게이팅 하면서 추가로 필요할 때 로딩을 할 수 있다는 것이다. 

일단 이렇게 종합해서 뷰에 전달할 필요가 없다는 점이 효율적인 측면에서 도움이 되는지는 와닿지는 않지만, 개발을 하는 과정에서는 편리했던 것같다. 하지만 추가로 로딩을 하게 되면 그에 따른 쿼리도 마찬가지로 발생하기 때문에 이는 필요에 따라서 최적화를 해 줄 필요성이 있어보인다. 

 

 

위와 같은 내용을 글로 접하다 보니 막 와닿지는 않은데, 무턱대고 공부를 하다가 위와 같은 현상을 맞닥뜨린 경우가 있었던 것 같다. ( 여기에 완전하게 해당되는 것이 맞는 지 모르겠지만!)

 

 

N+1 SELECT 문제가 발생했던 적이 있었다. 뷰에서 객체를 네비게이팅 하면서 필요한 값을 사용하고 있었는데, 다른 도메인(테이블)과 관계를 맺고 있는 필드, 즉, 관계를 형성하고 있는 도메인을 기존 도메인의 값을 통해서 참조할 때 lazy loading 에 의해서 쿼리가 발생했었다. 이는 곧, '일대다 관계'를 형성할때 '다'에 해당하는 'N'만큼 쿼리가 발생하였으니 성능에 영향을 미치는 것이었다. 그래서 위의 설명은 이런 부분을 최적화해야 한다는 것을 전달하는 것 같다.

(N+1 SELECT 문제는 FETCH 타입을 Lazy가 아닌 Eager로 해결했었던 것 같다! -> FETCH 타입과 로딩에 대해서도 한번

정리를 해야할 것같다.)

 

 

아무튼 아무튼!! 이렇게 Open Session In VIew(통상 OISV 라고 하더라)를 통해서 뷰를 랜더링 할 때도 영속성 컨텍스트를 사용해서 데이터를 받아올 수 있다.

 

 

OSIV은 스프링 부트가 자동 설정으로 Open Session In View filter / Open EntityManager In VIew 를 통해서 지원을 한다. 이 때 영속성 컨텍스트의 생존 범위는 뷰 랜더링이 끝날때 까지 라고했다. 

 

 

하지만 이 OSIV 설정이 적용되지 않으면 영속성 컨텍스트의 생존 범위는 트랜잭션의 범위와 동일해진다.

 

 

 

REFERENCE


https://velog.io/@neptunes032/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%9E%80

 

https://woowacourse.github.io/javable/post/2020-09-11-osiv/