You can make anything
by writing

C.S.Lewis

by anonymDev May 15. 2023

코드로 보는 스프링부트 OSIV 동작 원리

OSIV와 EntityManager LifeCycle

OSIV가 무엇인지 간단하게 알아보자


OSIV는 Open-Session-In-View Pattern의 약어이다. OSIV 패턴은 트랜잭션(Transaction)이 외부에서 엔티티(Entity)의 지연 로딩(Lazy Loading) 기능을 제공한다. 트랜잭션이 종료된 후에 엔티티매니저(카지노 게임 사이트)가 닫히게 되면 기본적으로 지연 로딩이 제공되지 않는다.

하지만 spring.jpa.open-in-view 를 true로 설정하면 트랜잭션이 종료된 후에도 엔티티 지연 로딩이 가능하다.


spring-boot과 Open카지노 게임 사이트InViewInterceptor


spring-boot autoconfiguration 기능을 활용하면 OSIV 기능을 간편하게 활용할 수 있다.

spirng-boot를 open-in-view 설정을 활성화하면 JpaWebConfiguration이 활성화되어

open카지노 게임 사이트InViewInterceptor 빈이 WebRequestInterceptor에 추가된다.


(코드)
@ConditionalOnProperty(prefix = "spring.jpa", name = "open-in-view", havingValue = "true", matchIfMissing = true)
protected static classJpaWebConfiguration {
...

/**

* open카지노 게임 사이트InViewInterceptor 빈을 생성한다.

**/
@Bean
publicOpen카지노 게임 사이트InViewInterceptor openEntityManagerInViewInterceptor() {
if(this.jpaProperties.getOpenInView() == null) {
logger.warn("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");
}
return newOpen카지노 게임 사이트InViewInterceptor();
}

/**

* open카지노 게임 사이트InViewInterceptor 빈을 WebRequestInterceptor에 추가한다.

**/
@Bean
publicWebMvcConfigurer open카지노 게임 사이트InViewInterceptorConfigurer(
Open카지노 게임 사이트InViewInterceptor interceptor) {
return newWebMvcConfigurer() {

@Override
public voidaddInterceptors(InterceptorRegistry registry) {
registry.addWebRequestInterceptor(interceptor);
}
...후략


Open카지노 게임 사이트InViewInterceptor는 Web Request Interceptor 중 하나다. 웹으로부터 요청이 들어오면 요청을 가로채서 무언가를 한다. 그 무엇인가가 무엇인고 하면 OSIV의 시작과 끝맺음이다. 자세한 내용은 코드를 보며 살펴보자.


public classOpen카지노 게임 사이트InViewInterceptor extends카지노 게임 사이트FactoryAccessor implements AsyncWebRequestInterceptor {



Open카지노 게임 사이트InViewInterceptor와 OSIV의 시작


시작은 매우 단순한다. 요청이 들어오면 인터셉터의 prehandle() 메서드가 호출돼 요청을 받는다.

TransactionSynchronizationManager를 통해 현재 요청을 처리하는 스레드에 카지노 게임 사이트를 생성해서 추가하는 것이다. 이게 다라고? 맞다. 이게 전부이다.


@Override
public void preHandle(WebRequest request) throws DataAccessException {
(중략)...

/**

* 카지노 게임 사이트를 가져온다.

**/
EntityManagerFactory emf = obtainEntityManagerFactory();
...(중략)
else {


logger.debug("Opening JPA EntityManager in Open카지노 게임 사이트InViewInterceptor");
try {

/**

* 1. 카지노 게임 사이트를 생성한다.

* 2. 카지노 게임 사이트를 현재 Thread에 바인딩한다. (TransactionSynchronizationManager 활용)

**/
EntityManager em = createEntityManager();
EntityManagerHolder emHolder = new EntityManagerHolder(em);
TransactionSynchronizationManager.bindResource(emf, emHolder);

... (후략)


간단한 추가 동작이지만 TransactionSynchronizationManager를 통해 스레드에 카지노 게임 사이트가 바인딩되면 추후에 카지노 게임 사이트의 라이프 사이클이 완전히 달라진다.



트랜잭션의 시작과 카지노 게임 사이트의 생성


트랜잭션이 시작될 때 생성되고 종료될 때 소멸하는 게 카지노 게임 사이트의 기본적인 라이프사이클이다. 트랜잭션과 엔티티매니저는 생명주기를 같이한다.


아래 코드는 트랜잭션이 시작될 때 호출되는 JpaTransactionManager의 doGetTransaction 메서드다.

(코드)

@Override
protectedObject doGetTransaction() {
JpaTransactionObject txObject = new JpaTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());

/**

* 현재 스레드에 바인딩된 카지노 게임 사이트를 조회한다.

* 카지노 게임 사이트가 존재하면(!=null) 트랜잭션 객채(txObject)에 카지노 게임 사이트를 바인딩한다.

**/

카지노 게임 사이트Holder emHolder = (카지노 게임 사이트Holder)
TransactionSynchronizationManager.getResource(obtainEntityManagerFactory());
if (emHolder != null) {
if (logger.isDebugEnabled()) {
logger.debug("Found thread-bound EntityManager [" + emHolder.getEntityManager() +
"] for JPA transaction");
}
txObject.set카지노 게임 사이트Holder(emHolder, false);
}
...(중략)
returntxObject;
}


TransactionSynchronizationManager에 추가된 카지노 게임 사이트를 가져와 트랜잭션 객체에 추가한다. set카지노 게임 사이트Holder 호출을 통해서 트랜잭션 객체에 카지노 게임 사이트가 바인딩된다.


setEntityManagerHolder의 두 번째 파라미터 new카지노 게임 사이트Holder를 눈여겨봐야 한다.false로 값을 세팅해주고 있다. 바인딩된 EntityManager가 신규 생성 됐는지 여부를 알려주는 Flag이다. 트랜잭션과 함께 생성된 EntityManager라면 true가 되고 아니라면 값은 false가 될 것이다.

public voidset카지노 게임 사이트Holder(

@Nullable 카지노 게임 사이트Holder 카지노 게임 사이트Holder,

booleannew카지노 게임 사이트Holder // 두 번째 파라미터

) {
this.entityManagerHolder = entityManagerHolder;
this.new카지노 게임 사이트Holder= new카지노 게임 사이트Holder;
}


OSIV 하에서는 EntityManager가 TransactionSynchronizationManager에 기존에 존재하고 있었기 때문에 false가 된다. new카지노 게임 사이트Holder도 카지노 게임 사이트의 라이프사이클을 결정하는 중요한 값이다. 뒤에서 어떻게 활용될 예정이므로 기억해 두자.

미리 설명해 두면 new카지노 게임 사이트Holder는 트랜잭션이 커밋 후 JpaTransactionManager가 EntityManager를 닫을지 말지를 결정할 때 참조하는 필드이다.


보너스

다음으로 넘어가기 전에 보너스로 OSIV가 활성화 돼있지 않다면 EntityManager는 어떻게 트랜잭션 객체에 추가되는 지 알아보자.


이후에 doBegin 메서드에서 신규로 생성된다. 그리고 두 번째 파라미터 new카지노 게임 사이트Holdertrue가 된다.

Open카지노 게임 사이트InViewInterceptor에서 EntityManager가 바인딩된 경우를 제외하곤 대부분 신규로 생성된다고 보면 된다.

(코드)

@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
JpaTransactionObject txObject = (JpaTransactionObject) transaction;
...(중략)

try {

/**

* 트랜잭션 객체에 카지노 게임 사이트가 바인딩 안 됐다면 신규로 생성해 추가한다.

**/
if (!txObject.hasEntityManagerHolder() ||
txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
EntityManager newEm = createEntityManagerForTransaction();
if (logger.isDebugEnabled()) {
logger.debug("Opened new EntityManager [" + newEm + "] for JPA transaction");
}
txObject.set카지노 게임 사이트Holder(new EntityManagerHolder(newEm), true);
}




EntityManager 죽느냐 사느냐. 그것은 new카지노 게임 사이트Holder의 문제이다


JpaTransactionManager는 트랜잭션이 종료되면 doCleanupAfterCompletion을 호출한다.

트랜잭션과 카지노 게임 사이트는 생명주기를 함께 했다고 앞서 말했다. 따라서 트랜잭션 소멸 메서드인 doCleanupAfterCompletion에서 카지노 게임 사이트도 소멸하게 된다.

@Override
protected voiddoCleanupAfterCompletion(Object transaction) {
JpaTransactionObject txObject = (JpaTransactionObject) transaction;
...(중략)


// Remove the entity manager holder from the thread.
if(txObject.isNewEntityManagerHolder()) { // 트랜잭션 객체의 EntityManager가 신규 생성됐는가?
EntityManager em = txObject.getEntityManagerHolder().getEntityManager();
if (logger.isDebugEnabled()) {
logger.debug("Closing JPA EntityManager [" + em + "] after transaction");
}

// 카지노 게임 사이트를 소멸한다.
EntityManagerFactoryUtils.closeEntityManager(em);
}
else {
logger.debug("Not closing pre-bound JPA EntityManager after transaction");
}
}


카지노 게임 사이트FactoryUtils.close카지노 게임 사이트(em)를 호출해 카지노 게임 사이트를 소멸한다. 하지만 카지노 게임 사이트의 소멸은 조건부이다. if(txObject.isNew카지노 게임 사이트Holder()) 조건이 참일 경우에만 카지노 게임 사이트를 소멸한다.


isNew카지노 게임 사이트Holder 메서드는 new카지노 게임 사이트Holder의 값을 그대로 반환한다. OSIV가 활성화된 경우에는 항상 if(txObject.isNew카지노 게임 사이트Holder())조건을 충족할 수 없으므로 EntityManager는 트랜잭션과 함께 소멸되지 않는다.


OSIV 하에서 트랜잭션이 종료된 후에도 엔티티 지연 로딩이 가능한 이유이다.


스레드에 카지노 게임 사이트가 상주하고 있는 거랑 지연로딩이랑 무슨 상관이죠? 이건 다음 글에서 자세하게 다시 알아보자.


그러면 카지노 게임 사이트는 언제 닫힐까?


앞서서 Open카지노 게임 사이트InViewInterceptor가 OSIV의 시작과 끝맺음을 한다고 했던 것을 기억하는가? EntityManager를 시작부에서 생성했듯이 소멸의 역할도 하며 끝맺음을 한다.

그 시점은 request가 종료되고 Open카지노 게임 사이트InViewInterceptor의 afterCompletion이 호출될 때이다.

@Override
public voidafterCompletion(WebRequest request, @Nullable Exception ex) {
if (!decrementParticipateCount(request)) {

/**

* 스레드에서 EntityMananger를 해제하고 소멸한다.

**/
카지노 게임 사이트Holder emHolder = (카지노 게임 사이트Holder)
TransactionSynchronizationManager.unbindResource(obtainEntityManagerFactory());
logger.debug("Closing JPA EntityManager in Open카지노 게임 사이트InViewInterceptor");
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}
}


요약하면

1. open-in-view: true로 설정하면 Open카지노 게임 사이트InViewInterceptor가 요청을 가로챈다.

2. 요청이 시작되면 인터셉터에서 카지노 게임 사이트를 생성한다.

3. (트랜잭션이 종료될 때가 아니라) 응답이 나갈 때 인터셉터에서 카지노 게임 사이트를 소멸한다.

4. 즉 요청을 처리하는 동안 카지노 게임 사이트는 스레드에 상주하며 마르고 닳도록 재사용된다.


이것이 간단하지만 복잡한 OSIV의 구현이다.




카지노 게임 사이트의 재사용 OSIV가 효율적인 방안인가?


카지노 게임 사이트를 여러 차례 생성하지 않고 재사용한다고 하면 마냥 효율적인 방안이라고 생각할 수 있다.

하지만 EntityManage가 열려있다는 것은 Connection도 열려있는 상태를 유지한다는 의미이기도 하다. 카지노 게임 사이트는 Connection 객체를 한번 연결하면 종료하지 않고 다음 요청에 재사용한다.


LogincalConnectionManagedImpl.java

privateConnection acquireConnectionIfNeeded() {

if ( physicalConnection == null ) {

/**

* 기존에 생성해 둔 연결이 없을 경우에만 신규로 생성한다.

**/
try {
physicalConnection = jdbcConnectionAccess.obtainConnection();
}
catch (SQLException e) {
throw sqlExceptionHelper.convert( e, "Unable to acquire JDBC Connection" );
}
finally {
observer.jdbcConnectionAcquisitionEnd( physicalConnection );
}
}
return physicalConnection;
}


즉 트랜잭션이 종료된 후에도 데이터베이스 연결을 점유하고 있다는 의미가 된다. 트랜잭션이 종료된 후에 요청이 짧은 시간 안에 완료된다면 큰 문제가 안되지만 트랜잭션 밖의(데이터베이스 작업과 상관없는) 별개의 큰 작업이 남아있다면 오히려 비효율적인 결과를 초래할 수도 있으므로 주의해야 한다.


진짜 끝

브런치는 최신 브라우저에 최적화 되어있습니다.